""" Wraps leaflet TileLayer, WmsTileLayer (TileLayer.WMS), ImageOverlay, and VideoOverlay """ from branca.element import Element, Figure from jinja2 import Environment, PackageLoader, Template from folium.map import Layer from folium.utilities import image_to_url, mercator_transform, parse_options ENV = Environment(loader=PackageLoader("folium", "templates")) class TileLayer(Layer): """ Create a tile layer to append on a Map. Parameters ---------- tiles: str or :class:`xyzservices.TileProvider`, default 'OpenStreetMap' Map tileset to use. Can choose from this list of built-in tiles: - "OpenStreetMap" - "Stamen Terrain", "Stamen Toner", "Stamen Watercolor" - "CartoDB positron", "CartoDB dark_matter" You can pass a custom tileset to Folium by passing a :class:`xyzservices.TileProvider` or a Leaflet-style URL to the tiles parameter: ``http://{s}.yourtiles.com/{z}/{x}/{y}.png``. You can find a list of free tile providers here: ``http://leaflet-extras.github.io/leaflet-providers/preview/``. Be sure to check their terms and conditions and to provide attribution with the `attr` keyword. min_zoom: int, default 0 Minimum allowed zoom level for this tile layer. max_zoom: int, default 18 Maximum allowed zoom level for this tile layer. max_native_zoom: int, default None The highest zoom level at which the tile server can provide tiles. If provided you can zoom in past this level. Else tiles will turn grey. attr: string, default None Map tile attribution; only required if passing custom tile URL. detect_retina: bool, default False If true and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution. name : string, default None The name of the Layer, as it will appear in LayerControls overlay : bool, default False Adds the layer as an optional overlay (True) or the base layer (False). control : bool, default True Whether the Layer will be included in LayerControls. show: bool, default True Whether the layer will be shown on opening (only for overlays). subdomains: list of strings, default ['abc'] Subdomains of the tile service. tms: bool, default False If true, inverses Y axis numbering for tiles (turn this on for TMS services). opacity: float, default 1 Sets the opacity for the layer. **kwargs : additional keyword arguments Other keyword arguments are passed as options to the Leaflet tileLayer object. """ _template = Template( """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.tileLayer( {{ this.tiles|tojson }}, {{ this.options|tojson }} ).addTo({{ this._parent.get_name() }}); {% endmacro %} """ ) def __init__( self, tiles="OpenStreetMap", min_zoom=0, max_zoom=18, max_native_zoom=None, attr=None, detect_retina=False, name=None, overlay=False, control=True, show=True, no_wrap=False, subdomains="abc", tms=False, opacity=1, **kwargs ): # check for xyzservices.TileProvider without importing it if isinstance(tiles, dict): attr = attr if attr else tiles.html_attribution min_zoom = tiles.get("min_zoom", min_zoom) max_zoom = tiles.get("max_zoom", max_zoom) subdomains = tiles.get("subdomains", subdomains) tiles = tiles.build_url(fill_subdomain=False, scale_factor="{r}") self.tile_name = ( name if name is not None else "".join(tiles.lower().strip().split()) ) super().__init__( name=self.tile_name, overlay=overlay, control=control, show=show ) self._name = "TileLayer" self._env = ENV tiles_flat = "".join(tiles.lower().strip().split()) if tiles_flat in {"cloudmade", "mapbox", "mapboxbright", "mapboxcontrolroom"}: # added in May 2020 after v0.11.0, remove in a future release raise ValueError( "Built-in templates for Mapbox and Cloudmade have been removed. " "You can still use these providers by passing a URL to the `tiles` " "argument. See the documentation of the `TileLayer` class." ) templates = list( self._env.list_templates(filter_func=lambda x: x.startswith("tiles/")) ) tile_template = "tiles/" + tiles_flat + "/tiles.txt" attr_template = "tiles/" + tiles_flat + "/attr.txt" if tile_template in templates and attr_template in templates: self.tiles = self._env.get_template(tile_template).render() attr = self._env.get_template(attr_template).render() else: self.tiles = tiles if not attr: raise ValueError("Custom tiles must have an attribution.") self.options = parse_options( min_zoom=min_zoom, max_zoom=max_zoom, max_native_zoom=max_native_zoom or max_zoom, no_wrap=no_wrap, attribution=attr, subdomains=subdomains, detect_retina=detect_retina, tms=tms, opacity=opacity, **kwargs ) class WmsTileLayer(Layer): """ Creates a Web Map Service (WMS) layer. Parameters ---------- url : str The url of the WMS server. layers : str Comma-separated list of WMS layers to show. styles : str, optional Comma-separated list of WMS styles. fmt : str, default 'image/jpeg' The format of the service output. Ex: 'image/png' transparent: bool, default False Whether the layer shall allow transparency. version : str, default '1.1.1' Version of the WMS service to use. attr : str, default '' The attribution of the service. Will be displayed in the bottom right corner. name : string, optional The name of the Layer, as it will appear in LayerControls overlay : bool, default True Adds the layer as an optional overlay (True) or the base layer (False). control : bool, default True Whether the Layer will be included in LayerControls. show: bool, default True Whether the layer will be shown on opening (only for overlays). **kwargs : additional keyword arguments Passed through to the underlying tileLayer.wms object and can be used for setting extra tileLayer.wms parameters or as extra parameters in the WMS request. See https://leafletjs.com/reference.html#tilelayer-wms """ _template = Template( """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.tileLayer.wms( {{ this.url|tojson }}, {{ this.options|tojson }} ).addTo({{ this._parent.get_name() }}); {% endmacro %} """ ) # noqa def __init__( self, url, layers, styles="", fmt="image/jpeg", transparent=False, version="1.1.1", attr="", name=None, overlay=True, control=True, show=True, **kwargs ): super().__init__(name=name, overlay=overlay, control=control, show=show) self.url = url kwargs["format"] = fmt cql_filter = kwargs.pop("cql_filter", None) self.options = parse_options( layers=layers, styles=styles, transparent=transparent, version=version, attribution=attr, **kwargs ) if cql_filter: # special parameter that shouldn't be camelized self.options["cql_filter"] = cql_filter class ImageOverlay(Layer): """ Used to load and display a single image over specific bounds of the map, implements ILayer interface. Parameters ---------- image: string, file or array-like object The data you want to draw on the map. * If string, it will be written directly in the output file. * If file, it's content will be converted as embedded in the output file. * If array-like, it will be converted to PNG base64 string and embedded in the output. bounds: list Image bounds on the map in the form [[lat_min, lon_min], [lat_max, lon_max]] opacity: float, default Leaflet's default (1.0) alt: string, default Leaflet's default ('') origin: ['upper' | 'lower'], optional, default 'upper' Place the [0,0] index of the array in the upper left or lower left corner of the axes. colormap: callable, used only for `mono` image. Function of the form [x -> (r,g,b)] or [x -> (r,g,b,a)] for transforming a mono image into RGB. It must output iterables of length 3 or 4, with values between 0 and 1. Hint: you can use colormaps from `matplotlib.cm`. mercator_project: bool, default False. Used only for array-like image. Transforms the data to project (longitude, latitude) coordinates to the Mercator projection. Beware that this will only work if `image` is an array-like object. Note that if used the image will be clipped beyond latitude -85 and 85. pixelated: bool, default True Sharp sharp/crips (True) or aliased corners (False). name : string, default None The name of the Layer, as it will appear in LayerControls overlay : bool, default True Adds the layer as an optional overlay (True) or the base layer (False). control : bool, default True Whether the Layer will be included in LayerControls. show: bool, default True Whether the layer will be shown on opening (only for overlays). See https://leafletjs.com/reference.html#imageoverlay for more options. """ _template = Template( """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.imageOverlay( {{ this.url|tojson }}, {{ this.bounds|tojson }}, {{ this.options|tojson }} ).addTo({{ this._parent.get_name() }}); {% endmacro %} """ ) def __init__( self, image, bounds, origin="upper", colormap=None, mercator_project=False, pixelated=True, name=None, overlay=True, control=True, show=True, **kwargs ): super().__init__(name=name, overlay=overlay, control=control, show=show) self._name = "ImageOverlay" self.bounds = bounds self.options = parse_options(**kwargs) self.pixelated = pixelated if mercator_project: image = mercator_transform( image, [bounds[0][0], bounds[1][0]], origin=origin ) self.url = image_to_url(image, origin=origin, colormap=colormap) def render(self, **kwargs): super().render() figure = self.get_root() assert isinstance( figure, Figure ), "You cannot render this Element if it is not in a Figure." if self.pixelated: pixelated = """ """ figure.header.add_child( Element(pixelated), name="leaflet-image-layer" ) # noqa def _get_self_bounds(self): """ Computes the bounds of the object itself (not including it's children) in the form [[lat_min, lon_min], [lat_max, lon_max]]. """ return self.bounds class VideoOverlay(Layer): """ Used to load and display a video over the map. Parameters ---------- video_url: str URL of the video bounds: list Video bounds on the map in the form [[lat_min, lon_min], [lat_max, lon_max]] autoplay: bool, default True loop: bool, default True name : string, default None The name of the Layer, as it will appear in LayerControls overlay : bool, default True Adds the layer as an optional overlay (True) or the base layer (False). control : bool, default True Whether the Layer will be included in LayerControls. show: bool, default True Whether the layer will be shown on opening (only for overlays). **kwargs: Other valid (possibly inherited) options. See: https://leafletjs.com/reference.html#videooverlay """ _template = Template( """ {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.videoOverlay( {{ this.video_url|tojson }}, {{ this.bounds|tojson }}, {{ this.options|tojson }} ).addTo({{ this._parent.get_name() }}); {% endmacro %} """ ) def __init__( self, video_url, bounds, autoplay=True, loop=True, name=None, overlay=True, control=True, show=True, **kwargs ): super().__init__(name=name, overlay=overlay, control=control, show=show) self._name = "VideoOverlay" self.video_url = video_url self.bounds = bounds self.options = parse_options(autoplay=autoplay, loop=loop, **kwargs) def _get_self_bounds(self): """ Computes the bounds of the object itself (not including it's children) in the form [[lat_min, lon_min], [lat_max, lon_max]] """ return self.bounds