Skip to content

Commit

Permalink
Add tooltip for PMTiles (#613)
Browse files Browse the repository at this point in the history
  • Loading branch information
giswqs authored Nov 20, 2023
1 parent c2b7a2a commit 865fd37
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 5 deletions.
42 changes: 41 additions & 1 deletion leafmap/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def view_vector(
**kwargs: Additional keyword arguments that will be passed to lonboard.Layer.from_geopandas()
Returns:
None
lonboard.Map: A lonboard Map object.
"""
from .deckgl import Map

Expand All @@ -83,6 +83,46 @@ def view_vector(
return m


def view_pmtiles(
url,
style=None,
name=None,
tooltip=True,
overlay=True,
control=True,
show=True,
zoom_to_layer=True,
map_args={},
**kwargs,
):
"""
Visualize PMTiles the map.
Args:
url (str): The URL of the PMTiles file.
style (str, optional): The CSS style to apply to the layer. Defaults to None.
See https://docs.mapbox.com/style-spec/reference/layers/ for more info.
name (str, optional): The name of the layer. Defaults to None.
tooltip (bool, optional): Whether to show a tooltip when hovering over the layer. Defaults to True.
overlay (bool, optional): Whether the layer should be added as an overlay. Defaults to True.
control (bool, optional): Whether to include the layer in the layer control. Defaults to True.
show (bool, optional): Whether the layer should be shown initially. Defaults to True.
zoom_to_layer (bool, optional): Whether to zoom to the layer extent. Defaults to True.
**kwargs: Additional keyword arguments to pass to the PMTilesLayer constructor.
Returns:
folium.Map: A folium Map object.
"""

from .foliumap import Map

m = Map(**map_args)
m.add_pmtiles(
url, style, name, tooltip, overlay, control, show, zoom_to_layer, **kwargs
)
return m


if _use_folium():
from .foliumap import *
else:
Expand Down
32 changes: 32 additions & 0 deletions leafmap/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,38 @@ def show_html(html: str):
raise Exception(e)


def display_html(
html: Union[str, bytes], width: str = "100%", height: int = 500
) -> None:
"""
Displays an HTML file or HTML string in a Jupyter Notebook.
Args:
html (Union[str, bytes]): Path to an HTML file or an HTML string.
width (str, optional): Width of the displayed iframe. Default is '100%'.
height (int, optional): Height of the displayed iframe. Default is 500.
Returns:
None
"""
from IPython.display import IFrame, display

if isinstance(html, str) and html.startswith("<"):
# If the input is an HTML string
html_content = html
elif isinstance(html, str):
# If the input is a file path
with open(html, "r") as file:
html_content = file.read()
elif isinstance(html, bytes):
# If the input is a byte string
html_content = html.decode("utf-8")
else:
raise ValueError("Invalid input type. Expected a file path or an HTML string.")

display(IFrame(srcdoc=html_content, width=width, height=height))


def has_transparency(img) -> bool:
"""Checks whether an image has transparency.
Expand Down
82 changes: 78 additions & 4 deletions leafmap/foliumap.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ def add_pmtiles(
url,
style=None,
name=None,
tooltip=True,
overlay=True,
control=True,
show=True,
Expand All @@ -230,6 +231,7 @@ def add_pmtiles(
style (str, optional): The CSS style to apply to the layer. Defaults to None.
See https://docs.mapbox.com/style-spec/reference/layers/ for more info.
name (str, optional): The name of the layer. Defaults to None.
tooltip (bool, optional): Whether to show a tooltip when hovering over the layer. Defaults to True.
overlay (bool, optional): Whether the layer should be added as an overlay. Defaults to True.
control (bool, optional): Whether to include the layer in the layer control. Defaults to True.
show (bool, optional): Whether the layer should be shown initially. Defaults to True.
Expand All @@ -245,6 +247,7 @@ def add_pmtiles(
url,
style=style,
name=name,
tooltip=tooltip,
overlay=overlay,
control=control,
show=show,
Expand Down Expand Up @@ -417,7 +420,7 @@ def add_wms_layer(
).add_to(self)
except Exception as e:
raise Exception(e)

def add_wms_legend(
self,
url,
Expand All @@ -428,7 +431,7 @@ def add_wms_legend(
url (str): URL of the WMS legend image. Should have this format if using wms legend: {geoserver}/wms?REQUEST=GetLegendGraphic&FORMAT=image/png&LAYER={layer}
"""
from branca.element import Figure, MacroElement, Element

# Check if the map is a Folium Map instance
if not isinstance(self, Map):
raise ValueError("The self argument must be an instance of folium.Map.")
Expand All @@ -446,7 +449,7 @@ def add_wms_legend(
</div>
{{% endmacro %}}
"""

# Create an Element with the HTML and add it to the map
macro = MacroElement()
macro._template = Template(legend_html)
Expand Down Expand Up @@ -3724,7 +3727,8 @@ class PMTilesLayer(JSCSSMixin, Layer):
var {{ this.get_name() }} = L.maplibreGL({
pane: 'overlay',
style: {{ this.style|tojson}}
style: {{ this.style|tojson}},
interactive: true,
}).addTo({{ this._parent.get_name() }});
{%- endmacro %}
Expand All @@ -3748,6 +3752,7 @@ def __init__(
url,
style=None,
name=None,
tooltip=True,
overlay=True,
show=True,
control=True,
Expand All @@ -3760,6 +3765,7 @@ def __init__(
url (str): The URL of the PMTiles file.
style (dict, optional): The style to apply to the layer. Defaults to None.
name (str, optional): The name of the layer. Defaults to None.
tooltip (bool, optional): Whether to show a tooltip. Defaults to True.
overlay (bool, optional): Whether the layer should be added as an overlay. Defaults to True.
show (bool, optional): Whether the layer should be shown initially. Defaults to True.
control (bool, optional): Whether to include the layer in the layer control. Defaults to True.
Expand All @@ -3778,7 +3784,75 @@ def __init__(
self.url = url
self._name = "PMTilesVector"

if tooltip:
self.add_child(PMTilesMapLibreTooltip())

if style is not None:
self.style = style
else:
self.style = {}


class PMTilesMapLibreTooltip(JSCSSMixin, Layer):
"""Creates a PMTilesMapLibreTooltip object for displaying tooltips.
Adapted from https://github.com/jtmiclat/folium-pmtiles. Credits to @jtmiclat.
"""

_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)

0 comments on commit 865fd37

Please sign in to comment.