jtmiclat / folium-pmtiles

🗺️ Folium Plugin to support PMTiles
https://pypi.org/project/folium-pmtiles/
MIT License
10 stars 4 forks source link

PMTilesMapLibreTooltip does not work with folium > 0.14.0 #16

Closed prusswan closed 11 months ago

prusswan commented 11 months ago

Very likely related to python-visualization/folium#1690 which "inspired" the example/fix below.

https://github.com/python-visualization/folium/pull/1690/commits/29429e8126b9ac719fdd5a244370b30fa6331a78 had a similar change by changing Layer to MacroElement

import folium

from folium_pmtiles.vector import PMTilesMapLibreLayer, PMTilesMapLibreTooltip

from folium.elements import JSCSSMixin
from folium.map import Layer
from jinja2 import Template
from branca.element import MacroElement

# not used, only included this for troubleshooting
class PMTilesMapLibreLayer2(JSCSSMixin, Layer):
    """Based of
    https://github.com/python-visualization/folium/blob/56d3665fdc9e7280eae1df1262450e53ec4f5a60/folium/plugins/vectorgrid_protobuf.py
    """

    _template = Template(
        """
            {% macro script(this, kwargs) -%}
            let protocol = new pmtiles.Protocol();
            maplibregl.addProtocol("pmtiles", protocol.tile);

           {{ this._parent.get_name() }}.createPane('overlay');
           {{ this._parent.get_name() }}.getPane('overlay').style.zIndex = 650;
           {{ this._parent.get_name() }}.getPane('overlay').style.pointerEvents = 'none';

            var {{ this.get_name() }} = L.maplibreGL({
            pane: 'overlay',
            style: {{ this.style|tojson}},
            interactive: true,
            }).addTo({{ this._parent.get_name() }});

            {%- endmacro %}
            """
    )
    default_css = [
        ("maplibre_css", "https://unpkg.com/maplibre-gl@2.2.1/dist/maplibre-gl.css")
    ]

    default_js = [
        ("pmtiles", "https://unpkg.com/pmtiles@2.5.0/dist/index.js"),
        ("maplibre-lib", "https://unpkg.com/maplibre-gl@2.2.1/dist/maplibre-gl.js"),
        (
            "maplibre-leaflet",
            "https://unpkg.com/@maplibre/maplibre-gl-leaflet@0.0.17/leaflet-maplibre-gl.js",
        ),
    ]

    def __init__(self, url, layer_name=None, style=None, tooltip=None, **kwargs):
        self.layer_name = layer_name if layer_name else "PMTilesVector"

        super().__init__(name=self.layer_name, **kwargs)

        self.url = url
        self._name = "PMTilesVector"
        if tooltip is not None:
            self.add_child(tooltip)
        if style is not None:
            self.style = style
        else:
            self.style = {}

#class PMTilesMapLibreTooltip2(JSCSSMixin, Layer):
class PMTilesMapLibreTooltip2(JSCSSMixin, MacroElement):
    _template = Template(
        """
            {% macro header(this, kwargs) %}
            <style>
            .maplibregl-popup {
                font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
                z-index: 651;
            }
            .feature-row{
                margin-bottom: 0.5em;
                &:not(:last-of-type) {
                    border-bottom: 1px solid black;
                }
            }
            </style>
            {% endmacro %}

            {% macro script(this, kwargs) -%}
                var {{ this.get_name() }} = {{ this._parent.get_name() }}.getMaplibreMap();
                const popup = new maplibregl.Popup({
                    closeButton: false,
                    closeOnClick: false
                });
                {{ this.get_name() }}.on('load', () => {
                    {{ this.get_name() }}.on('mousemove', (e) => { 
                        {{ this.get_name() }}.getCanvas().style.cursor = 'pointer';
                        const { x, y } = e.point;
                        const r = 2; // radius around the point
                        const features = {{ this.get_name() }}.queryRenderedFeatures([
                            [x - r, y - r],
                            [x + r, y + r],
                        ]);

                        const {lng, lat}  = e.lngLat;
                        const coordinates = [lng, lat]
                        const html = features.map(f=>`
                        <div class="feature-row">
                            <span>
                                <strong>${f.layer['source-layer']}</strong>
                                <span style="fontSize: 0.8em" }> (${f.geometry.type})</span>
                            </span>
                            <table>
                                ${Object.entries(f.properties).map(([key, value]) =>`<tr><td>${key}</td><td style="text-align: right">${value}</td></tr>`).join("")}
                            </table>
                        </div>
                        `).join("")
                        if(features.length){
                            popup.setLngLat(e.lngLat).setHTML(html).addTo({{ this.get_name() }});
                        } else {
                            popup.remove();
                        }
                    });
                    {{ this.get_name() }}.on('mouseleave', () => {popup.remove();});
                });
            {%- endmacro %}
            """
    )

    def __init__(self, name=None, **kwargs):
        #super().__init__(name=name if name else "PMTilesTooltip", **kwargs)
        super().__init__(**kwargs)

m = folium.Map(location=[43.7798, 11.24148], zoom_start=13, tiles="cartodb positron")

tooltip = PMTilesMapLibreTooltip2()
pmtiles_url = "https://pmtiles.jtmiclat.me/protomaps(vector)ODbL_firenze.pmtiles"
pmtiles_layer = PMTilesMapLibreLayer(
    "folium_layer_name",
    layer_name="pmtiles layer name ",
    style={
        "version": 8,
        "sources": {
            "example_source": {
                "type": "vector",
                "url": "pmtiles://" + pmtiles_url,
                "attribution": '<a href="https://protomaps.com">Protomaps</a> © <a href="https://openstreetmap.org/copyright">OpenStreetMap</a>',
            }
        },
        "layers": [
            {
                "id": "buildings",
                "source": "example_source",
                "source-layer": "landuse",
                "type": "fill",
                "paint": {"fill-color": "steelblue"},
            },
            {
                "id": "roads",
                "source": "example_source",
                "source-layer": "roads",
                "type": "line",
                "paint": {"line-color": "black"},
            },
        ],
    },
    tooltip=tooltip,
)

m.add_child(pmtiles_layer)
folium.LayerControl().add_to(m)
m
jtmiclat commented 11 months ago

@prusswan Thanks for the report! Merged your suggested fix and published a newer version as 0.4.2!

prusswan commented 11 months ago

@jtmiclat Turns out that there was more than one issue involving LayerControl (see opengeos/leafmap#645). I tried to get it to work with leafmap first, if the changes make sense, I can submit a pull request for this repo as well.

jtmiclat commented 11 months ago

@prusswan Feel free to make a new PR ! I'll review it when I get the chance