Skip to content

Commit 7e19824

Browse files
committed
Add visible_callback
1 parent 15b63ca commit 7e19824

File tree

2 files changed

+152
-3
lines changed

2 files changed

+152
-3
lines changed

examples/markers_or_density.ipynb

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": null,
6+
"id": "15eefe2f-7d1b-4814-9a5b-3b57df451fd7",
7+
"metadata": {},
8+
"outputs": [],
9+
"source": [
10+
"from ipyleaflet import GeoData, Map, basemaps, LayersControl, WidgetControl\n",
11+
"from ipywidgets import FloatSlider\n",
12+
"import xarray_leaflet\n",
13+
"import geopandas as gpd\n",
14+
"import matplotlib.pyplot as plt"
15+
]
16+
},
17+
{
18+
"cell_type": "code",
19+
"execution_count": null,
20+
"id": "30114fbe-e6ef-4547-98ab-28686f8980b1",
21+
"metadata": {},
22+
"outputs": [],
23+
"source": [
24+
"!wget https://raw.githubusercontent.com/drei01/geojson-world-cities/master/cities.geojson"
25+
]
26+
},
27+
{
28+
"cell_type": "code",
29+
"execution_count": null,
30+
"id": "ab8cd637-be46-4953-96a8-edd918aeccf8",
31+
"metadata": {},
32+
"outputs": [],
33+
"source": [
34+
"df = gpd.read_file(\"cities.geojson\")\n",
35+
"df.geometry = df.geometry.centroid\n",
36+
"measurement = \"mask\"\n",
37+
"df[measurement] = 1"
38+
]
39+
},
40+
{
41+
"cell_type": "code",
42+
"execution_count": null,
43+
"id": "55a78adf",
44+
"metadata": {},
45+
"outputs": [],
46+
"source": [
47+
"m = Map(center=(34.36519854648025, -47.3743535773436), zoom=3, basemap=basemaps.CartoDB.DarkMatter, interpolation='nearest')\n",
48+
"m"
49+
]
50+
},
51+
{
52+
"cell_type": "code",
53+
"execution_count": null,
54+
"id": "6180d7f5-05e5-4f56-b179-78cc8f581f1a",
55+
"metadata": {},
56+
"outputs": [],
57+
"source": [
58+
"class LayerManager:\n",
59+
"\n",
60+
" def __init__(self, df, measurement):\n",
61+
" self.df = df\n",
62+
" self.measurement = measurement\n",
63+
" self.marker_layer = None\n",
64+
"\n",
65+
" def visible_callback(self, m, da, bbox):\n",
66+
" # show density if there are too many points\n",
67+
" # otherwise show individual markers\n",
68+
" density_visible = da.sum() > 500\n",
69+
" if self.marker_layer:\n",
70+
" # remove markers\n",
71+
" m.remove(self.marker_layer)\n",
72+
" self.marker_layer = None\n",
73+
" if not density_visible:\n",
74+
" # only show markers that are on the map view\n",
75+
" self.marker_layer = GeoData(geo_dataframe=self.df.clip(bbox))\n",
76+
" m.add(self.marker_layer)\n",
77+
" return density_visible\n",
78+
"\n",
79+
"layer_manager = LayerManager(df, measurement)"
80+
]
81+
},
82+
{
83+
"cell_type": "code",
84+
"execution_count": null,
85+
"id": "2ab54311-d960-4b51-9623-e8d91b97ad75",
86+
"metadata": {},
87+
"outputs": [],
88+
"source": [
89+
"l = df.leaflet.plot(m, measurement=\"mask\", colormap=plt.cm.inferno, visible_callback=layer_manager.visible_callback)"
90+
]
91+
},
92+
{
93+
"cell_type": "code",
94+
"execution_count": null,
95+
"id": "fe1ea3ac-7c1b-4443-8f82-2bffe819b27c",
96+
"metadata": {},
97+
"outputs": [],
98+
"source": [
99+
"layers_control = LayersControl(position='topright')\n",
100+
"m.add_control(layers_control)\n",
101+
"\n",
102+
"opacity_slider = FloatSlider(description='Opacity:', min=0, max=1, value=1)\n",
103+
"\n",
104+
"def set_opacity(change):\n",
105+
" l.opacity = change['new']\n",
106+
"\n",
107+
"opacity_slider.observe(set_opacity, names='value')\n",
108+
"slider_control = WidgetControl(widget=opacity_slider, position='bottomleft')\n",
109+
"m.add_control(slider_control)"
110+
]
111+
}
112+
],
113+
"metadata": {
114+
"kernelspec": {
115+
"display_name": "Python 3 (ipykernel)",
116+
"language": "python",
117+
"name": "python3"
118+
},
119+
"language_info": {
120+
"codemirror_mode": {
121+
"name": "ipython",
122+
"version": 3
123+
},
124+
"file_extension": ".py",
125+
"mimetype": "text/x-python",
126+
"name": "python",
127+
"nbconvert_exporter": "python",
128+
"pygments_lexer": "ipython3",
129+
"version": "3.10.5"
130+
}
131+
},
132+
"nbformat": 4,
133+
"nbformat_minor": 5
134+
}

xarray_leaflet/xarray_leaflet.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import asyncio
22
import os
33
import tempfile
4-
from typing import Optional
4+
from typing import Callable, Optional
55

66
import geopandas as gpd
77
import matplotlib as mpl
@@ -62,6 +62,7 @@ def plot(
6262
resampling=Resampling.nearest,
6363
get_base_url=None,
6464
measurement: Optional[str] = None,
65+
visible_callback: Optional[Callable] = None,
6566
):
6667
"""Display an array as an interactive map.
6768
@@ -114,6 +115,13 @@ def plot(
114115
A function taking the window URL and returning the base URL to use.
115116
measurement: str, optional
116117
The geocube measurement.
118+
visible_callback: callable, optional
119+
A callable taking the following arguments:
120+
- the ipyleaflet.Map
121+
- the xarray.DataArray of the visible region
122+
- the mercantile.LngLatBbox of the visible region
123+
124+
and returning True if the layer should be shown, False otherwise.
117125
118126
Returns
119127
-------
@@ -125,6 +133,7 @@ def plot(
125133

126134
if self.is_vector:
127135
# source is a GeoDataFrame (vector)
136+
self.visible_callback = visible_callback
128137
if measurement is None:
129138
raise RuntimeError("You must provide a 'measurement'.")
130139
self.measurement = measurement
@@ -365,6 +374,12 @@ def _get_vector_tiles(self, change=None):
365374
if self.dynamic:
366375
llbbox = mercantile.LngLatBbox(west, south, east, north)
367376
da_visible = self.zvect.get_da_llbbox(llbbox, z)
377+
# check if we must show the layer
378+
if self.visible_callback and not self.visible_callback(
379+
self.m, da_visible, llbbox
380+
):
381+
self.m.remove_control(self.spinner_control)
382+
return
368383
if da_visible is None:
369384
self.max_value = 0
370385
else:
@@ -600,7 +615,7 @@ async def async_fit_bounds(self):
600615
class DataArrayLeaflet(Leaflet):
601616
"""A DataArraye extension for tiled map plotting, based on (ipy)leaflet."""
602617

603-
def __init__(self, da: xr.DataArray = None):
618+
def __init__(self, da: xr.DataArray):
604619
self._da = da
605620
self._da_selected = None
606621
self.is_vector = False
@@ -610,6 +625,6 @@ def __init__(self, da: xr.DataArray = None):
610625
class GeoDataFrameLeaflet(Leaflet):
611626
"""A GeoDataFrame extension for tiled map plotting, based on (ipy)leaflet."""
612627

613-
def __init__(self, df: gpd.GeoDataFrame = None):
628+
def __init__(self, df: gpd.GeoDataFrame):
614629
self._df = df
615630
self.is_vector = True

0 commit comments

Comments
 (0)