ipyleaflet icon indicating copy to clipboard operation
ipyleaflet copied to clipboard

Interactive vector tile layers

Open lopezvoliver opened this issue 1 year ago • 0 comments

Renderer factory

Vector tile layers can be rendered using either L.canvas.tile or L.svg.tile. Currently, only the canvas option is implemented in ipyleaflet and it's not doing a great job at higher levels of zoom, see for example #1095.

Here a new renderer_factory option was added to VectorTileLayer, which can be either svg or canvas, with the new default being svg.

Here's an example using the ms-buildings from Microsoft Planetary Computer, with the new default (svg):

import ipyleaflet
m = ipyleaflet.Map(center=(41.91867,-88.10602), zoom=15)
url = 'https://planetarycomputer.microsoft.com/api/data/v1/vector/collections/ms-buildings/tilesets/global-footprints/tiles/{z}/{x}/{y}'
layer = ipyleaflet.VectorTileLayer(
    url=url, 
    attribution='Microsoft', 
    max_native_zoom=13,
    max_zoom=20,
    vector_tile_layer_styles={"bingmlbuildings":{
        "fill":True,
        "weight":0.5
    }},    
)
m.add(layer)
m

And here's how it renders using renderer_factory='canvas':

Interactivity

A new interactive option was added with False as default, which enables the user to add listeners to the layer, which include information about the feature. Note that the default renderer_factory='svg' option should be used for interactivity. For example:

m = ipyleaflet.Map()
layer = ipyleaflet.VectorTileLayer(
    url=url_data,  # My vector tile layer 
    interactive=True,  # New interactive option 
    max_native_zoom=13,
    max_zoom=20,
    renderer_factory='svg', # New renderer_factory option. Defaults to 'svg'
    vector_tile_layer_styles=jStyles,   # javascript function given as a string
    get_feature_id = 'label', # New get_feature_id option. Here, 'label' is the name of the (numeric) 
    # attribute in my layer that uniquely identifies each feature (see below for more information)
)
m.add(layer)

def handle_click(**kwargs):    
    if ("properties" in kwargs):
        properties = kwargs["properties"]
        options = kwargs["options"]
        print(properties)
        print(options)

layer.on_click(handle_click)  

m

get_feature_id

This is an optional attribute that is used to construct a simple javascript function to uniquely identify a feature. This is required if you will be updating feature styles through the new set_feature_style and reset_feature_style methods. The javascript function is of the form:

function (feat: any) {return feat.properties[idVar];}

where feat is the feature, and idVar is the name of the (numeric) attribute in the layer to identify a feature. Note that features with the same id will be treated as one when changing style (see the original getFeatureId documentation here).

Updating styles

Two new methods for VectorTileLayer were added: set_feature_style and reset_feature_style. The first one is used to update the style for an individual feature, which is useful for highlighting a feature (e.g., on click or mouseover), while the second one is useful for resetting the style to the default (e.g. to clear the highlight).

Example

Here's a motivating example that demonstrates all of the new features.

m = ipyleaflet.Map()
layer = ipyleaflet.VectorTileLayer(
    url=url_data,  # My vector tile layer 
    interactive=True,  # New interactive option 
    max_native_zoom=13,
    max_zoom=20,
    renderer_factory='svg', # New renderer_factory option. Defaults to 'svg'
    vector_tile_layer_styles=jStyles,   # javascript function given as a string
    get_feature_id = 'label', # New get_feature_id option. Here, 'label' is the name of the (numeric) 
    # attribute in my layer that uniquely identifies each feature.
)
m.add(layer)
layer._selected = None  


info_title = "<h4>Field info</h4>"
info_default_value = "Hover over a field" 

info_widget = widgets.HTML(value=info_title + info_default_value)

highlight_style = {
                "weight": 5,
                "color": '#666',
                "dashArray": '',
                "fillOpacity": 0.7,
                "fill": True 
                }

def highlight_feature(**kwargs):    
    if ("properties" in kwargs):
        properties = kwargs["properties"]
        options = kwargs["options"]
        feature_id = properties["label"]
        fill_color = options["fillColor"]
        highlight_style["fillColor"] = fill_color
        layer._selected = feature_id
        layer.set_feature_style(
            id=feature_id,
            layer_style=highlight_style,
        )
        info_html = info_title
        for k,v in properties.items():
            info_html += "<b>"+ k + "</b>" + ": " + str(v) + "<br />"
        info_widget.value = info_html

def clear_highlight(**kwargs):
    if layer._selected: 
        layer.reset_feature_style(layer._selected)
        info_widget.value = info_title + info_default_value
        
layer.on_mouseover(highlight_feature)
layer.on_mouseout(clear_highlight)

widget_control = ipyleaflet.WidgetControl(widget=info_widget, position='topright')
m.add(widget_control)

m

lopezvoliver avatar Jun 28 '24 17:06 lopezvoliver