def mk_map_region_selector(height='600px', **kwargs): from ipyleaflet import Map, WidgetControl, FullScreenControl, DrawControl from ipywidgets.widgets import Layout, Button, HTML from types import SimpleNamespace state = SimpleNamespace(selection=None, bounds=None, done=False) btn_done = Button(description='done', layout=Layout(width='5em')) btn_done.style.button_color = 'green' btn_done.disabled = True html_info = HTML( layout=Layout(flex='1 0 20em', width='20em', height='3em')) def update_info(txt): html_info.value = '<pre style="color:grey">' + txt + '</pre>' m = Map(**kwargs) if len(kwargs) else Map(zoom=2) m.scroll_wheel_zoom = True m.layout.height = height widgets = [ WidgetControl(widget=btn_done, position='topright'), WidgetControl(widget=html_info, position='bottomleft'), ] for w in widgets: m.add_control(w) draw = DrawControl() draw.circle = {} draw.polyline = {} draw.circlemarker = {} shape_opts = { "fillColor": "#fca45d", "color": "#000000", "fillOpacity": 0.1 } draw.rectangle = {"shapeOptions": shape_opts} poly_opts = {"shapeOptions": dict(**shape_opts)} poly_opts["shapeOptions"]["original"] = dict(**shape_opts) poly_opts["shapeOptions"]["editing"] = dict(**shape_opts) draw.polygon = poly_opts draw.edit = True draw.remove = True m.add_control(draw) m.add_control(FullScreenControl()) def on_done(btn): state.done = True btn_done.disabled = True m.remove_control(draw) for w in widgets: m.remove_control(w) def bounds_handler(event): (lat1, lon1), (lat2, lon2) = event['new'] txt = 'lat: [{:.{n}f}, {:.{n}f}]\nlon: [{:.{n}f}, {:.{n}f}]'.format( lat1, lat2, lon1, lon2, n=4) update_info(txt) state.bounds = dict(lat=(lat1, lat2), lon=(lon1, lon2)) def on_draw(event): v = event['new'] action = event['name'] if action == 'last_draw': state.selection = v['geometry'] elif action == 'last_action' and v == 'deleted': state.selection = None btn_done.disabled = state.selection is None draw.observe(on_draw) m.observe(bounds_handler, ('bounds', )) btn_done.on_click(on_done) return m, state
def __init__(self, map: ipyleaflet.Map) -> None: self.layers: Dict[ipyleaflet.TileLayer, TileManager] = {} self.map = map map.observe(self, names=["window_url", "bounds", "layers"])
def __init__( self, a_map: Map, description: str = "H3", position: str = "topright", ): """Instantiate a tile grid tool and place it on a map. """ self._max_zoom_delta = -1 self.tile_id = "" self.level = int(a_map.zoom) style = {"color": "#888888", "weight": 1, "fillOpacity": 0} hover_style = {"weight": 3, "fillOpacity": 0.1} self.gj = GeoJSON(data=geojson.Feature(), name=description, style=style, hover_style=hover_style) min_, max_ = 0, int(a_map.zoom) + self._max_zoom_delta self.slider = IntSlider(description=description, min=min_, max=max_, value=self.level) self.ht = HTML(f"ID: {self.tile_id} Map zoom: {int(a_map.zoom)}") self.close_btn = Button( icon="times", button_style="info", tooltip="Close the widget", layout=Layout(width="32px"), ) self.widget = HBox([self.slider, self.ht, self.close_btn]) def hover(event, feature, **kwargs): if event == "mouseover": self.tile_id = feature["id"] self.ht.value = f"{self.tile_id} Map zoom: {int(a_map.zoom)}" def slider_moved(event): if event["type"] == "change" and event["name"] == "value": self.level = event["new"] # Ipyleaflet buglet(?): This name is updated in the GeoJSON layer, # but not in the LayersControl! self.gj.name = f"H3" # level {self.level}" self.tile_id = "" map_interacted({ "type": "change", "name": "bounds", "owner": a_map }) self.slider.observe(slider_moved) def map_interacted(event): if event["type"] == "change" and event["name"] == "bounds": self.ht.value = f"{self.tile_id}, Map zoom: {int(a_map.zoom)}" self.slider.max = int(a_map.zoom) + self._max_zoom_delta m = event["owner"] b_poly = list(m.bounds_polygon) b_poly += [tuple(b_poly[0])] # m += Polyline(locations=b_poly) poly = geojson.Polygon(coordinates=[[(p[0], p[1]) for p in b_poly]]) hexagons = list(h3.polyfill(dict(poly), self.slider.value)) fc = geojson.FeatureCollection(features=[ geojson.Polygon(coordinates=h3.h3_set_to_multi_polygon( [h], geo_json=True)[0], id=h) for h in hexagons ]) self.gj.data = fc # Ipyleaflet buglet(?): This name is updated in the GeoJSON layer, # but not in the LayersControl! self.gj.name = f"H3" # level {self.level}" self.gj.on_hover(hover) def close_click(change): self.widget.children = [] self.widget.close() self.close_btn.on_click(close_click) a_map += self.gj a_map.observe(map_interacted) map_interacted({"type": "change", "name": "bounds", "owner": a_map}) self.widget_control = WidgetControl(widget=self.widget, position=position) a_map.add_control(self.widget_control)
def __init__( self, a_map: Map, description: str = "Mercator", position: str = "topright", ): """Instantiate a tile grid tool and place it on a map. """ self._max_zoom_delta = 4 self.tile_id = "" self.level = int(a_map.zoom) style = {"color": "#888888", "weight": 1, "fillOpacity": 0} hover_style = {"weight": 3, "fillOpacity": 0.1} self.gj = GeoJSON(data=geojson.Feature(), name=description, style=style, hover_style=hover_style) min, max = 0, int(a_map.zoom) + self._max_zoom_delta self.slider = IntSlider(description=description, min=min, max=max, value=self.level) self.ht = HTML(f"ID: {self.tile_id} Map zoom: {int(a_map.zoom)}") self.close_btn = Button( icon="times", button_style="info", tooltip="Close the widget", layout=Layout(width="32px"), ) self.widget = HBox([self.slider, self.ht, self.close_btn]) def hover(event, feature, **kwargs): if event == "mouseover": self.tile_id = feature["id"] self.ht.value = f"{self.tile_id} Map zoom: {int(a_map.zoom)}" def slider_moved(event): if event["type"] == "change" and event["name"] == "value": self.level = event["new"] # Ipyleaflet buglet(?): This name is updated in the GeoJSON layer, # but not in the LayersControl! self.gj.name = f"Mercator" # level {self.level}" self.tile_id = "" map_interacted({ "type": "change", "name": "bounds", "owner": a_map }) self.slider.observe(slider_moved) def map_interacted(event): if event["type"] == "change" and event["name"] == "bounds": self.ht.value = f"{self.tile_id}, Map zoom: {int(a_map.zoom)}" self.slider.max = int(a_map.zoom) + self._max_zoom_delta m = event["owner"] ((south, west), (north, east)) = m.bounds b_poly = list(m.bounds_polygon) b_poly += [tuple(b_poly[0])] # m += Polyline(locations=b_poly) # Attention in the order of west, south, east, north! tiles = mercantile.tiles(west, south, east, north, zooms=self.level) features = [mercantile.feature(t) for t in tiles] self.gj.data = geojson.FeatureCollection(features=features) # Ipyleaflet buglet(?): This name is updated in the GeoJSON layer, # but not in the LayersControl! self.gj.name = f"Mercator" # level {self.level}" self.gj.on_hover(hover) def close_click(change): self.widget.children = [] self.widget.close() self.close_btn.on_click(close_click) a_map += self.gj a_map.observe(map_interacted) map_interacted({"type": "change", "name": "bounds", "owner": a_map}) self.widget_control = WidgetControl(widget=self.widget, position=position) a_map.add_control(self.widget_control)
def mk_map_region_selector(map=None, height="600px", **kwargs): from ipyleaflet import Map, WidgetControl, FullScreenControl, DrawControl from ipywidgets.widgets import Layout, Button, HTML from types import SimpleNamespace state = SimpleNamespace(selection=None, bounds=None, done=False) btn_done = Button(description="done", layout=Layout(width="5em")) btn_done.style.button_color = "green" btn_done.disabled = True html_info = HTML(layout=Layout(flex="1 0 20em", width="20em", height="3em")) def update_info(txt): html_info.value = '<pre style="color:grey">' + txt + "</pre>" def render_bounds(bounds): (lat1, lon1), (lat2, lon2) = bounds txt = "lat: [{:.{n}f}, {:.{n}f}]\nlon: [{:.{n}f}, {:.{n}f}]".format( lat1, lat2, lon1, lon2, n=4 ) update_info(txt) if map is None: m = Map(**kwargs) if len(kwargs) else Map(zoom=2) m.scroll_wheel_zoom = True m.layout.height = height else: m = map render_bounds(m.bounds) widgets = [ WidgetControl(widget=btn_done, position="topright"), WidgetControl(widget=html_info, position="bottomleft"), ] for w in widgets: m.add_control(w) draw = DrawControl() draw.circle = {} draw.polyline = {} draw.circlemarker = {} shape_opts = {"fillColor": "#fca45d", "color": "#000000", "fillOpacity": 0.1} draw.rectangle = {"shapeOptions": shape_opts, "metric": ["km", "m"]} poly_opts = {"shapeOptions": dict(**shape_opts)} poly_opts["shapeOptions"]["original"] = dict(**shape_opts) poly_opts["shapeOptions"]["editing"] = dict(**shape_opts) draw.polygon = poly_opts draw.edit = True draw.remove = True m.add_control(draw) m.add_control(FullScreenControl()) def on_done(btn): state.done = True btn_done.disabled = True m.remove_control(draw) for w in widgets: m.remove_control(w) def bounds_handler(event): bounds = event["new"] render_bounds(bounds) (lat1, lon1), (lat2, lon2) = bounds state.bounds = dict(lat=(lat1, lat2), lon=(lon1, lon2)) def on_draw(event): v = event["new"] action = event["name"] if action == "last_draw": state.selection = v["geometry"] elif action == "last_action" and v == "deleted": state.selection = None btn_done.disabled = state.selection is None draw.observe(on_draw) m.observe(bounds_handler, ("bounds",)) btn_done.on_click(on_done) return m, state