Skip to content

Commit 8a500e7

Browse files
Interactive vector tile layers (#1213)
* Add renderer_factory option * Update docstring for interactive, default False * Optional get_feature_id * Send propagatedForm properties and options * Add set_feature_style and reset_feature_style * feature_style as traitlets property * setOpacity to 0 if VTL initialized with visible=False * Rename vector_tile_layer_styles to layer_styles in python * Rename renderer_factory to renderer in python * Rename get_feature_id to feature_id in python * backwards compatibility for vector_tile_layer_styles * Backwards compatiblity and update layer_styles --------- Co-authored-by: martinRenou <[email protected]>
1 parent 506ec04 commit 8a500e7

File tree

2 files changed

+140
-19
lines changed

2 files changed

+140
-19
lines changed

python/ipyleaflet/ipyleaflet/leaflet.py

+58-4
Original file line numberDiff line numberDiff line change
@@ -1096,7 +1096,7 @@ class VectorTileLayer(Layer):
10961096
Url to the vector tile service.
10971097
attribution: string, default ""
10981098
Vector tile service attribution.
1099-
vector_tile_layer_styles: dict or string, default {}. If string, it will be parsed as a javascript object (useful for defining styles that depend on properties and/or zoom).
1099+
layer_styles: dict or string, default {}. If string, it will be parsed as a javascript object (useful for defining styles that depend on properties and/or zoom).
11001100
CSS Styles to apply to the vector data.
11011101
min_zoom: int, default 0
11021102
The minimum zoom level down to which this layer will be displayed (inclusive).
@@ -1110,6 +1110,12 @@ class VectorTileLayer(Layer):
11101110
Opacity of the layer between 0. (fully transparent) and 1. (fully opaque).
11111111
visible: boolean, default True
11121112
Whether the layer is visible or not.
1113+
renderer: string, default 'svg'
1114+
Engine for rendering VectorTileLayers; either 'canvas' or 'svg'. Use 'svg' for interactive layers.
1115+
interactive: boolean, default False
1116+
Whether the layer is interactive or not.
1117+
feature_id: string, default None
1118+
Optional attribute name of a unique feature identifier.
11131119
"""
11141120

11151121
_view_name = Unicode("LeafletVectorTileLayerView").tag(sync=True)
@@ -1118,13 +1124,34 @@ class VectorTileLayer(Layer):
11181124
url = Unicode().tag(sync=True, o=True)
11191125
attribution = Unicode().tag(sync=True, o=True)
11201126

1121-
vector_tile_layer_styles = Union([Dict(), Unicode()]).tag(sync=True, o=True)
1122-
opacity = Float(1.0, min=0.0, max=1.0).tag(sync=True)
1123-
visible = Bool(True).tag(sync=True)
1127+
layer_styles = Union([Dict(), Unicode()]).tag(sync=True, o=True)
1128+
opacity = Float(1.0, min=0.0, max=1.0).tag(sync=True,o=True)
1129+
visible = Bool(True).tag(sync=True, o=True)
1130+
interactive = Bool(False).tag(sync=True, o=True)
11241131
min_zoom = Int(0).tag(sync=True, o=True)
11251132
max_zoom = Int(18).tag(sync=True, o=True)
11261133
min_native_zoom = Int(default_value=None, allow_none=True).tag(sync=True, o=True)
11271134
max_native_zoom = Int(default_value=None, allow_none=True).tag(sync=True, o=True)
1135+
renderer = Unicode('svg').tag(sync=True, o=True)
1136+
feature_id = Unicode(allow_none=True, default_value=None).tag(sync=True, o=True)
1137+
feature_style = Dict().tag(sync=True)
1138+
1139+
# Backwards compatibility: allow vector_tile_layer_styles as input:
1140+
@property
1141+
def vector_tile_layer_styles(self):
1142+
return self.layer_styles
1143+
1144+
@vector_tile_layer_styles.setter
1145+
def vector_tile_layer_styles(self, value):
1146+
self.layer_styles = value
1147+
1148+
def __init__(self, **kwargs):
1149+
super(VectorTileLayer, self).__init__(**kwargs)
1150+
# Backwards compatibility: allow vector_tile_layer_styles as input:
1151+
if "vector_tile_layer_styles" in kwargs:
1152+
vtl_style = kwargs["vector_tile_layer_styles"]
1153+
if(vtl_style):
1154+
self.layer_styles = vtl_style
11281155

11291156
def redraw(self):
11301157
"""Force redrawing the tiles.
@@ -1134,6 +1161,33 @@ def redraw(self):
11341161
"""
11351162
self.send({"msg": "redraw"})
11361163

1164+
def set_feature_style(self, id:Int, layer_style:Dict):
1165+
"""Re-symbolize one feature.
1166+
1167+
Given the unique ID for a vector features, re-symbolizes that feature across all tiles it appears in.
1168+
Reverts the effects of a previous set_feature_style call. get_feature_id must be defined for
1169+
set_feature_style to work.
1170+
1171+
Attributes
1172+
----------
1173+
id: int
1174+
The unique identifier for the feature to re-symbolize
1175+
layer_styles: dict
1176+
Style to apply to the feature
1177+
"""
1178+
self.feature_style = {"id": id, "layerStyle": layer_style, "reset": False}
1179+
1180+
def reset_feature_style(self, id:Int):
1181+
"""Reset feature style
1182+
1183+
Reverts the style to the layer's deafult.
1184+
1185+
Attributes
1186+
----------
1187+
id: int
1188+
The unique identifier for the feature to re-symbolize
1189+
"""
1190+
self.feature_style = {"id": id, "reset": True}
11371191

11381192
class PMTilesLayer(Layer):
11391193
"""PMTilesLayer class, with Layer as parent class.

python/jupyter_leaflet/src/layers/VectorTileLayer.ts

+82-15
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Jupyter Development Team.
22
// Distributed under the terms of the Modified BSD License.
33

4-
import { VectorGrid } from 'leaflet';
4+
import { LeafletMouseEvent, VectorGrid } from 'leaflet';
55
import L from '../leaflet';
66
import { LeafletLayerModel, LeafletLayerView } from './Layer';
77

@@ -17,40 +17,107 @@ export class LeafletVectorTileLayerModel extends LeafletLayerModel {
1717
max_zoom: 18,
1818
min_native_zoom: null,
1919
max_native_zoom: null,
20-
interactive: true,
20+
interactive: false,
2121
visible: true,
2222
opacity: 1.0,
23+
rendererFactory: L.svg.tile,
24+
getFeatureId: null,
2325
};
2426
}
2527
}
2628

2729
export class LeafletVectorTileLayerView extends LeafletLayerView {
2830
obj: VectorGrid.Protobuf;
2931

32+
async set_vector_tile_layer_styles(options: any) {
33+
if ('layerStyles' in options) {
34+
let x: any = options['layerStyles'];
35+
options['vectorTileLayerStyles'] = x;
36+
if (typeof x === 'string') {
37+
try {
38+
let blobCode = `const jsStyle=${x}; export { jsStyle };`;
39+
40+
const blob = new Blob([blobCode], { type: 'text/javascript' });
41+
const url = URL.createObjectURL(blob);
42+
const module = await import(/* webpackIgnore: true*/ url);
43+
const jsStyle = module.jsStyle;
44+
45+
options['vectorTileLayerStyles'] = jsStyle;
46+
} catch (error) {
47+
options['vectorTileLayerStyles'] = {};
48+
}
49+
}
50+
}
51+
return options;
52+
}
53+
3054
async create_obj() {
3155
let options = {
3256
...this.get_options(),
3357
};
34-
options['rendererFactory'] = L.canvas.tile;
3558

36-
let x: any = options['vectorTileLayerStyles'];
37-
if (typeof x === 'string') {
38-
try {
39-
let blobCode = `const jsStyle=${x}; export { jsStyle };`;
40-
41-
const blob = new Blob([blobCode], { type: 'text/javascript' });
42-
const url = URL.createObjectURL(blob);
43-
const module = await import(/* webpackIgnore: true*/ url);
44-
const jsStyle = module.jsStyle;
59+
if ('featureId' in options) {
60+
let idVar = options['featureId'];
61+
options['getFeatureId'] = function (feat: any) {
62+
return feat.properties[idVar];
63+
};
64+
}
4565

46-
options['vectorTileLayerStyles'] = jsStyle;
47-
} catch (error) {
48-
options['vectorTileLayerStyles'] = {};
66+
if ('renderer' in options) {
67+
let r: any = options['renderer'];
68+
if (r === 'canvas') {
69+
options['rendererFactory'] = L.canvas.tile;
70+
} else {
71+
options['rendererFactory'] = L.svg.tile;
4972
}
5073
}
5174

75+
options = await this.set_vector_tile_layer_styles(options);
76+
5277
this.obj = L.vectorGrid.protobuf(this.model.get('url'), options);
5378
this.model.on('msg:custom', this.handle_message.bind(this));
79+
80+
if (this.model.get('visible') == false) {
81+
this.obj.setOpacity(0);
82+
}
83+
84+
this.model.on('change:layer_styles', async () => {
85+
let options = {
86+
...this.get_options(),
87+
};
88+
options = await this.set_vector_tile_layer_styles(options);
89+
this.obj.options.vectorTileLayerStyles = options['vectorTileLayerStyles'];
90+
if (this.model.get('visible') == false) {
91+
this.obj.setOpacity(0);
92+
}
93+
this.obj.redraw();
94+
});
95+
96+
this.model.on('change:feature_style', () => {
97+
const feature_style = this.model.get('feature_style');
98+
const reset = feature_style['reset'];
99+
if (reset) {
100+
this.obj.resetFeatureStyle(feature_style['id']);
101+
} else {
102+
this.obj.setFeatureStyle(
103+
feature_style['id'],
104+
feature_style['layerStyle']
105+
);
106+
}
107+
});
108+
109+
this.obj.on(
110+
'click mouseover mouseout' as any,
111+
(event: LeafletMouseEvent) => {
112+
this.send({
113+
event: 'interaction',
114+
type: event.type,
115+
coordinates: [event.latlng.lat, event.latlng.lng],
116+
properties: event.propagatedFrom.properties,
117+
options: event.propagatedFrom.options,
118+
});
119+
}
120+
);
54121
}
55122

56123
model_events() {

0 commit comments

Comments
 (0)