Source code for geodesic.mapping.base

import ipyleaflet
import geodesic
import numpy as np
from shapely.geometry import shape
from shapely.ops import transform

DEFAULT_BASEMAP = ipyleaflet.basemaps.CartoDB.DarkMatter

_color_cycle = [
    "#428BA9",
    "#FF902B",
    "#5FAD56",
    "#966B9D",
    "#DF2935",
]


def _get_color(i: int) -> str:
    """Returns the color corresponding to the given index, cyclic on the _color_cycle."""
    return _color_cycle[i % len(_color_cycle)]


[docs] class Map(ipyleaflet.Map): """Light wrapper on top of an ipyleaflet.Map. A basic map with some basic initialization. """ def __init__( self, basemap=DEFAULT_BASEMAP, zoom=3, center=(0.0, 0.0), scroll_wheel_zoom=True, **kwargs ): super().__init__( basemap=basemap, zoom=zoom, center=center, scroll_wheel_zoom=scroll_wheel_zoom, **kwargs ) self._fc_layers = [] self.add_control(ipyleaflet.LayersControl())
[docs] def add_feature_collection( self, layer_name: str, feature_collection: geodesic.FeatureCollection, on_click: callable = None, style_callback: callable = None, **kwargs, ) -> None: """Add a feature colleciton to the map. Adds a geodesic.FeatureCollection to the map as a GeoJSON layer. Args: layer_name: display name of the layer feature_collection: the ``geodesic.FeatureCollection`` or `geopandas.GeoDataFrame`` to add to the map. on_click: a callback function that will be called when a feature is selected. style_callback: a function that will be called on each feature to return the style. Can be used to style each feature based on its properties. **kwargs: additional kwargs passed to the layer constructor. """ index = len(self._fc_layers) add_layer = True current_layer = None for i, layer in enumerate(self._fc_layers): if layer.name == layer_name: index = i current_layer = layer add_layer = False break color = _get_color(index) if "style" not in kwargs: kwargs["style"] = dict(opacity=1.0, fillOpacity=0.5, color=color, fillColor=color) if "hover_style" not in kwargs: kwargs["hover_style"] = dict(fillOpacity=1.0) try: data = feature_collection.__geo_interface__ except AttributeError: data = dict(feature_collection) if current_layer is None: current_layer = ipyleaflet.GeoJSON(name=layer_name, data=data, **kwargs) else: current_layer.data = data if on_click is not None: current_layer.on_click(on_click) current_layer.style_callback = style_callback if add_layer: self._fc_layers.append(current_layer) self.add_layer(current_layer) else: self._fc_layers[index] = current_layer
[docs] class BBoxSelector(Map): """select a geometry/aoi on the a interactive webmap. Lets a user draw a geometry on the map. This geometry will be accessible as this object's `geometry` parameter and the bounding rectangle corners as `bbox`. Note: Does not handle geometries that cross the datetime. Args: set_geometry_on: a list of objects such as a `geodesic.Feature`, a `geodesic.tesseract.Job`, or a `geodesic.entanglement.Object`. When a draw action is performed, the drawn geometry is set on all objects in this list. """ def __init__(self, set_geometry_on=[], scroll_wheel_zoom=True, **kwargs): super().__init__(scroll_wheel_zoom=scroll_wheel_zoom, **kwargs) self.draw_control = ipyleaflet.DrawControl() self.draw_control.rectangle = dict( shapeOptions=dict(fillColor="#428BA9", color="#428BA9", fillOpacity=0.15) ) self.draw_control.polygon = dict( shapeOptions=dict(fillColor="#428BA9", color="#428BA9", fillOpacity=0.15) ) self.draw_control.circle = dict( shapeOptions=dict(fillColor="#428BA9", color="#428BA9", fillOpacity=0.15) ) self.set_geometry_on = set_geometry_on self.geometry = None self.draw_control.on_draw(self._bbox_callback) self.add_control(self.draw_control) def _bbox_callback(self, target, action, geo_json): geometry = geo_json["geometry"] # If it's a circle, parse that into a polygon so we can use it if "radius" in geo_json["properties"]["style"]: cx, cy = geometry["coordinates"] radius = geo_json["properties"]["style"]["radius"] / ( 1852.0 * 60.0 ) # rough conversion from m to deg angles = np.linspace(0.0, 2.0 * np.pi, 25) cos = np.cos(angles) sin = np.sin(angles) x = (cx + radius * cos).tolist() y = (cy + radius * sin).tolist() coordinates = [[c for c in zip(x, y)]] geometry = {"type": "Polygon", "coordinates": coordinates} def xform(x, y, z=None) -> tuple: while x < -180: x += 360 while x > 180: x -= 360 return x, y self.geometry = shape(geometry) self.geometry = transform(xform, self.geometry) self.action = action for obj in self.set_geometry_on: obj.geometry = self.geometry target.data = [geo_json] @property def bbox(self): """Bbox for the geometry drawn on the map.""" if self.geometry is None: return None return self.geometry.bounds