Skip to content

Commit 7186116

Browse files
authored
Add Vector Tiles support (#544)
1 parent ea75f29 commit 7186116

File tree

8 files changed

+423
-14
lines changed

8 files changed

+423
-14
lines changed
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": null,
6+
"metadata": {},
7+
"outputs": [],
8+
"source": [
9+
"from ipyleaflet import Map, VectorTileLayer, TileLayer"
10+
]
11+
},
12+
{
13+
"cell_type": "code",
14+
"execution_count": null,
15+
"metadata": {},
16+
"outputs": [],
17+
"source": [
18+
"from traitlets import Unicode, Dict\n",
19+
"\n",
20+
"class CustomVectorTileLayer(VectorTileLayer):\n",
21+
" api_key = Unicode('gCZXZglvRQa6sB2z7JzL1w').tag(sync=True, o=True)\n",
22+
" attribution = Unicode('<a href=\"https://nextzen.com/\">&copy; NextZen</a>, <a href=\"http://www.openstreetmap.org/copyright\">&copy; OpenStreetMap</a> contributors').tag(sync=True, o=True)\n"
23+
]
24+
},
25+
{
26+
"cell_type": "code",
27+
"execution_count": null,
28+
"metadata": {},
29+
"outputs": [],
30+
"source": [
31+
"water_style = dict(\n",
32+
" fill=\"true\",\n",
33+
" weight=1,\n",
34+
" fillColor=\"#06cccc\",\n",
35+
" color=\"#06cccc\",\n",
36+
" fillOpacity=0.2,\n",
37+
" opacity=0.4,\n",
38+
")\n",
39+
"\n",
40+
"waterway_style = dict(\n",
41+
" weight=1, fillColor=\"#2375e0\", color=\"#2375e0\", fillOpacity=0.2, opacity=0.4\n",
42+
")\n",
43+
"\n",
44+
"admin_style = dict(\n",
45+
" weight=1, fillColor=\"pink\", color=\"pink\", fillOpacity=0.2, opacity=0.4\n",
46+
")\n",
47+
"\n",
48+
"landcover_style = dict(\n",
49+
" fill=\"true\",\n",
50+
" weight=1,\n",
51+
" fillColor=\"#53e033\",\n",
52+
" color=\"#53e033\",\n",
53+
" fillOpacity=0.2,\n",
54+
" opacity=0.4,\n",
55+
")\n",
56+
"\n",
57+
"landuse_style = dict(\n",
58+
" fill=\"true\",\n",
59+
" weight=1,\n",
60+
" fillColor=\"#e5b404\",\n",
61+
" color=\"#e5b404\",\n",
62+
" fillOpacity=0.2,\n",
63+
" opacity=0.4,\n",
64+
")\n",
65+
"\n",
66+
"park_style = dict(\n",
67+
" fill=\"true\",\n",
68+
" weight=1,\n",
69+
" fillColor=\"#84ea5b\",\n",
70+
" color=\"#84ea5b\",\n",
71+
" fillOpacity=0.2,\n",
72+
" opacity=0.4,\n",
73+
")\n",
74+
"\n",
75+
"boundary_style = dict(\n",
76+
" weight=1, fillColor=\"#c545d3\", color=\"#c545d3\", fillOpacity=0.2, opacity=0.4\n",
77+
")\n",
78+
"\n",
79+
"\n",
80+
"aeroway = dict(\n",
81+
" weight=1, fillColor=\"#51aeb5\", color=\"#51aeb5\", fillOpacity=0.2, opacity=0.4\n",
82+
")\n",
83+
"road = dict(\n",
84+
" weight=1, fillColor=\"#f2b648\", color=\"#f2b648\", fillOpacity=0.2, opacity=0.4\n",
85+
")\n",
86+
"transit = dict(\n",
87+
" weight=0.5, fillColor=\"#f2b648\", color=\"#f2b648\", fillOpacity=0.2, opacity=0.4\n",
88+
")\n",
89+
"buildings = dict(\n",
90+
" fill=\"true\",\n",
91+
" weight=1,\n",
92+
" fillColor=\"#2b2b2b\",\n",
93+
" color=\"#2b2b2b\",\n",
94+
" fillOpacity=0.2,\n",
95+
" opacity=0.4,\n",
96+
")\n",
97+
"water_name = dict(\n",
98+
" weight=1, fillColor=\"#022c5b\", color=\"#022c5b\", fillOpacity=0.2, opacity=0.4\n",
99+
")\n",
100+
"transportation_name = dict(\n",
101+
" weight=1, fillColor=\"#bc6b38\", color=\"#bc6b38\", fillOpacity=0.2, opacity=0.4\n",
102+
")\n",
103+
"place = dict(\n",
104+
" weight=1, fillColor=\"#f20e93\", color=\"#f20e93\", fillOpacity=0.2, opacity=0.4\n",
105+
")\n",
106+
"housenumber = dict(\n",
107+
" weight=1, fillColor=\"#ef4c8b\", color=\"#ef4c8b\", fillOpacity=0.2, opacity=0.4\n",
108+
")\n",
109+
"poi = dict(weight=1, fillColor=\"#3bb50a\", color=\"#3bb50a\", fillOpacity=0.2, opacity=0.4)\n",
110+
"earth = dict(\n",
111+
" fill=\"true\",\n",
112+
" weight=1,\n",
113+
" fillColor=\"#c0c0c0\",\n",
114+
" color=\"#c0c0c0\",\n",
115+
" fillOpacity=0.2,\n",
116+
" opacity=0.4,\n",
117+
")\n"
118+
]
119+
},
120+
{
121+
"cell_type": "code",
122+
"execution_count": null,
123+
"metadata": {},
124+
"outputs": [],
125+
"source": [
126+
"url = 'https://tile.nextzen.org/tilezen/vector/v1/512/all/{z}/{x}/{y}.mvt?api_key={apiKey}'\n",
127+
"vectorTileLayerStyles=dict(water=water_style,\n",
128+
" waterway=waterway_style,\n",
129+
" admin=admin_style,\n",
130+
" andcover=landcover_style,\n",
131+
" landuse=landuse_style,\n",
132+
" park=park_style,\n",
133+
" boundaries=boundary_style,\n",
134+
" aeroway=aeroway,\n",
135+
" roads=road,\n",
136+
" transit=transit,\n",
137+
" buildings=buildings,\n",
138+
" water_name=water_name,\n",
139+
" transportation_name=transportation_name,\n",
140+
" places=place,\n",
141+
" housenumber=housenumber,\n",
142+
" pois=poi,\n",
143+
" earth=earth,)\n",
144+
"m = Map(center=(52.204793, 360.121558), zoom=9)\n",
145+
"vl = CustomVectorTileLayer(url=url, vector_tile_layer_styles=vectorTileLayerStyles)\n",
146+
"m.add_layer(vl)\n",
147+
"m"
148+
]
149+
},
150+
{
151+
"cell_type": "code",
152+
"execution_count": null,
153+
"metadata": {},
154+
"outputs": [],
155+
"source": [
156+
"m.zoom=3.58"
157+
]
158+
},
159+
{
160+
"cell_type": "code",
161+
"execution_count": null,
162+
"metadata": {},
163+
"outputs": [],
164+
"source": [
165+
"vl.url = \"https://basemaps.arcgis.com/v1/arcgis/rest/services/World_Basemap/VectorTileServer/tile/{z}/{y}/{x}.pbf\""
166+
]
167+
},
168+
{
169+
"cell_type": "code",
170+
"execution_count": null,
171+
"metadata": {},
172+
"outputs": [],
173+
"source": [
174+
"m.remove_layer(m.layers[0])"
175+
]
176+
}
177+
],
178+
"metadata": {
179+
"kernelspec": {
180+
"display_name": "Python 3",
181+
"language": "python",
182+
"name": "python3"
183+
},
184+
"language_info": {
185+
"codemirror_mode": {
186+
"name": "ipython",
187+
"version": 3
188+
},
189+
"file_extension": ".py",
190+
"mimetype": "text/x-python",
191+
"name": "python",
192+
"nbconvert_exporter": "python",
193+
"pygments_lexer": "ipython3",
194+
"version": "3.7.3"
195+
}
196+
},
197+
"nbformat": 4,
198+
"nbformat_minor": 4
199+
}

ipyleaflet/leaflet.py

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,9 @@ class AwesomeIcon(UILayer):
170170

171171
name = Unicode('home').tag(sync=True)
172172
marker_color = Enum(
173-
values=['white', 'red', 'darkred', 'lightred', 'orange', 'beige', 'green', 'darkgreen', 'lightgreen', 'blue', 'darkblue', 'lightblue', 'purple', 'darkpurple', 'pink', 'cadetblue', 'white', 'gray', 'lightgray', 'black'],
173+
values=['white', 'red', 'darkred', 'lightred', 'orange', 'beige', 'green', 'darkgreen', 'lightgreen', 'blue',
174+
'darkblue', 'lightblue', 'purple', 'darkpurple', 'pink', 'cadetblue', 'white', 'gray', 'lightgray',
175+
'black'],
174176
default_value='blue'
175177
).tag(sync=True)
176178
icon_color = Color('white').tag(sync=True)
@@ -184,7 +186,8 @@ class Marker(UILayer):
184186
location = List(def_loc).tag(sync=True)
185187
opacity = Float(1.0, min=0.0, max=1.0).tag(sync=True)
186188
visible = Bool(True).tag(sync=True)
187-
icon = Union((Instance(Icon), Instance(AwesomeIcon)), allow_none=True, default_value=None).tag(sync=True, **widget_serialization)
189+
icon = Union((Instance(Icon), Instance(AwesomeIcon)), allow_none=True, default_value=None).tag(sync=True,
190+
**widget_serialization)
188191

189192
# Options
190193
z_index_offset = Int(0).tag(sync=True, o=True)
@@ -254,7 +257,8 @@ class TileLayer(RasterLayer):
254257
min_native_zoom = Int(0).tag(sync=True, o=True)
255258
max_native_zoom = Int(18).tag(sync=True, o=True)
256259
tile_size = Int(256).tag(sync=True, o=True)
257-
attribution = Unicode('Map data (c) <a href="https://openstreetmap.org">OpenStreetMap</a> contributors').tag(sync=True, o=True)
260+
attribution = Unicode('Map data (c) <a href="https://openstreetmap.org">OpenStreetMap</a> contributors').tag(
261+
sync=True, o=True)
258262
detect_retina = Bool(False).tag(sync=True, o=True)
259263
no_wrap = Bool(False).tag(sync=True, o=True)
260264
tms = Bool(False).tag(sync=True, o=True)
@@ -384,6 +388,28 @@ class Heatmap(RasterLayer):
384388
gradient = Dict({0.4: 'blue', 0.6: 'cyan', 0.7: 'lime', 0.8: 'yellow', 1.0: 'red'}).tag(sync=True, o=True)
385389

386390

391+
class VectorTileLayer(Layer):
392+
_view_name = Unicode('LeafletVectorTileLayerView').tag(sync=True)
393+
_model_name = Unicode('LeafletVectorTileLayerModel').tag(sync=True)
394+
395+
url = Unicode().tag(sync=True, o=True)
396+
vector_tile_layer_styles = Dict().tag(sync=True, o=True)
397+
398+
def __init__(self, **kwargs):
399+
super(VectorTileLayer, self).__init__(**kwargs)
400+
self.on_msg(self._handle_leaflet_event)
401+
402+
def _handle_leaflet_event(self, _, content, buffers):
403+
if content.get('event', '') == 'load':
404+
self._load_callbacks(**content)
405+
406+
def on_load(self, callback, remove=False):
407+
self._load_callbacks.register_callback(callback, remove=remove)
408+
409+
def redraw(self):
410+
self.send({'msg': 'redraw'})
411+
412+
387413
class VectorLayer(Layer):
388414
_view_name = Unicode('LeafletVectorLayerView').tag(sync=True)
389415
_model_name = Unicode('LeafletVectorLayerModel').tag(sync=True)
@@ -650,7 +676,8 @@ def _get_data(self):
650676
data = copy.deepcopy(self.geo_data)
651677

652678
for feature in data['features']:
653-
feature['properties']['style'] = self.style_callback(feature, colormap, self.choro_data[feature[self.key_on]])
679+
feature['properties']['style'] = self.style_callback(feature, colormap,
680+
self.choro_data[feature[self.key_on]])
654681

655682
return data
656683

@@ -914,10 +941,10 @@ class Map(DOMWidget, InteractMixin):
914941

915942
# Map options
916943
center = List(def_loc).tag(sync=True, o=True)
917-
zoom_start = Int(12).tag(sync=True, o=True)
918-
zoom = Int(12).tag(sync=True, o=True)
919-
max_zoom = Int(18).tag(sync=True, o=True)
920-
min_zoom = Int(1).tag(sync=True, o=True)
944+
zoom_start = CFloat(12).tag(sync=True, o=True)
945+
zoom = CFloat(12).tag(sync=True, o=True)
946+
max_zoom = CFloat(18).tag(sync=True, o=True)
947+
min_zoom = CFloat(1).tag(sync=True, o=True)
921948
interpolation = Unicode('bilinear').tag(sync=True, o=True)
922949
crs = Enum(values=allowed_crs, default_value='EPSG3857').tag(sync=True)
923950

@@ -981,11 +1008,12 @@ def _default_options(self):
9811008

9821009
@default('layers')
9831010
def _default_layers(self):
984-
basemap = self.basemap if isinstance(self.basemap, TileLayer) else basemap_to_tiles(self.basemap, self.modisdate)
1011+
basemap = self.basemap if isinstance(self.basemap, TileLayer) else basemap_to_tiles(self.basemap,
1012+
self.modisdate)
9851013

9861014
basemap.base = True
9871015

988-
return (basemap, )
1016+
return (basemap,)
9891017

9901018
bounds = Tuple(read_only=True)
9911019
bounds_polygon = Tuple(read_only=True)

0 commit comments

Comments
 (0)