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), **kwargs):
super().__init__(basemap=basemap, zoom=zoom, center=center, **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` 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)
if current_layer is None:
current_layer = ipyleaflet.GeoJSON(
name=layer_name, data=dict(feature_collection), **kwargs
)
else:
current_layer.data = dict(feature_collection)
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=[], **kwargs):
super().__init__(**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