Source code for ipyleaflet.leaflet

# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
#

import copy
import asyncio
import json
import xyzservices
from datetime import date, timedelta
from math import isnan
from branca.colormap import linear, ColorMap
from IPython.display import display
import warnings

from ipywidgets import (
    Widget, DOMWidget, Box, Color, CallbackDispatcher, widget_serialization,
    interactive, Style, Output
)

from ipywidgets.widgets.trait_types import InstanceDict
from ipywidgets.embed import embed_minimal_html

from traitlets import (
    CFloat, Float, Unicode, Int, Tuple, List, Instance, Bool, Dict, Enum,
    link, observe, default, validate, TraitError, Union, Any
)
from ._version import EXTENSION_VERSION

from .projections import projections


def_loc = [0.0, 0.0]
allowed_cursor = ['alias', 'cell', 'grab', 'move', 'crosshair', 'context-menu',
                  'n-resize', 'ne-resize', 'e-resize', 'se-resize', 's-resize',
                  'sw-resize', 'w-resize', 'nw-resize', 'nesw-resize',
                  'nwse-resize', 'row-resize', 'col-resize', 'copy', 'default',
                  'grabbing', 'help', 'no-drop', 'not-allowed', 'pointer',
                  'progress', 'text', 'wait', 'zoom-in', 'zoom-out']

yesterday = (date.today() - timedelta(days=1)).strftime("%Y-%m-%d")


[docs] def basemap_to_tiles(basemap, day=yesterday, **kwargs): """Turn a basemap into a TileLayer object. Parameters ---------- basemap : class:`xyzservices.lib.TileProvider` or Dict Basemap description coming from ipyleaflet.basemaps. day: string If relevant for the chosen basemap, you can specify the day for the tiles in the "%Y-%m-%d" format. Defaults to yesterday's date. kwargs: key-word arguments Extra key-word arguments to pass to the TileLayer constructor. """ if isinstance(basemap, xyzservices.lib.TileProvider): url = basemap.build_url(time=day) elif isinstance(basemap, dict): url = basemap.get("url", "") else: raise ValueError("Invalid basemap type") return TileLayer( url=url, max_zoom=basemap.get('max_zoom', 18), min_zoom=basemap.get('min_zoom', 1), attribution=basemap.get('html_attribution', '') or basemap.get('attribution', ''), name=basemap.get('name', ''), **kwargs )
def wait_for_change(widget, value): future = asyncio.Future() def get_value(change): future.set_result(change.new) widget.unobserve(get_value, value) widget.observe(get_value, value) return future
[docs] class PaneException(TraitError): """Custom PaneException class.""" pass
[docs] class LayerException(TraitError): """Custom LayerException class.""" pass
[docs] class InteractMixin(object): """Abstract InteractMixin class.""" def interact(self, **kwargs): c = [] for name, abbrev in kwargs.items(): default = getattr(self, name) widget = interactive.widget_from_abbrev(abbrev, default) if not widget.description: widget.description = name widget.link = link((widget, 'value'), (self, name)) c.append(widget) cont = Box(children=c) return cont
[docs] class Layer(Widget, InteractMixin): """Abstract Layer class. Base class for all layers in ipyleaflet. Attributes ---------- name : string Custom name for the layer, which will be used by the LayersControl. popup: object Interactive widget that will be shown in a Popup when clicking on the layer. pane: string Name of the pane to use for the layer. """ _view_name = Unicode('LeafletLayerView').tag(sync=True) _model_name = Unicode('LeafletLayerModel').tag(sync=True) _view_module = Unicode('jupyter-leaflet').tag(sync=True) _model_module = Unicode('jupyter-leaflet').tag(sync=True) _view_module_version = Unicode(EXTENSION_VERSION).tag(sync=True) _model_module_version = Unicode(EXTENSION_VERSION).tag(sync=True) name = Unicode('').tag(sync=True) base = Bool(False).tag(sync=True) bottom = Bool(False).tag(sync=True) popup = Instance(Widget, allow_none=True, default_value=None).tag(sync=True, **widget_serialization) popup_min_width = Int(50).tag(sync=True) popup_max_width = Int(300).tag(sync=True) popup_max_height = Int(default_value=None, allow_none=True).tag(sync=True) pane = Unicode('').tag(sync=True) options = List(trait=Unicode()).tag(sync=True) subitems = Tuple().tag(trait=Instance(Widget), sync=True, **widget_serialization) @validate('subitems') def _validate_subitems(self, proposal): '''Validate subitems list. Makes sure only one instance of any given subitem can exist in the subitem list. ''' subitem_ids = [subitem.model_id for subitem in proposal.value] if len(set(subitem_ids)) != len(subitem_ids): raise Exception('duplicate subitem detected, only use each subitem once') return proposal.value def __init__(self, **kwargs): super(Layer, self).__init__(**kwargs) self.on_msg(self._handle_mouse_events) @default('options') def _default_options(self): return [name for name in self.traits(o=True)] # Event handling _click_callbacks = Instance(CallbackDispatcher, ()) _dblclick_callbacks = Instance(CallbackDispatcher, ()) _mousedown_callbacks = Instance(CallbackDispatcher, ()) _mouseup_callbacks = Instance(CallbackDispatcher, ()) _mouseover_callbacks = Instance(CallbackDispatcher, ()) _mouseout_callbacks = Instance(CallbackDispatcher, ()) def _handle_mouse_events(self, _, content, buffers): event_type = content.get('type', '') if event_type == 'click': self._click_callbacks(**content) if event_type == 'dblclick': self._dblclick_callbacks(**content) if event_type == 'mousedown': self._mousedown_callbacks(**content) if event_type == 'mouseup': self._mouseup_callbacks(**content) if event_type == 'mouseover': self._mouseover_callbacks(**content) if event_type == 'mouseout': self._mouseout_callbacks(**content)
[docs] def on_click(self, callback, remove=False): """Add a click event listener. Parameters ---------- callback : callable Callback function that will be called on click event. remove: boolean Whether to remove this callback or not. Defaults to False. """ self._click_callbacks.register_callback(callback, remove=remove)
[docs] def on_dblclick(self, callback, remove=False): """Add a double-click event listener. Parameters ---------- callback : callable Callback function that will be called on double-click event. remove: boolean Whether to remove this callback or not. Defaults to False. """ self._dblclick_callbacks.register_callback(callback, remove=remove)
[docs] def on_mousedown(self, callback, remove=False): """Add a mouse-down event listener. Parameters ---------- callback : callable Callback function that will be called on mouse-down event. remove: boolean Whether to remove this callback or not. Defaults to False. """ self._mousedown_callbacks.register_callback(callback, remove=remove)
[docs] def on_mouseup(self, callback, remove=False): """Add a mouse-up event listener. Parameters ---------- callback : callable Callback function that will be called on mouse-up event. remove: boolean Whether to remove this callback or not. Defaults to False. """ self._mouseup_callbacks.register_callback(callback, remove=remove)
[docs] def on_mouseover(self, callback, remove=False): """Add a mouse-over event listener. Parameters ---------- callback : callable Callback function that will be called on mouse-over event. remove: boolean Whether to remove this callback or not. Defaults to False. """ self._mouseover_callbacks.register_callback(callback, remove=remove)
[docs] def on_mouseout(self, callback, remove=False): """Add a mouse-out event listener. Parameters ---------- callback : callable Callback function that will be called on mouse-out event. remove: boolean Whether to remove this callback or not. Defaults to False. """ self._mouseout_callbacks.register_callback(callback, remove=remove)
[docs] class UILayer(Layer): """Abstract UILayer class.""" _view_name = Unicode('LeafletUILayerView').tag(sync=True) _model_name = Unicode('LeafletUILayerModel').tag(sync=True)
[docs] class Icon(UILayer): """Icon class. Custom icon used for markers. Attributes ---------- icon_url : string, default "" The url to the image used for the icon. shadow_url: string, default None The url to the image used for the icon shadow. icon_size: tuple, default None The size of the icon, in pixels. shadow_size: tuple, default None The size of the icon shadow, in pixels. icon_anchor: tuple, default None The coordinates of the "tip" of the icon (relative to its top left corner). The icon will be aligned so that this point is at the marker's geographical location. Centered by default if icon_size is specified. shadow_anchor: tuple, default None The coordinates of the "tip" of the shadow (relative to its top left corner). The same as icon_anchor if None. popup_anchor: tuple, default None The coordinates of the point from which popups will "open", relative to the icon anchor. """ _view_name = Unicode('LeafletIconView').tag(sync=True) _model_name = Unicode('LeafletIconModel').tag(sync=True) icon_url = Unicode('').tag(sync=True, o=True) shadow_url = Unicode(None, allow_none=True).tag(sync=True, o=True) icon_size = List(default_value=None, allow_none=True).tag(sync=True, o=True) shadow_size = List(default_value=None, allow_none=True).tag(sync=True, o=True) icon_anchor = List(default_value=None, allow_none=True).tag(sync=True, o=True) shadow_anchor = List(default_value=None, allow_none=True).tag(sync=True, o=True) popup_anchor = List([0, 0], allow_none=True).tag(sync=True, o=True) @validate('icon_size', 'shadow_size', 'icon_anchor', 'shadow_anchor', 'popup_anchor') def _validate_attr(self, proposal): value = proposal['value'] # Workaround Traitlets which does not respect the None default value if value is None or len(value) == 0: return None if len(value) != 2: raise TraitError('The value should be of size 2, but {} was given'.format(value)) return value
[docs] class DivIcon(UILayer): """DivIcon class. Custom lightweight icon for markers that uses a simple <div> element instead of an image used for markers. Attributes ---------- html : string Custom HTML code to put inside the div element, empty by default. bg_pos : tuple, default [0, 0] Optional relative position of the background, in pixels. icon_size: tuple, default None The size of the icon, in pixels. icon_anchor: tuple, default None The coordinates of the "tip" of the icon (relative to its top left corner). The icon will be aligned so that this point is at the marker's geographical location. Centered by default if icon_size is specified. popup_anchor: tuple, default None The coordinates of the point from which popups will "open", relative to the icon anchor. """ _view_name = Unicode('LeafletDivIconView').tag(sync=True) _model_name = Unicode('LeafletDivIconModel').tag(sync=True) html = Unicode('').tag(sync=True, o=True) bg_pos = List([0, 0], allow_none=True).tag(sync=True, o=True) icon_size = List(default_value=None, allow_none=True).tag(sync=True, o=True) icon_anchor = List(default_value=None, allow_none=True).tag(sync=True, o=True) popup_anchor = List([0, 0], allow_none=True).tag(sync=True, o=True) @validate('icon_size', 'icon_anchor', 'popup_anchor') def _validate_attr(self, proposal): value = proposal['value'] # Workaround Traitlets which does not respect the None default value if value is None or len(value) == 0: return None if len(value) != 2: raise TraitError('The value should be of size 2, but {} was given'.format(value)) return value
[docs] class AwesomeIcon(UILayer): """AwesomeIcon class. FontAwesome icon used for markers. Attributes ---------- name : string, default "home" The name of the FontAwesome icon to use. See https://fontawesome.com/v4.7.0/icons for available icons. marker_color: string, default "blue" Color used for the icon background. icon_color: string, default "white" CSS color used for the FontAwesome icon. spin: boolean, default False Whether the icon is spinning or not. """ _view_name = Unicode('LeafletAwesomeIconView').tag(sync=True) _model_name = Unicode('LeafletAwesomeIconModel').tag(sync=True) name = Unicode('home').tag(sync=True) marker_color = Enum( values=['white', 'red', 'darkred', 'lightred', 'orange', 'beige', 'green', 'darkgreen', 'lightgreen', 'blue', 'darkblue', 'lightblue', 'purple', 'darkpurple', 'pink', 'cadetblue', 'white', 'gray', 'lightgray', 'black'], default_value='blue' ).tag(sync=True) icon_color = Color('white').tag(sync=True) spin = Bool(False).tag(sync=True)
[docs] class Marker(UILayer): """Marker class. Clickable/Draggable marker on the map. Attributes ---------- location: tuple, default (0, 0) The tuple containing the latitude/longitude of the marker. opacity: float, default 1. Opacity of the marker between 0. (fully transparent) and 1. (fully opaque). visible: boolean, default True Whether the marker is visible or not. icon: object, default None Icon used for the marker, it can be an Icon or an AwesomeIcon instance. By default it will use a blue icon. draggable: boolean, default True Whether the marker is draggable with the mouse or not. keyboard: boolean, default True Whether the marker can be tabbed to with a keyboard and clicked by pressing enter. title: string, default '' Text for the browser tooltip that appear on marker hover (no tooltip by default). alt: string, default '' Text for the alt attribute of the icon image (useful for accessibility). rotation_angle: float, default 0. The rotation angle of the marker in degrees. rotation_origin: string, default '' The rotation origin of the marker. z_index_offset: int, default 0 opacity: float, default 1.0 rise_offset: int, default 250 The z-index offset used for the rise_on_hover feature """ _view_name = Unicode('LeafletMarkerView').tag(sync=True) _model_name = Unicode('LeafletMarkerModel').tag(sync=True) location = List(def_loc).tag(sync=True) opacity = Float(1.0, min=0.0, max=1.0).tag(sync=True) visible = Bool(True).tag(sync=True) icon = Union((Instance(Icon), Instance(AwesomeIcon), Instance(DivIcon)), allow_none=True, default_value=None).tag(sync=True, **widget_serialization) # Options z_index_offset = Int(0).tag(sync=True, o=True) draggable = Bool(True).tag(sync=True, o=True) keyboard = Bool(True).tag(sync=True, o=True) title = Unicode('').tag(sync=True, o=True) alt = Unicode('').tag(sync=True, o=True) rise_on_hover = Bool(False).tag(sync=True, o=True) rise_offset = Int(250).tag(sync=True, o=True) rotation_angle = Float(0).tag(sync=True, o=True) rotation_origin = Unicode('').tag(sync=True, o=True) _move_callbacks = Instance(CallbackDispatcher, ()) def __init__(self, **kwargs): super(Marker, self).__init__(**kwargs) self.on_msg(self._handle_leaflet_event) def _handle_leaflet_event(self, _, content, buffers): if content.get('event', '') == 'move': self._move_callbacks(**content)
[docs] def on_move(self, callback, remove=False): """Add a move event listener. Parameters ---------- callback : callable Callback function that will be called on move event. remove: boolean Whether to remove this callback or not. Defaults to False. """ self._move_callbacks.register_callback(callback, remove=remove)
[docs] class RasterLayer(Layer): """Abstract RasterLayer class. Attributes ---------- opacity: float, default 1. Opacity of the layer between 0. (fully transparent) and 1. (fully opaque). visible: boolean, default True Whether the layer is visible or not. """ _view_name = Unicode('LeafletRasterLayerView').tag(sync=True) _model_name = Unicode('LeafletRasterLayerModel').tag(sync=True) opacity = Float(1.0, min=0.0, max=1.0).tag(sync=True) visible = Bool(True).tag(sync=True)
[docs] class TileLayer(RasterLayer): """TileLayer class. Tile service layer. Attributes ---------- url: string, default "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" Url to the tiles service. min_zoom: int, default 0 The minimum zoom level down to which this layer will be displayed (inclusive). max_zoom: int, default 18 The maximum zoom level up to which this layer will be displayed (inclusive). min_native_zoom: int, default None Minimum zoom number the tile source has available. If it is specified, the tiles on all zoom levels lower than min_native_zoom will be loaded from min_native_zoom level and auto-scaled. max_native_zoom: int, default None Maximum zoom number the tile source has available. If it is specified, the tiles on all zoom levels higher than max_native_zoom will be loaded from max_native_zoom level and auto-scaled. bounds: list or None, default None List of SW and NE location tuples. e.g. [(50, 75), (75, 120)]. tile_size: int, default 256 Tile sizes for this tile service. attribution: string, default None. Tiles service attribution. no_wrap: boolean, default False Whether the layer is wrapped around the antimeridian. tms: boolean, default False If true, inverses Y axis numbering for tiles (turn this on for TMS services). zoom_offset: int, default 0 The zoom number used in tile URLs will be offset with this value. show_loading: boolean, default False Whether to show a spinner when tiles are loading. loading: boolean, default False (dynamically updated) Whether the tiles are currently loading. detect_retina: boolean, default False opacity: float, default 1.0 visible: boolean, default True """ _view_name = Unicode('LeafletTileLayerView').tag(sync=True) _model_name = Unicode('LeafletTileLayerModel').tag(sync=True) bottom = Bool(True).tag(sync=True) url = Unicode('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').tag(sync=True) min_zoom = Int(0).tag(sync=True, o=True) max_zoom = Int(18).tag(sync=True, o=True) min_native_zoom = Int(default_value=None, allow_none=True).tag(sync=True, o=True) max_native_zoom = Int(default_value=None, allow_none=True).tag(sync=True, o=True) bounds = List(default_value=None, allow_none=True, help='list of SW and NE location tuples').tag(sync=True, o=True) tile_size = Int(256).tag(sync=True, o=True) attribution = Unicode(default_value=None, allow_none=True).tag(sync=True, o=True) detect_retina = Bool(False).tag(sync=True, o=True) no_wrap = Bool(False).tag(sync=True, o=True) tms = Bool(False).tag(sync=True, o=True) zoom_offset = Int(0).tag(sync=True, o=True) show_loading = Bool(False).tag(sync=True) loading = Bool(False, read_only=True).tag(sync=True) _load_callbacks = Instance(CallbackDispatcher, ()) def __init__(self, **kwargs): super(TileLayer, self).__init__(**kwargs) self.on_msg(self._handle_leaflet_event) def _handle_leaflet_event(self, _, content, buffers): if content.get('event', '') == 'load': self._load_callbacks(**content)
[docs] def on_load(self, callback, remove=False): """Add a load event listener. Parameters ---------- callback : callable Callback function that will be called when the tiles have finished loading. remove: boolean Whether to remove this callback or not. Defaults to False. """ self._load_callbacks.register_callback(callback, remove=remove)
[docs] def redraw(self): """Force redrawing the tiles. This is especially useful when you are sure the server updated the tiles and you need to refresh the layer. """ self.send({'msg': 'redraw'})
[docs] class LocalTileLayer(TileLayer): """LocalTileLayer class. Custom tile layer using local tile files. Attributes ---------- path: string, default "" Path to your local tiles. In the classic Jupyter Notebook, the path is relative to the Notebook you are working on. In JupyterLab, the path is relative to the server (where you started JupyterLab) and you need to prefix the path with “files/”. """ _view_name = Unicode('LeafletLocalTileLayerView').tag(sync=True) _model_name = Unicode('LeafletLocalTileLayerModel').tag(sync=True) path = Unicode('').tag(sync=True)
[docs] class WMSLayer(TileLayer): """WMSLayer class, with TileLayer as a parent class. Attributes ---------- layers: string, default "" Comma-separated list of WMS layers to show. styles: string, default "" Comma-separated list of WMS styles format: string, default "image/jpeg" WMS image format (use `'image/png'` for layers with transparency). transparent: boolean, default False If true, the WMS service will return images with transparency. crs: dict, default ipyleaflet.projections.EPSG3857 Projection used for this WMS service. """ _view_name = Unicode('LeafletWMSLayerView').tag(sync=True) _model_name = Unicode('LeafletWMSLayerModel').tag(sync=True) # Options layers = Unicode().tag(sync=True, o=True) styles = Unicode().tag(sync=True, o=True) format = Unicode('image/jpeg').tag(sync=True, o=True) transparent = Bool(False).tag(sync=True, o=True) crs = Dict(default_value=projections.EPSG3857).tag(sync=True) uppercase = Bool(False).tag(sync=True, o=True)
[docs] class MagnifyingGlass(RasterLayer): """MagnifyingGlass class. Attributes ---------- radius: int, default 100 The radius of the magnifying glass, in pixels. zoom_offset: int, default 3 The zoom level offset between the main map zoom and the magnifying glass. fixed_zoom: int, default -1 If different than -1, defines a fixed zoom level to always use in the magnifying glass, ignoring the main map zoom and the zoomOffet value. fixed_position: boolean, default False If True, the magnifying glass will stay at the same position on the map, not following the mouse cursor. lat_lng: list, default [0, 0] The initial position of the magnifying glass, both on the main map and as the center of the magnified view. If fixed_position is True, it will always keep this position. layers: list, default [] Set of layers to display in the magnified view. These layers shouldn't be already added to a map instance. """ _view_name = Unicode('LeafletMagnifyingGlassView').tag(sync=True) _model_name = Unicode('LeafletMagnifyingGlassModel').tag(sync=True) # Options radius = Int(100).tag(sync=True, o=True) zoom_offset = Int(3).tag(sync=True, o=True) fixed_zoom = Int(-1).tag(sync=True, o=True) fixed_position = Bool(False).tag(sync=True, o=True) lat_lng = List(def_loc).tag(sync=True, o=True) layers = Tuple().tag(trait=Instance(Layer), sync=True, o=True, **widget_serialization) _layer_ids = List() @validate('layers') def _validate_layers(self, proposal): '''Validate layers list. Makes sure only one instance of any given layer can exist in the layers list. ''' self._layer_ids = [layer.model_id for layer in proposal.value] if len(set(self._layer_ids)) != len(self._layer_ids): raise LayerException('duplicate layer detected, only use each layer once') return proposal.value
[docs] class ImageOverlay(RasterLayer): """ImageOverlay class. Image layer from a local or remote image file. Attributes ---------- url: string, default "" Url to the local or remote image file. bounds: list, default [0., 0] SW and NE corners of the image. attribution: string, default "" Image attribution. """ _view_name = Unicode('LeafletImageOverlayView').tag(sync=True) _model_name = Unicode('LeafletImageOverlayModel').tag(sync=True) url = Unicode().tag(sync=True) bounds = List([def_loc, def_loc], help='SW and NE corners of the image').tag(sync=True) # Options attribution = Unicode().tag(sync=True, o=True)
[docs] class VideoOverlay(RasterLayer): """VideoOverlay class. Video layer from a local or remote video file. Attributes ---------- url: string, default "" Url to the local or remote video file. bounds: list, default [0., 0] SW and NE corners of the video. attribution: string, default "" Video attribution. """ _view_name = Unicode('LeafletVideoOverlayView').tag(sync=True) _model_name = Unicode('LeafletVideoOverlayModel').tag(sync=True) url = Unicode().tag(sync=True) bounds = List([def_loc, def_loc], help='SW and NE corners of the image').tag(sync=True) # Options attribution = Unicode().tag(sync=True, o=True)
[docs] class ImageService(Layer): """ImageService class Image Service layer for raster data served through a web service Attributes ---------- url: string, default "" URL to the image service f: string, default "image" response format (use ``"image"`` to stream as bytes) format: string, default "jpgpng" format of exported image - ``"jpgpng"`` - ``"png"`` - ``"png8"`` - ``"png24"`` - ``"jpg"`` - ``"bmp"`` - ``"gif"`` - ``"tiff"`` - ``"png32"`` - ``"bip"`` - ``"bsq"`` - ``"lerc"`` pixel_type: string, default "UNKNOWN" data type of the raster image - ``"C128"`` - ``"C64"`` - ``"F32"`` - ``"F64"`` - ``"S16"`` - ``"S32"`` - ``"S8"`` - ``"U1"`` - ``"U16"`` - ``"U2"`` - ``"U32"`` - ``"U4"`` - ``"U8"`` - ``"UNKNOWN"`` no_data: List[int], default [] pixel values representing no data no_data_interpretation: string, default "" how to interpret no data values - ``"esriNoDataMatchAny"`` - ``"esriNoDataMatchAll"`` interpolation: string, default "" resampling process for interpolating the pixel values - ``"RSP_BilinearInterpolation"`` - ``"RSP_CubicConvolution"`` - ``"RSP_Majority"`` - ``"RSP_NearestNeighbor"`` compression_quality: int, default 100 lossy quality for image compression band_ids: List[int], default [] order of bands to export for multiple band images time: List[string], default [] time range for image rendering_rule: dict, default {} rules for rendering mosaic_rule: dict, default {} rules for mosaicking endpoint: string, default "Esri" endpoint format for building the export image URL - ``"Esri"`` attribution: string, default "" include image service attribution crs: dict, default ipyleaflet.projections.EPSG3857 projection used for this image service. interactive: bool, default False emit when clicked for registered callback update_interval: int, default 200 minimum time interval to query for updates when panning (ms) """ _view_name = Unicode('LeafletImageServiceView').tag(sync=True) _model_name = Unicode('LeafletImageServiceModel').tag(sync=True) _formats = ['jpgpng', 'png', 'png8', 'png24', 'jpg', 'bmp', 'gif', 'tiff', 'png32', 'bip', 'bsq', 'lerc'] _pixel_types = ['C128', 'C64', 'F32', 'F64', 'S16', 'S32', 'S8', 'U1', 'U16', 'U2', 'U32', 'U4', 'U8', 'UNKNOWN'] _no_data_interpretations = ['esriNoDataMatchAny', 'esriNoDataMatchAll'] _interpolations = ['RSP_BilinearInterpolation', 'RSP_CubicConvolution', 'RSP_Majority', 'RSP_NearestNeighbor'] url = Unicode().tag(sync=True) f = Unicode('image').tag(sync=True, o=True) format = Enum(values=_formats, default_value='jpgpng').tag(sync=True, o=True) pixel_type = Enum(values=_pixel_types, default_value='UNKNOWN').tag(sync=True, o=True) no_data = List(allow_none=True).tag(sync=True, o=True) no_data_interpretation = Enum(values=_no_data_interpretations, allow_none=True).tag(sync=True, o=True) interpolation = Enum(values=_interpolations, allow_none=True).tag(sync=True, o=True) compression_quality = Unicode().tag(sync=True, o=True) band_ids = List(allow_none=True).tag(sync=True, o=True) time = List(allow_none=True).tag(sync=True, o=True) rendering_rule = Dict({}).tag(sync=True, o=True) mosaic_rule = Dict({}).tag(sync=True, o=True) endpoint = Unicode('Esri').tag(sync=True, o=True) attribution = Unicode('').tag(sync=True, o=True) crs = Dict(default_value=projections.EPSG3857).tag(sync=True) interactive = Bool(False).tag(sync=True, o=True) update_interval = Int(200).tag(sync=True, o=True) _click_callbacks = Instance(CallbackDispatcher, ()) def __init__(self, **kwargs): super(ImageService, self).__init__(**kwargs) self.on_msg(self._handle_mouse_events) def _handle_mouse_events(self, _, content, buffers): event_type = content.get('type', '') if event_type == 'click': self._click_callbacks(**content)
[docs] class Heatmap(RasterLayer): """Heatmap class, with RasterLayer as parent class. Heatmap layer. Attributes ---------- locations: list, default [] List of data points locations for generating the heatmap. radius: float, default 25. Radius of the data points. blur: float, default 15. Blurring intensity. gradient: dict, default {0.4: 'blue', 0.6: 'cyan', 0.7: 'lime', 0.8: 'yellow', 1.0: 'red'} Colors used for the color-mapping from low to high heatmap intensity. """ _view_name = Unicode('LeafletHeatmapView').tag(sync=True) _model_name = Unicode('LeafletHeatmapModel').tag(sync=True) locations = List().tag(sync=True) # Options min_opacity = Float(0.05).tag(sync=True, o=True) max_zoom = Int(18).tag(sync=True, o=True) max = Float(1.0).tag(sync=True, o=True) radius = Float(25.0).tag(sync=True, o=True) blur = Float(15.0).tag(sync=True, o=True) gradient = Dict({0.4: 'blue', 0.6: 'cyan', 0.7: 'lime', 0.8: 'yellow', 1.0: 'red'}).tag(sync=True, o=True)
[docs] class VectorTileLayer(Layer): """VectorTileLayer class, with Layer as parent class. Vector tile layer. Attributes ---------- url: string, default "" Url to the vector tile service. attribution: string, default "" Vector tile service attribution. vector_tile_layer_styles: dict, default {} CSS Styles to apply to the vector data. """ _view_name = Unicode('LeafletVectorTileLayerView').tag(sync=True) _model_name = Unicode('LeafletVectorTileLayerModel').tag(sync=True) url = Unicode().tag(sync=True, o=True) attribution = Unicode().tag(sync=True, o=True) vector_tile_layer_styles = Dict().tag(sync=True, o=True)
[docs] def redraw(self): """Force redrawing the tiles. This is especially useful when you are sure the server updated the tiles and you need to refresh the layer. """ self.send({'msg': 'redraw'})
[docs] class PMTilesLayer(Layer): """PMTilesLayer class, with Layer as parent class. PMTiles layer. Attributes ---------- url: string, default "" Url to the PMTiles archive. attribution: string, default "" PMTiles archive attribution. style: dict, default {} CSS Styles to apply to the vector data. """ _view_name = Unicode('LeafletPMTilesLayerView').tag(sync=True) _model_name = Unicode('LeafletPMTilesLayerModel').tag(sync=True) url = Unicode().tag(sync=True, o=True) attribution = Unicode().tag(sync=True, o=True) style = Dict().tag(sync=True, o=True)
[docs] def add_inspector(self): """Add an inspector to the layer. """ self.send({'msg': 'add_inspector'})
[docs] class VectorLayer(Layer): """VectorLayer abstract class.""" _view_name = Unicode('LeafletVectorLayerView').tag(sync=True) _model_name = Unicode('LeafletVectorLayerModel').tag(sync=True)
[docs] class Path(VectorLayer): """Path abstract class. Path layer. Attributes ---------- stroke: boolean, default True Whether to draw a stroke. color: CSS color, default '#0033FF' CSS color. weight: int, default 5 Weight of the stroke. fill: boolean, default True Whether to fill the path with a flat color. fill_color: CSS color, default None Color used for filling the path shape. If None, the color attribute value is used. fill_opacity: float, default 0.2 Opacity used for filling the path shape. line_cap: string, default "round" A string that defines shape to be used at the end of the stroke. Possible values are 'round', 'butt' or 'square'. line_join: string, default "round" A string that defines shape to be used at the corners of the stroke. Possible values are 'arcs', 'bevel', 'miter', 'miter-clip' or 'round'. """ _view_name = Unicode('LeafletPathView').tag(sync=True) _model_name = Unicode('LeafletPathModel').tag(sync=True) # Options stroke = Bool(True).tag(sync=True, o=True) color = Color('#0033FF').tag(sync=True, o=True) weight = Int(5).tag(sync=True, o=True) fill = Bool(True).tag(sync=True, o=True) fill_color = Color(None, allow_none=True).tag(sync=True, o=True) fill_opacity = Float(0.2).tag(sync=True, o=True) dash_array = Unicode(allow_none=True, default_value=None).tag(sync=True, o=True) line_cap = Enum(values=['round', 'butt', 'square'], default_value='round').tag(sync=True, o=True) line_join = Enum(values=['arcs', 'bevel', 'miter', 'miter-clip', 'round'], default_value='round').tag(sync=True, o=True) pointer_events = Unicode('').tag(sync=True, o=True) opacity = Float(1.0, min=0.0, max=1.0).tag(sync=True, o=True)
[docs] class AntPath(VectorLayer): """AntPath class, with VectorLayer as parent class. AntPath layer. Attributes ---------- locations: list, default [] Locations through which the ant-path is going. use: string, default 'polyline' Type of shape to use for the ant-path. Possible values are 'polyline', 'polygon', 'rectangle' and 'circle'. delay: int, default 400 Add a delay to the animation flux. weight: int, default 5 Weight of the ant-path. dash_array: list, default [10, 20] The sizes of the animated dashes. color: CSS color, default '#0000FF' The color of the primary dashes. pulse_color: CSS color, default '#FFFFFF' The color of the secondary dashes. paused: boolean, default False Whether the animation is running or not. reverse: boolean, default False Whether the animation is going backwards or not. hardware_accelerated: boolean, default False Whether the ant-path uses hardware acceleration. radius: int, default 10 Radius of the circle, if use is set to ‘circle’ """ _view_name = Unicode('LeafletAntPathView').tag(sync=True) _model_name = Unicode('LeafletAntPathModel').tag(sync=True) locations = List().tag(sync=True) # Options use = Enum(values=['polyline', 'polygon', 'rectangle', 'circle'], default_value='polyline').tag(sync=True, o=True) delay = Int(400).tag(sync=True, o=True) weight = Int(5).tag(sync=True, o=True) dash_array = List([10, 20]).tag(sync=True, o=True) color = Color('#0000FF').tag(sync=True, o=True) pulse_color = Color('#FFFFFF').tag(sync=True, o=True) paused = Bool(False).tag(sync=True, o=True) reverse = Bool(False).tag(sync=True, o=True) hardware_accelerated = Bool(False).tag(sync=True, o=True) radius = Int(10).tag(sync=True, o=True)
[docs] class Polyline(Path): """Polyline abstract class, with Path as parent class. Attributes ---------- locations: list, default [] Locations defining the shape. scaling: boolean, default True Whether you can edit the scale of the shape or not. rotation: boolean, default True Whether you can rotate the shape or not. uniform_scaling: boolean, default False Whether to keep the size ratio when changing the shape scale. smooth_factor: float, default 1. Smoothing intensity. transform: boolean, default False Whether the shape is editable or not. draggable: boolean, default False Whether you can drag the shape on the map or not. """ _view_name = Unicode('LeafletPolylineView').tag(sync=True) _model_name = Unicode('LeafletPolylineModel').tag(sync=True) locations = List().tag(sync=True) scaling = Bool(True).tag(sync=True) rotation = Bool(True).tag(sync=True) uniform_scaling = Bool(False).tag(sync=True) # Options smooth_factor = Float(1.0).tag(sync=True, o=True) no_clip = Bool(True).tag(sync=True, o=True) transform = Bool(False).tag(sync=True, o=True) draggable = Bool(False).tag(sync=True, o=True)
[docs] class Polygon(Polyline): """Polygon class, with Polyline as parent class. Polygon layer. """ _view_name = Unicode('LeafletPolygonView').tag(sync=True) _model_name = Unicode('LeafletPolygonModel').tag(sync=True)
[docs] class Rectangle(Polygon): """Rectangle class, with Polygon as parent class. Rectangle layer. Attributes ---------- bounds: list, default [] List of SW and NE location tuples. e.g. [(50, 75), (75, 120)]. """ _view_name = Unicode('LeafletRectangleView').tag(sync=True) _model_name = Unicode('LeafletRectangleModel').tag(sync=True) bounds = List(help='list of SW and NE location tuples').tag(sync=True)
[docs] class CircleMarker(Path): """CircleMarker class, with Path as parent class. CircleMarker layer. Attributes ---------- location: list, default [0, 0] Location of the marker (lat, long). radius: int, default 10 Radius of the circle marker in pixels. """ _view_name = Unicode('LeafletCircleMarkerView').tag(sync=True) _model_name = Unicode('LeafletCircleMarkerModel').tag(sync=True) location = List(def_loc).tag(sync=True) # Options radius = Int(10, help='radius of circle in pixels').tag(sync=True, o=True)
[docs] class Circle(CircleMarker): """Circle class, with CircleMarker as parent class. Circle layer. """ _view_name = Unicode('LeafletCircleView').tag(sync=True) _model_name = Unicode('LeafletCircleModel').tag(sync=True) # Options radius = Int(1000, help='radius of circle in meters').tag(sync=True, o=True)
[docs] class MarkerCluster(Layer): """MarkerCluster class, with Layer as parent class. A cluster of markers that you can put on the map like other layers. Attributes ---------- markers: list, default [] List of markers to include in the cluster. """ _view_name = Unicode('LeafletMarkerClusterView').tag(sync=True) _model_name = Unicode('LeafletMarkerClusterModel').tag(sync=True) markers = Tuple().tag(trait=Instance(Layer), sync=True, **widget_serialization) # Options disable_clustering_at_zoom = Int(18).tag(sync=True, o=True) max_cluster_radius = Int(80).tag(sync=True, o=True)
[docs] class LayerGroup(Layer): """LayerGroup class. A group of layers that you can put on the map like other layers. Attributes ---------- layers: list, default [] List of layers to include in the group. """ _view_name = Unicode('LeafletLayerGroupView').tag(sync=True) _model_name = Unicode('LeafletLayerGroupModel').tag(sync=True) layers = Tuple().tag(trait=Instance(Layer), sync=True, **widget_serialization) _layer_ids = List() @validate('layers') def _validate_layers(self, proposal): '''Validate layers list. Makes sure only one instance of any given layer can exist in the layers list. ''' self._layer_ids = [layer.model_id for layer in proposal.value] if len(set(self._layer_ids)) != len(self._layer_ids): raise LayerException('duplicate layer detected, only use each layer once') return proposal.value
[docs] def add_layer(self, layer): """Add a new layer to the group. .. deprecated :: 0.17.0 Use add method instead. Parameters ---------- layer: layer instance The new layer to include in the group. """ warnings.warn("add_layer is deprecated, use add instead", DeprecationWarning) self.add(layer)
[docs] def remove_layer(self, rm_layer): """Remove a layer from the group. .. deprecated :: 0.17.0 Use remove method instead. Parameters ---------- layer: layer instance The layer to remove from the group. """ warnings.warn("remove_layer is deprecated, use remove instead", DeprecationWarning) self.remove(rm_layer)
[docs] def substitute_layer(self, old, new): """Substitute a layer with another one in the group. .. deprecated :: 0.17.0 Use substitute method instead. Parameters ---------- old: layer instance The layer to remove from the group. new: layer instance The new layer to include in the group. """ warnings.warn("substitute_layer is deprecated, use substitute instead", DeprecationWarning) self.substitute(old, new)
[docs] def clear_layers(self): """Remove all layers from the group. .. deprecated :: 0.17.0 Use clear method instead. """ warnings.warn("clear_layers is deprecated, use clear instead", DeprecationWarning) self.layers = ()
[docs] def add(self, layer): """Add a new layer to the group. Parameters ---------- layer: layer instance The new layer to include in the group. This can also be an object with an ``as_leaflet_layer`` method which generates a compatible layer type. """ if isinstance(layer, dict): layer = basemap_to_tiles(layer) if layer.model_id in self._layer_ids: raise LayerException('layer already in layergroup: %r' % layer) self.layers = tuple([layer for layer in self.layers] + [layer])
[docs] def remove(self, rm_layer): """Remove a layer from the group. Parameters ---------- layer: layer instance The layer to remove from the group. """ if rm_layer.model_id not in self._layer_ids: raise LayerException('layer not on in layergroup: %r' % rm_layer) self.layers = tuple([layer for layer in self.layers if layer.model_id != rm_layer.model_id])
[docs] def substitute(self, old, new): """Substitute a layer with another one in the group. Parameters ---------- old: layer instance The layer to remove from the group. new: layer instance The new layer to include in the group. """ if isinstance(new, dict): new = basemap_to_tiles(new) if old.model_id not in self._layer_ids: raise LayerException('Could not substitute layer: layer not in layergroup.') self.layers = tuple([new if layer.model_id == old.model_id else layer for layer in self.layers])
[docs] def clear(self): """Remove all layers from the group.""" self.layers = ()
[docs] class FeatureGroup(LayerGroup): """FeatureGroup abstract class.""" _view_name = Unicode('LeafletFeatureGroupView').tag(sync=True) _model_name = Unicode('LeafletFeatureGroupModel').tag(sync=True)
[docs] class GeoJSON(FeatureGroup): """GeoJSON class, with FeatureGroup as parent class. Layer created from a GeoJSON data structure. Attributes ---------- data: dict, default {} The JSON data structure. style: dict, default {} Extra style to apply to the features. hover_style: dict, default {} Style that will be applied to a feature when the mouse is over this feature. point_style: dict, default {} Extra style to apply to the point features. style_callback: callable, default None Function that will be called for each feature, should take the feature as input and return the feature style. """ _view_name = Unicode('LeafletGeoJSONView').tag(sync=True) _model_name = Unicode('LeafletGeoJSONModel').tag(sync=True) data = Dict().tag(sync=True) style = Dict().tag(sync=True) visible = Bool(True).tag(sync=True) hover_style = Dict().tag(sync=True) point_style = Dict().tag(sync=True) style_callback = Any() _click_callbacks = Instance(CallbackDispatcher, ()) _hover_callbacks = Instance(CallbackDispatcher, ()) def __init__(self, **kwargs): self.updating = True super(GeoJSON, self).__init__(**kwargs) self.data = self._get_data() self.updating = False @validate('style_callback') def _validate_style_callback(self, proposal): if not callable(proposal.value): raise TraitError('style_callback should be callable (functor/function/lambda)') return proposal.value @observe('data', 'style', 'style_callback') def _update_data(self, change): if self.updating: return self.updating = True self.data = self._get_data() self.updating = False def _get_data(self): if 'type' not in self.data: # We can't apply a style we don't know what the data look like return self.data datatype = self.data['type'] style_callback = None if self.style_callback: style_callback = self.style_callback elif self.style: style_callback = lambda feature: self.style else: # No style to apply return self.data # We need to make a deep copy for ipywidgets to see the change data = copy.deepcopy(self.data) if datatype == 'Feature': self._apply_style(data, style_callback) elif datatype == 'FeatureCollection': for feature in data['features']: self._apply_style(feature, style_callback) return data @property def __geo_interface__(self): """ Return a dict whose structure aligns to the GeoJSON format For more information about the ``__geo_interface__``, see https://gist.github.com/sgillies/2217756 """ return self.data def _apply_style(self, feature, style_callback): if 'properties' not in feature: feature['properties'] = {} properties = feature['properties'] if 'style' in properties: style = properties['style'].copy() style.update(style_callback(feature)) properties['style'] = style else: properties['style'] = style_callback(feature) def _handle_mouse_events(self, _, content, buffers): if content.get('event', '') == 'click': self._click_callbacks(**content) if content.get('event', '') == 'mouseover': self._hover_callbacks(**content)
[docs] def on_click(self, callback, remove=False): """Add a feature click event listener. Parameters ---------- callback : callable Callback function that will be called on click event on a feature, this function should take the event and the feature as inputs. remove: boolean Whether to remove this callback or not. Defaults to False. """ self._click_callbacks.register_callback(callback, remove=remove)
[docs] def on_hover(self, callback, remove=False): """Add a feature hover event listener. Parameters ---------- callback : callable Callback function that will be called when the mouse is over a feature, this function should take the event and the feature as inputs. remove: boolean Whether to remove this callback or not. Defaults to False. """ self._hover_callbacks.register_callback(callback, remove=remove)
[docs] class GeoData(GeoJSON): """GeoData class with GeoJSON as parent class. Layer created from a GeoPandas dataframe. Attributes ---------- geo_dataframe: geopandas.GeoDataFrame instance, default None The GeoPandas dataframe to use. """ geo_dataframe = Instance('geopandas.GeoDataFrame') def __init__(self, **kwargs): super(GeoData, self).__init__(**kwargs) self.data = self._get_data() @observe('geo_dataframe', 'style', 'style_callback') def _update_data(self, change): self.data = self._get_data() def _get_data(self): return json.loads(self.geo_dataframe.to_json()) @property def __geo_interface__(self): """ Return a dict whose structure aligns to the GeoJSON format For more information about the ``__geo_interface__``, see https://gist.github.com/sgillies/2217756 """ return self.geo_dataframe.__geo_interface__
[docs] class Choropleth(GeoJSON): """Choropleth class, with GeoJSON as parent class. Layer showing a Choropleth effect on a GeoJSON structure. Attributes ---------- geo_data: dict, default None The GeoJSON structure on which to apply the Choropleth effect. choro_data: dict, default None Data used for building the Choropleth effect. value_min: float, default None Minimum data value for the color mapping. value_max: float, default None Maximum data value for the color mapping. colormap: branca.colormap.ColorMap instance, default linear.OrRd_06 The colormap used for the effect. key_on: string, default "id" The feature key to use for the colormap effect. nan_color: string, default "black" The color used for filling polygons with NaN-values data. nan_opacity : float, default 0.4 The opacity used for NaN data polygons, between 0. (fully transparent) and 1. (fully opaque). default_opacity: float, default 1.0 The opacity used for well-defined data (non-NaN values), between 0. (fully transparent) and 1. (fully opaque). """ geo_data = Dict() choro_data = Dict() value_min = CFloat(None, allow_none=True) value_max = CFloat(None, allow_none=True) colormap = Instance(ColorMap, default_value=linear.OrRd_06) key_on = Unicode('id') nan_color = Unicode('black') nan_opacity = CFloat(0.4) default_opacity = CFloat(1.0) @observe('style', 'style_callback', 'value_min', 'value_max', 'nan_color', 'nan_opacity', 'default_opacity', 'geo_data', 'choro_data', 'colormap') def _update_data(self, change): self.data = self._get_data() @default('style_callback') def _default_style_callback(self): def compute_style(feature, colormap, choro_data): return dict( fillColor=self.nan_color if isnan(choro_data) else colormap(choro_data), fillOpacity=self.nan_opacity if isnan(choro_data) else self.default_opacity, color='black', weight=0.9 ) return compute_style def _get_data(self): if not self.geo_data: return {} choro_data_values_list = [x for x in self.choro_data.values() if not isnan(x)] if self.value_min is None: self.value_min = min(choro_data_values_list) if self.value_max is None: self.value_max = max(choro_data_values_list) colormap = self.colormap.scale(self.value_min, self.value_max) data = copy.deepcopy(self.geo_data) for feature in data['features']: feature['properties']['style'] = self.style_callback(feature, colormap, self.choro_data[feature[self.key_on]]) return data def __init__(self, **kwargs): super(Choropleth, self).__init__(**kwargs) self.data = self._get_data()
[docs] class WKTLayer(GeoJSON): """WKTLayer class. Layer created from a local WKT file or WKT string input. Attributes ---------- path: string, default "" file path of local WKT file. wkt_string: string, default "" WKT string. """ path = Unicode('') wkt_string = Unicode('') def __init__(self, **kwargs): super().__init__(**kwargs) self.data = self._get_data() @observe('path', 'wkt_string', 'style', 'style_callback') def _update_data(self, change): self.data = self._get_data() def _get_data(self): try: from shapely import geometry, wkt except ImportError: raise RuntimeError("The WKTLayer needs shapely to be installed, please run `pip install shapely`") if self.path: with open(self.path) as f: parsed_wkt = wkt.load(f) elif self.wkt_string: parsed_wkt = wkt.loads(self.wkt_string) else: raise ValueError("Please provide either WKT file path or WKT string") geo = geometry.mapping(parsed_wkt) if geo["type"] == "GeometryCollection": features = [{"geometry": g, "properties": {}, "type": "Feature"} for g in geo["geometries"]] feature_collection = {"type": "FeatureCollection", "features": features} return feature_collection else: feature = {"geometry": geo, "properties": {}, "type": "Feature"} return feature
[docs] class ControlException(TraitError): """Custom LayerException class.""" pass
[docs] class Control(Widget): """Control abstract class. This is the base class for all ipyleaflet controls. A control is additional UI components on top of the Map. Attributes ---------- position: str, default 'topleft' The position of the control. Possible values are 'topright', 'topleft', 'bottomright' and 'bottomleft'. """ _view_name = Unicode('LeafletControlView').tag(sync=True) _model_name = Unicode('LeafletControlModel').tag(sync=True) _view_module = Unicode('jupyter-leaflet').tag(sync=True) _model_module = Unicode('jupyter-leaflet').tag(sync=True) _view_module_version = Unicode(EXTENSION_VERSION).tag(sync=True) _model_module_version = Unicode(EXTENSION_VERSION).tag(sync=True) options = List(trait=Unicode()).tag(sync=True) position = Enum( ['topright', 'topleft', 'bottomright', 'bottomleft'], default_value='topleft', help="""Possible values are topleft, topright, bottomleft or bottomright""" ).tag(sync=True, o=True) @default('options') def _default_options(self): return [name for name in self.traits(o=True)]
[docs] class WidgetControl(Control): """WidgetControl class, with Control as parent class. A control that contains any DOMWidget instance. Attributes ---------- widget: DOMWidget The widget to put inside of the control. It can be any widget, even coming from a third-party library like bqplot. """ _view_name = Unicode('LeafletWidgetControlView').tag(sync=True) _model_name = Unicode('LeafletWidgetControlModel').tag(sync=True) widget = Instance(DOMWidget).tag(sync=True, **widget_serialization) max_width = Int(default_value=None, allow_none=True).tag(sync=True) min_width = Int(default_value=None, allow_none=True).tag(sync=True) max_height = Int(default_value=None, allow_none=True).tag(sync=True) min_height = Int(default_value=None, allow_none=True).tag(sync=True) transparent_bg = Bool(False).tag(sync=True, o=True)
[docs] class FullScreenControl(Control): """FullScreenControl class, with Control as parent class. A control which contains a button that will put the Map in full-screen when clicked. """ _view_name = Unicode('LeafletFullScreenControlView').tag(sync=True) _model_name = Unicode('LeafletFullScreenControlModel').tag(sync=True)
[docs] class LayersControl(Control): """LayersControl class, with Control as parent class. A control which allows hiding/showing different layers on the Map. Attributes ---------------------- collapsed: bool, default True Set whether control should be open or closed by default """ _view_name = Unicode('LeafletLayersControlView').tag(sync=True) _model_name = Unicode('LeafletLayersControlModel').tag(sync=True) collapsed = Bool(True).tag(sync=True, o=True)
[docs] class MeasureControl(Control): """MeasureControl class, with Control as parent class. A control which allows making measurements on the Map. Attributes ---------------------- primary_length_unit: str, default 'feet' Possible values are 'feet', 'meters', 'miles', 'kilometers' or any user defined unit. secondary_length_unit: str, default None Possible values are 'feet', 'meters', 'miles', 'kilometers' or any user defined unit. primary_area_unit: str, default 'acres' Possible values are 'acres', 'hectares', 'sqfeet', 'sqmeters', 'sqmiles' or any user defined unit. secondary_area_unit: str, default None Possible values are 'acres', 'hectares', 'sqfeet', 'sqmeters', 'sqmiles' or any user defined unit. active_color: CSS Color, default '#ABE67E' The color used for current measurements. completed_color: CSS Color, default '#C8F2BE' The color used for the completed measurements. """ _view_name = Unicode('LeafletMeasureControlView').tag(sync=True) _model_name = Unicode('LeafletMeasureControlModel').tag(sync=True) _length_units = ['feet', 'meters', 'miles', 'kilometers'] _area_units = ['acres', 'hectares', 'sqfeet', 'sqmeters', 'sqmiles'] _custom_units_dict = {} _custom_units = Dict().tag(sync=True) primary_length_unit = Enum( values=_length_units, default_value='feet', help="""Possible values are feet, meters, miles, kilometers or any user defined unit""" ).tag(sync=True, o=True) secondary_length_unit = Enum( values=_length_units, default_value=None, allow_none=True, help="""Possible values are feet, meters, miles, kilometers or any user defined unit""" ).tag(sync=True, o=True) primary_area_unit = Enum( values=_area_units, default_value='acres', help="""Possible values are acres, hectares, sqfeet, sqmeters, sqmiles or any user defined unit""" ).tag(sync=True, o=True) secondary_area_unit = Enum( values=_area_units, default_value=None, allow_none=True, help="""Possible values are acres, hectares, sqfeet, sqmeters, sqmiles or any user defined unit""" ).tag(sync=True, o=True) active_color = Color('#ABE67E').tag(sync=True, o=True) completed_color = Color('#C8F2BE').tag(sync=True, o=True) popup_options = Dict({ 'className': 'leaflet-measure-resultpopup', 'autoPanPadding': [10, 10] }).tag(sync=True, o=True) capture_z_index = Int(10000).tag(sync=True, o=True)
[docs] def add_length_unit(self, name, factor, decimals=0): """Add a custom length unit. Parameters ---------- name: str The name for your custom unit. factor: float Factor to apply when converting to this unit. Length in meters will be multiplied by this factor. decimals: int, default 0 Number of decimals to round results when using this unit. """ self._length_units.append(name) self._add_unit(name, factor, decimals)
[docs] def add_area_unit(self, name, factor, decimals=0): """Add a custom area unit. Parameters ---------- name: str The name for your custom unit. factor: float Factor to apply when converting to this unit. Area in sqmeters will be multiplied by this factor. decimals: int, default 0 Number of decimals to round results when using this unit. """ self._area_units.append(name) self._add_unit(name, factor, decimals)
def _add_unit(self, name, factor, decimals): self._custom_units_dict[name] = { 'factor': factor, 'display': name, 'decimals': decimals } self._custom_units = dict(**self._custom_units_dict)
[docs] class SplitMapControl(Control): """SplitMapControl class, with Control as parent class. A control which allows comparing layers by splitting the map in two. Attributes ---------- left_layer: Layer or list of Layers The left layer(s) for comparison. right_layer: Layer or list of Layers The right layer(s) for comparison. """ _view_name = Unicode('LeafletSplitMapControlView').tag(sync=True) _model_name = Unicode('LeafletSplitMapControlModel').tag(sync=True) left_layer = Union((Instance(Layer), List(Instance(Layer)))).tag(sync=True, **widget_serialization) right_layer = Union((Instance(Layer), List(Instance(Layer)))).tag(sync=True, **widget_serialization) @default('left_layer') def _default_left_layer(self): # TODO: Shouldn't this be None? return TileLayer() @default('right_layer') def _default_right_layer(self): # TODO: Shouldn't this be None? return TileLayer() def __init__(self, **kwargs): super(SplitMapControl, self).__init__(**kwargs) self.on_msg(self._handle_leaflet_event) def _handle_leaflet_event(self, _, content, buffers): if content.get('event', '') == 'dividermove': event = content.get('event') # TODO: Add x trait? self.x = event.x
[docs] class DrawControl(Control): """DrawControl class. Drawing tools for drawing on the map. """ _view_name = Unicode('LeafletDrawControlView').tag(sync=True) _model_name = Unicode('LeafletDrawControlModel').tag(sync=True) # Enable each of the following drawing by giving them a non empty dict of options # You can add Leaflet style options in the shapeOptions sub-dict # See https://github.com/Leaflet/Leaflet.draw#polylineoptions # TODO: mutable default value! polyline = Dict({'shapeOptions': {}}).tag(sync=True) # See https://github.com/Leaflet/Leaflet.draw#polygonoptions # TODO: mutable default value! polygon = Dict({'shapeOptions': {}}).tag(sync=True) circlemarker = Dict({'shapeOptions': {}}).tag(sync=True) # Leave empty to disable these circle = Dict().tag(sync=True) rectangle = Dict().tag(sync=True) marker = Dict().tag(sync=True) # Edit tools edit = Bool(True).tag(sync=True) remove = Bool(True).tag(sync=True) # Layer data data = List().tag(sync=True) last_draw = Dict({ 'type': 'Feature', 'geometry': None }) last_action = Unicode() _draw_callbacks = Instance(CallbackDispatcher, ()) def __init__(self, **kwargs): super(DrawControl, self).__init__(**kwargs) self.on_msg(self._handle_leaflet_event) def _handle_leaflet_event(self, _, content, buffers): if content.get('event', '').startswith('draw'): event, action = content.get('event').split(':') self.last_draw = content.get('geo_json') self.last_action = action self._draw_callbacks(self, action=action, geo_json=self.last_draw)
[docs] def on_draw(self, callback, remove=False): """Add a draw event listener. Parameters ---------- callback : callable Callback function that will be called on draw event. remove: boolean Whether to remove this callback or not. Defaults to False. """ self._draw_callbacks.register_callback(callback, remove=remove)
[docs] def clear(self): """Clear all drawings.""" self.send({'msg': 'clear'})
[docs] def clear_polylines(self): """Clear all polylines.""" self.send({'msg': 'clear_polylines'})
[docs] def clear_polygons(self): """Clear all polygons.""" self.send({'msg': 'clear_polygons'})
[docs] def clear_circles(self): """Clear all circles.""" self.send({'msg': 'clear_circles'})
[docs] def clear_circle_markers(self): """Clear all circle markers.""" self.send({'msg': 'clear_circle_markers'})
[docs] def clear_rectangles(self): """Clear all rectangles.""" self.send({'msg': 'clear_rectangles'})
[docs] def clear_markers(self): """Clear all markers.""" self.send({'msg': 'clear_markers'})
[docs] class ZoomControl(Control): """ZoomControl class, with Control as parent class. A control which contains buttons for zooming in/out the Map. Attributes ---------- zoom_in_text: str, default '+' Text to put in the zoom-in button. zoom_in_title: str, default 'Zoom in' Title to put in the zoom-in button, this is shown when the mouse is over the button. zoom_out_text: str, default '-' Text to put in the zoom-out button. zoom_out_title: str, default 'Zoom out' Title to put in the zoom-out button, this is shown when the mouse is over the button. """ _view_name = Unicode('LeafletZoomControlView').tag(sync=True) _model_name = Unicode('LeafletZoomControlModel').tag(sync=True) zoom_in_text = Unicode('+').tag(sync=True, o=True) zoom_in_title = Unicode('Zoom in').tag(sync=True, o=True) zoom_out_text = Unicode('-').tag(sync=True, o=True) zoom_out_title = Unicode('Zoom out').tag(sync=True, o=True)
[docs] class ScaleControl(Control): """ScaleControl class, with Control as parent class. A control which shows the Map scale. Attributes ---------- max_width: int, default 100 Max width of the control, in pixels. metric: bool, default True Whether to show metric units. imperial: bool, default True Whether to show imperial units. """ _view_name = Unicode('LeafletScaleControlView').tag(sync=True) _model_name = Unicode('LeafletScaleControlModel').tag(sync=True) max_width = Int(100).tag(sync=True, o=True) metric = Bool(True).tag(sync=True, o=True) imperial = Bool(True).tag(sync=True, o=True) update_when_idle = Bool(False).tag(sync=True, o=True)
[docs] class AttributionControl(Control): """AttributionControl class. A control which contains the layers attribution. """ _view_name = Unicode('LeafletAttributionControlView').tag(sync=True) _model_name = Unicode('LeafletAttributionControlModel').tag(sync=True) prefix = Unicode('ipyleaflet').tag(sync=True, o=True)
[docs] class LegendControl(Control): """LegendControl class, with Control as parent class. A control which contains a legend. .. deprecated :: 0.17.0 The constructor argument 'name' is deprecated, use the 'title' argument instead. Attributes ---------- title: str, default 'Legend' The title of the legend. legend: dict, default 'Legend' A dictionary containing names as keys and CSS colors as values. """ _view_name = Unicode('LeafletLegendControlView').tag(sync=True) _model_name = Unicode('LeafletLegendControlModel').tag(sync=True) title = Unicode('Legend').tag(sync=True) legend = Dict(default_value={ "value 1": "#AAF", "value 2": "#55A", "value 3": "#005"}).tag(sync=True) def __init__(self, legend, *args, **kwargs): kwargs["legend"] = legend # For backwards compatibility with ipyleaflet<=0.16.0 if 'name' in kwargs: warnings.warn("the name argument is deprecated, use title instead", DeprecationWarning) kwargs.setdefault('title', kwargs['name']) del kwargs['name'] super().__init__(*args, **kwargs) @property def name(self): """The title of the legend. .. deprecated :: 0.17.0 Use title attribute instead. """ warnings.warn(".name is deprecated, use .title instead", DeprecationWarning) return self.title @name.setter def name(self, title): warnings.warn(".name is deprecated, use .title instead", DeprecationWarning) self.title = title @property def legends(self): """The legend information. .. deprecated :: 0.17.0 Use legend attribute instead. """ warnings.warn(".legends is deprecated, use .legend instead", DeprecationWarning) return self.legend @legends.setter def legends(self, legends): warnings.warn(".legends is deprecated, use .legend instead", DeprecationWarning) self.legend = legends @property def positioning(self): """The position information. .. deprecated :: 0.17.0 Use position attribute instead. """ warnings.warn(".positioning is deprecated, use .position instead", DeprecationWarning) return self.position @positioning.setter def positioning(self, position): warnings.warn(".positioning is deprecated, use .position instead", DeprecationWarning) self.position = position @property def positionning(self): """The position information. .. deprecated :: 0.17.0 Use position attribute instead. """ warnings.warn(".positionning is deprecated, use .position instead", DeprecationWarning) return self.position @positionning.setter def positionning(self, position): warnings.warn(".positionning is deprecated, use .position instead", DeprecationWarning) self.position = position
[docs] def add_legend_element(self, key, value): """Add a new legend element. Parameters ---------- key: str The key for the legend element. value: CSS Color The value for the legend element. """ self.legend[key] = value self.send_state()
[docs] def remove_legend_element(self, key): """Remove a legend element. Parameters ---------- key: str The element to remove. """ del self.legend[key] self.send_state()
[docs] class ColormapControl(WidgetControl): """ColormapControl class, with WidgetControl as parent class. A control which contains a colormap. Attributes ---------- caption : str, default 'caption' The caption of the colormap. colormap: branca.colormap.ColorMap instance, default linear.OrRd_06 The colormap used for the effect. value_min : float, default 0.0 The minimal value taken by the data to be represented by the colormap. value_max : float, default 1.0 The maximal value taken by the data to be represented by the colormap. """ caption = Unicode('caption') colormap = Instance(ColorMap, default_value=linear.OrRd_06) value_min = CFloat(0.0) value_max = CFloat(1.0) @default('widget') def _default_widget(self): widget = Output(layout={'height': '40px', 'width': '520px', 'margin': '0px -19px 0px 0px'}) with widget: colormap = self.colormap.scale(self.value_min, self.value_max) colormap.caption = self.caption display(colormap) return widget
[docs] class SearchControl(Control): """ SearchControl class, with Control as parent class. Attributes ---------- url: string, default "" The url used for the search queries. layer: default None The LayerGroup used for search queries. zoom: int, default None The zoom level after moving to searched location, by default zoom level will not change. marker: default Marker() The marker used by the control. found_style: default {‘fillColor’: ‘#3f0’, ‘color’: ‘#0f0’} Style for searched feature when searching in LayerGroup. """ _view_name = Unicode('LeafletSearchControlView').tag(sync=True) _model_name = Unicode('LeafletSearchControlModel').tag(sync=True) url = Unicode().tag(sync=True, o=True) zoom = Int(default_value=None, allow_none=True).tag(sync=True, o=True) property_name = Unicode('display_name').tag(sync=True, o=True) property_loc = List(['lat', 'lon']).tag(sync=True, o=True) jsonp_param = Unicode('json_callback').tag(sync=True, o=True) auto_type = Bool(False).tag(sync=True, o=True) auto_collapse = Bool(False).tag(sync=True, o=True) animate_location = Bool(False).tag(sync=True, o=True) found_style = Dict(default_value={"fillColor": "#3f0", "color": "#0f0"}).tag(sync=True, o=True) marker = Instance(Marker, allow_none=True, default_value=None).tag(sync=True, **widget_serialization) layer = Instance(LayerGroup, allow_none=True, default_value=None).tag(sync=True, **widget_serialization) _location_found_callbacks = Instance(CallbackDispatcher, ()) def __init__(self, **kwargs): super().__init__(**kwargs) self.on_msg(self._handle_leaflet_event) def _handle_leaflet_event(self, _, content, buffers): if content.get('event', '') == 'locationfound': self._location_found_callbacks(**content)
[docs] def on_feature_found(self, callback, remove=False): """Add a found feature event listener for searching in GeoJSON layer. Parameters ---------- callback : callable Callback function that will be called on found event when searching in GeoJSON layer. remove: boolean Whether to remove this callback or not. Defaults to False. """ self._location_found_callbacks.register_callback(callback, remove=remove)
[docs] def on_location_found(self, callback, remove=False): """Add a found location event listener. The callback will be called when a search result has been found. Parameters ---------- callback : callable Callback function that will be called on location found event. remove: boolean Whether to remove this callback or not. Defaults to False. """ self._location_found_callbacks.register_callback(callback, remove=remove)
[docs] class MapStyle(Style, Widget): """Map Style Widget Custom map style. Attributes ---------- cursor: str, default 'grab' The cursor to use for the mouse when it's on the map. Should be a valid CSS cursor value. """ _model_name = Unicode('LeafletMapStyleModel').tag(sync=True) _model_module = Unicode("jupyter-leaflet").tag(sync=True) _model_module_version = Unicode(EXTENSION_VERSION).tag(sync=True) cursor = Enum(values=allowed_cursor, default_value='grab').tag(sync=True)
[docs] class Map(DOMWidget, InteractMixin): """Map class. The Map class is the main widget in ipyleaflet. Attributes ---------- layers: list of Layer instances The list of layers that are currently on the map. controls: list of Control instances The list of controls that are currently on the map. center: list, default [0, 0] The current center of the map. zoom: float, default 12 The current zoom value of the map. max_zoom: float, default None Maximal zoom value. min_zoom: float, default None Minimal zoom value. zoom_snap: float, default 1 Forces the map’s zoom level to always be a multiple of this. zoom_delta: float, default 1 Controls how much the map’s zoom level will change after pressing + or - on the keyboard, or using the zoom controls. crs: projection, default projections.EPSG3857 Coordinate reference system, which can be ‘Earth’, ‘EPSG3395’, ‘EPSG3857’, ‘EPSG4326’, ‘Base’, ‘Simple’ or user defined projection. dragging: boolean, default True Whether the map be draggable with mouse/touch or not. touch_zoom: boolean, default True Whether the map can be zoomed by touch-dragging with two fingers on mobile. scroll_wheel_zoom: boolean,default False Whether the map can be zoomed by using the mouse wheel. double_click_zoom: boolean, default True Whether the map can be zoomed in by double clicking on it and zoomed out by double clicking while holding shift. box_zoom: boolean, default True Whether the map can be zoomed to a rectangular area specified by dragging the mouse while pressing the shift key tap: boolean, default True Enables mobile hacks for supporting instant taps. tap_tolerance: int, default 15 The max number of pixels a user can shift his finger during touch for it to be considered a valid tap. world_copy_jump: boolean, default False With this option enabled, the map tracks when you pan to another “copy” of the world and seamlessly jumps to. close_popup_on_click: boolean, default True Set it to False if you don’t want popups to close when user clicks the map. bounce_at_zoom_limits: boolean, default True Set it to False if you don’t want the map to zoom beyond min/max zoom and then bounce back when pinch-zooming. keyboard: booelan, default True Makes the map focusable and allows users to navigate the map with keyboard arrows and +/- keys. keyboard_pan_offset: int, default 80 keyboard_zoom_offset: int, default 1 inertia: boolean, default True If enabled, panning of the map will have an inertia effect. inertia_deceleration: float, default 3000 The rate with which the inertial movement slows down, in pixels/second². inertia_max_speed: float, default 1500 Max speed of the inertial movement, in pixels/second. zoom_control: boolean, default True attribution_control: boolean, default True zoom_animation_threshold: int, default 4 """ _view_name = Unicode('LeafletMapView').tag(sync=True) _model_name = Unicode('LeafletMapModel').tag(sync=True) _view_module = Unicode('jupyter-leaflet').tag(sync=True) _model_module = Unicode('jupyter-leaflet').tag(sync=True) _view_module_version = Unicode(EXTENSION_VERSION).tag(sync=True) _model_module_version = Unicode(EXTENSION_VERSION).tag(sync=True) # URL of the window where the map is displayed window_url = Unicode(read_only=True).tag(sync=True) # Map options center = List(def_loc).tag(sync=True, o=True) zoom = CFloat(12).tag(sync=True, o=True) max_zoom = CFloat(default_value=None, allow_none=True).tag(sync=True, o=True) min_zoom = CFloat(default_value=None, allow_none=True).tag(sync=True, o=True) zoom_delta = CFloat(1).tag(sync=True, o=True) zoom_snap = CFloat(1).tag(sync=True, o=True) interpolation = Unicode('bilinear').tag(sync=True, o=True) crs = Dict(default_value=projections.EPSG3857).tag(sync=True) prefer_canvas = Bool(False).tag(sync=True, o=True) # Specification of the basemap basemap = Union( (Dict(), Instance(xyzservices.lib.TileProvider), Instance(TileLayer)), default_value=xyzservices.providers.OpenStreetMap.Mapnik) modisdate = Unicode((date.today() - timedelta(days=1)).strftime("%Y-%m-%d")).tag(sync=True) # Interaction options dragging = Bool(True).tag(sync=True, o=True) touch_zoom = Bool(True).tag(sync=True, o=True) scroll_wheel_zoom = Bool(False).tag(sync=True, o=True) double_click_zoom = Bool(True).tag(sync=True, o=True) box_zoom = Bool(True).tag(sync=True, o=True) tap = Bool(True).tag(sync=True, o=True) tap_tolerance = Int(15).tag(sync=True, o=True) world_copy_jump = Bool(False).tag(sync=True, o=True) close_popup_on_click = Bool(True).tag(sync=True, o=True) bounce_at_zoom_limits = Bool(True).tag(sync=True, o=True) keyboard = Bool(True).tag(sync=True, o=True) keyboard_pan_offset = Int(80).tag(sync=True, o=True) keyboard_zoom_offset = Int(1).tag(sync=True, o=True) inertia = Bool(True).tag(sync=True, o=True) inertia_deceleration = Int(3000).tag(sync=True, o=True) inertia_max_speed = Int(1500).tag(sync=True, o=True) # inertia_threshold = Int(?, o=True).tag(sync=True) # fade_animation = Bool(?).tag(sync=True, o=True) # zoom_animation = Bool(?).tag(sync=True, o=True) zoom_animation_threshold = Int(4).tag(sync=True, o=True) # marker_zoom_animation = Bool(?).tag(sync=True, o=True) fullscreen = Bool(False).tag(sync=True, o=True) options = List(trait=Unicode()).tag(sync=True) style = InstanceDict(MapStyle).tag(sync=True, **widget_serialization) default_style = InstanceDict(MapStyle).tag(sync=True, **widget_serialization) dragging_style = InstanceDict(MapStyle).tag(sync=True, **widget_serialization) zoom_control = Bool(True) attribution_control = Bool(True) @default('dragging_style') def _default_dragging_style(self): return {'cursor': 'move'} @default('options') def _default_options(self): return [name for name in self.traits(o=True)] south = Float(def_loc[0], read_only=True).tag(sync=True) north = Float(def_loc[0], read_only=True).tag(sync=True) east = Float(def_loc[1], read_only=True).tag(sync=True) west = Float(def_loc[1], read_only=True).tag(sync=True) bottom = Float(0, read_only=True).tag(sync=True) top = Float(9007199254740991, read_only=True).tag(sync=True) right = Float(0, read_only=True).tag(sync=True) left = Float(9007199254740991, read_only=True).tag(sync=True) panes = Dict().tag(sync=True) layers = Tuple().tag(trait=Instance(Layer), sync=True, **widget_serialization) @default('layers') def _default_layers(self): basemap = self.basemap if isinstance(self.basemap, TileLayer) else basemap_to_tiles(self.basemap, day=self.modisdate) basemap.base = True return (basemap,) bounds = Tuple(read_only=True) bounds_polygon = Tuple(read_only=True) pixel_bounds = Tuple(read_only=True) @observe('south', 'north', 'east', 'west') def _observe_bounds(self, change): self.set_trait('bounds', ((self.south, self.west), (self.north, self.east))) self.set_trait('bounds_polygon', ((self.north, self.west), (self.north, self.east), (self.south, self.east), (self.south, self.west))) @observe('bottom', 'top', 'right', 'left') def _observe_pixel_bounds(self, change): self.set_trait('pixel_bounds', ((self.left, self.top), (self.right, self.bottom))) def __init__(self, **kwargs): self.zoom_control_instance = None self.attribution_control_instance = None super(Map, self).__init__(**kwargs) self.on_msg(self._handle_leaflet_event) if self.zoom_control: self.zoom_control_instance = ZoomControl() self.add(self.zoom_control_instance) if self.attribution_control: self.attribution_control_instance = AttributionControl(position='bottomright') self.add(self.attribution_control_instance) @observe('zoom_control') def observe_zoom_control(self, change): if change['new']: self.zoom_control_instance = ZoomControl() self.add(self.zoom_control_instance) else: if self.zoom_control_instance is not None and self.zoom_control_instance in self.controls: self.remove(self.zoom_control_instance) @observe('attribution_control') def observe_attribution_control(self, change): if change['new']: self.attribution_control_instance = AttributionControl(position='bottomright') self.add(self.attribution_control_instance) else: if self.attribution_control_instance is not None and self.attribution_control_instance in self.controls: self.remove(self.attribution_control_instance) @validate('panes') def _validate_panes(self, proposal): '''Validate panes. ''' error_msg = "Panes should look like: {'pane_name': {'zIndex': 650, 'pointerEvents': 'none'}, ...}" for k1, v1 in proposal.value.items(): if not isinstance(k1, str) or not isinstance(v1, dict): raise PaneException(error_msg) for k2, v2 in v1.items(): if not isinstance(k2, str) or not isinstance(v2, (str, int, float)): raise PaneException(error_msg) return proposal.value _layer_ids = List() @validate('layers') def _validate_layers(self, proposal): '''Validate layers list. Makes sure only one instance of any given layer can exist in the layers list. ''' self._layer_ids = [layer.model_id for layer in proposal.value] if len(set(self._layer_ids)) != len(self._layer_ids): raise LayerException('duplicate layer detected, only use each layer once') return proposal.value
[docs] def add_layer(self, layer): """Add a layer on the map. .. deprecated :: 0.17.0 Use add method instead. Parameters ---------- layer: Layer instance The new layer to add. """ warnings.warn("add_layer is deprecated, use add instead", DeprecationWarning) self.add(layer)
[docs] def remove_layer(self, rm_layer): """Remove a layer from the map. .. deprecated :: 0.17.0 Use remove method instead. Parameters ---------- layer: Layer instance The layer to remove. """ warnings.warn("remove_layer is deprecated, use remove instead", DeprecationWarning) self.remove(rm_layer)
[docs] def substitute_layer(self, old, new): """Replace a layer with another one on the map. .. deprecated :: 0.17.0 Use substitute method instead. Parameters ---------- old: Layer instance The old layer to remove. new: Layer instance The new layer to add. """ warnings.warn("substitute_layer is deprecated, use substitute instead", DeprecationWarning) self.substitute(old, new)
[docs] def clear_layers(self): """Remove all layers from the map. .. deprecated :: 0.17.0 Use add method instead. """ warnings.warn("clear_layers is deprecated, use clear instead", DeprecationWarning) self.layers = ()
controls = Tuple().tag(trait=Instance(Control), sync=True, **widget_serialization) _control_ids = List() @validate('controls') def _validate_controls(self, proposal): '''Validate controls list. Makes sure only one instance of any given layer can exist in the controls list. ''' self._control_ids = [c.model_id for c in proposal.value] if len(set(self._control_ids)) != len(self._control_ids): raise ControlException('duplicate control detected, only use each control once') return proposal.value
[docs] def add_control(self, control): """Add a control on the map. .. deprecated :: 0.17.0 Use add method instead. Parameters ---------- control: Control instance The new control to add. """ warnings.warn("add_control is deprecated, use add instead", DeprecationWarning) self.add(control)
[docs] def remove_control(self, control): """Remove a control from the map. .. deprecated :: 0.17.0 Use remove method instead. Parameters ---------- control: Control instance The control to remove. """ warnings.warn("remove_control is deprecated, use remove instead", DeprecationWarning) self.remove(control)
[docs] def clear_controls(self): """Remove all controls from the map. .. deprecated :: 0.17.0 Use clear method instead. """ warnings.warn("clear_controls is deprecated, use clear instead", DeprecationWarning) self.controls = ()
[docs] def save(self, outfile, **kwargs): """Save the Map to an .html file. Parameters ---------- outfile: str or file-like object The file to write the HTML output to. kwargs: keyword-arguments Extra parameters to pass to the ipywidgets.embed.embed_minimal_html function. """ embed_minimal_html(outfile, views=[self], **kwargs)
def __iadd__(self, item): self.add(item) return self def __isub__(self, item): self.remove(item) return self def __add__(self, item): return self.add(item)
[docs] def add(self, item, index=None): """Add an item on the map: either a layer or a control. Parameters ---------- item: Layer or Control instance The layer or control to add. index: int The index to insert a Layer. If not specified, the layer is added to the end (on top). """ if hasattr(item, 'as_leaflet_layer'): item = item.as_leaflet_layer() if isinstance(item, Layer): if isinstance(item, dict): item = basemap_to_tiles(item) if item.model_id in self._layer_ids: raise LayerException('layer already on map: %r' % item) if index is not None: if not isinstance(index, int) or index < 0 or index > len(self.layers): raise ValueError("Invalid index value") self.layers = tuple(list(self.layers)[:index] + [item] + list(self.layers)[index:]) else: self.layers = tuple([layer for layer in self.layers] + [item]) elif isinstance(item, Control): if item.model_id in self._control_ids: raise ControlException('control already on map: %r' % item) self.controls = tuple([control for control in self.controls] + [item]) return self
[docs] def remove(self, item): """Remove an item from the map : either a layer or a control. Parameters ---------- item: Layer or Control instance The layer or control to remove. """ if isinstance(item, Layer): if item.model_id not in self._layer_ids: raise LayerException('layer not on map: %r' % item) self.layers = tuple([layer for layer in self.layers if layer.model_id != item.model_id]) elif isinstance(item, Control): if item.model_id not in self._control_ids: raise ControlException('control not on map: %r' % item) self.controls = tuple([control for control in self.controls if control.model_id != item.model_id]) return self
[docs] def clear(self): "Clear all layers and controls." self.controls = () self.layers = () return self
[docs] def substitute(self, old, new): """Replace an item (layer or control) with another one on the map. Parameters ---------- old: Layer or control instance The old layer or control to remove. new: Layer or control instance The new layer or control to add. """ if (isinstance(new, Layer)): if isinstance(new, dict): new = basemap_to_tiles(new) if old.model_id not in self._layer_ids: raise LayerException('Could not substitute layer: layer not on map.') self.layers = tuple([new if layer.model_id == old.model_id else layer for layer in self.layers]) elif (isinstance(new, Control)): if old.model_id not in self._control_ids: raise ControlException('Could not substitute control: control not on map.') self.controls = tuple([new if control.model_id == old.model_id else control for control in self.controls]) return self
# Event handling _interaction_callbacks = Instance(CallbackDispatcher, ()) def _handle_leaflet_event(self, _, content, buffers): if content.get('event', '') == 'interaction': self._interaction_callbacks(**content) def on_interaction(self, callback, remove=False): self._interaction_callbacks.register_callback(callback, remove=remove)
[docs] def fit_bounds(self, bounds): """Sets a map view that contains the given geographical bounds with the maximum zoom level possible. Parameters ---------- bounds: list of lists The lat/lon bounds in the form [[south, west], [north, east]]. """ asyncio.ensure_future(self._fit_bounds(bounds))
async def _fit_bounds(self, bounds): (b_south, b_west), (b_north, b_east) = bounds center = b_south + (b_north - b_south) / 2, b_west + (b_east - b_west) / 2 if center != self.center: self.center = center await wait_for_change(self, 'bounds') zoomed_out = False # zoom out while True: if self.zoom <= 1: break (south, west), (north, east) = self.bounds if south > b_south or north < b_north or west > b_west or east < b_east: self.zoom -= 1 await wait_for_change(self, 'bounds') zoomed_out = True else: break if not zoomed_out: # zoom in while True: (south, west), (north, east) = self.bounds if south < b_south and north > b_north and west < b_west and east > b_east: self.zoom += 1 await wait_for_change(self, 'bounds') else: self.zoom -= 1 await wait_for_change(self, 'bounds') break