Skip to content

Commit

Permalink
Add visible_callback
Browse files Browse the repository at this point in the history
  • Loading branch information
davidbrochart committed Aug 4, 2022
1 parent 15b63ca commit 7e19824
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 3 deletions.
134 changes: 134 additions & 0 deletions examples/markers_or_density.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "15eefe2f-7d1b-4814-9a5b-3b57df451fd7",
"metadata": {},
"outputs": [],
"source": [
"from ipyleaflet import GeoData, Map, basemaps, LayersControl, WidgetControl\n",
"from ipywidgets import FloatSlider\n",
"import xarray_leaflet\n",
"import geopandas as gpd\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "30114fbe-e6ef-4547-98ab-28686f8980b1",
"metadata": {},
"outputs": [],
"source": [
"!wget https://raw.githubusercontent.com/drei01/geojson-world-cities/master/cities.geojson"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ab8cd637-be46-4953-96a8-edd918aeccf8",
"metadata": {},
"outputs": [],
"source": [
"df = gpd.read_file(\"cities.geojson\")\n",
"df.geometry = df.geometry.centroid\n",
"measurement = \"mask\"\n",
"df[measurement] = 1"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "55a78adf",
"metadata": {},
"outputs": [],
"source": [
"m = Map(center=(34.36519854648025, -47.3743535773436), zoom=3, basemap=basemaps.CartoDB.DarkMatter, interpolation='nearest')\n",
"m"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6180d7f5-05e5-4f56-b179-78cc8f581f1a",
"metadata": {},
"outputs": [],
"source": [
"class LayerManager:\n",
"\n",
" def __init__(self, df, measurement):\n",
" self.df = df\n",
" self.measurement = measurement\n",
" self.marker_layer = None\n",
"\n",
" def visible_callback(self, m, da, bbox):\n",
" # show density if there are too many points\n",
" # otherwise show individual markers\n",
" density_visible = da.sum() > 500\n",
" if self.marker_layer:\n",
" # remove markers\n",
" m.remove(self.marker_layer)\n",
" self.marker_layer = None\n",
" if not density_visible:\n",
" # only show markers that are on the map view\n",
" self.marker_layer = GeoData(geo_dataframe=self.df.clip(bbox))\n",
" m.add(self.marker_layer)\n",
" return density_visible\n",
"\n",
"layer_manager = LayerManager(df, measurement)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2ab54311-d960-4b51-9623-e8d91b97ad75",
"metadata": {},
"outputs": [],
"source": [
"l = df.leaflet.plot(m, measurement=\"mask\", colormap=plt.cm.inferno, visible_callback=layer_manager.visible_callback)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fe1ea3ac-7c1b-4443-8f82-2bffe819b27c",
"metadata": {},
"outputs": [],
"source": [
"layers_control = LayersControl(position='topright')\n",
"m.add_control(layers_control)\n",
"\n",
"opacity_slider = FloatSlider(description='Opacity:', min=0, max=1, value=1)\n",
"\n",
"def set_opacity(change):\n",
" l.opacity = change['new']\n",
"\n",
"opacity_slider.observe(set_opacity, names='value')\n",
"slider_control = WidgetControl(widget=opacity_slider, position='bottomleft')\n",
"m.add_control(slider_control)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.5"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
21 changes: 18 additions & 3 deletions xarray_leaflet/xarray_leaflet.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import asyncio
import os
import tempfile
from typing import Optional
from typing import Callable, Optional

import geopandas as gpd
import matplotlib as mpl
Expand Down Expand Up @@ -62,6 +62,7 @@ def plot(
resampling=Resampling.nearest,
get_base_url=None,
measurement: Optional[str] = None,
visible_callback: Optional[Callable] = None,
):
"""Display an array as an interactive map.
Expand Down Expand Up @@ -114,6 +115,13 @@ def plot(
A function taking the window URL and returning the base URL to use.
measurement: str, optional
The geocube measurement.
visible_callback: callable, optional
A callable taking the following arguments:
- the ipyleaflet.Map
- the xarray.DataArray of the visible region
- the mercantile.LngLatBbox of the visible region
and returning True if the layer should be shown, False otherwise.
Returns
-------
Expand All @@ -125,6 +133,7 @@ def plot(

if self.is_vector:
# source is a GeoDataFrame (vector)
self.visible_callback = visible_callback
if measurement is None:
raise RuntimeError("You must provide a 'measurement'.")
self.measurement = measurement
Expand Down Expand Up @@ -365,6 +374,12 @@ def _get_vector_tiles(self, change=None):
if self.dynamic:
llbbox = mercantile.LngLatBbox(west, south, east, north)
da_visible = self.zvect.get_da_llbbox(llbbox, z)
# check if we must show the layer
if self.visible_callback and not self.visible_callback(
self.m, da_visible, llbbox
):
self.m.remove_control(self.spinner_control)
return
if da_visible is None:
self.max_value = 0
else:
Expand Down Expand Up @@ -600,7 +615,7 @@ async def async_fit_bounds(self):
class DataArrayLeaflet(Leaflet):
"""A DataArraye extension for tiled map plotting, based on (ipy)leaflet."""

def __init__(self, da: xr.DataArray = None):
def __init__(self, da: xr.DataArray):
self._da = da
self._da_selected = None
self.is_vector = False
Expand All @@ -610,6 +625,6 @@ def __init__(self, da: xr.DataArray = None):
class GeoDataFrameLeaflet(Leaflet):
"""A GeoDataFrame extension for tiled map plotting, based on (ipy)leaflet."""

def __init__(self, df: gpd.GeoDataFrame = None):
def __init__(self, df: gpd.GeoDataFrame):
self._df = df
self.is_vector = True

0 comments on commit 7e19824

Please sign in to comment.