diff --git a/docs/notebooks/82_pmtiles.ipynb b/docs/notebooks/82_pmtiles.ipynb index 2847574a34..20a3223b45 100644 --- a/docs/notebooks/82_pmtiles.ipynb +++ b/docs/notebooks/82_pmtiles.ipynb @@ -98,6 +98,8 @@ " ],\n", "}\n", "\n", + "# style = leafmap.pmtiles_style(url) # Use default style\n", + "\n", "m.add_pmtiles(\n", " url, name='PMTiles', style=style, overlay=True, show=True, zoom_to_layer=True\n", ")\n", @@ -184,6 +186,8 @@ " ],\n", "}\n", "\n", + "# style = leafmap.pmtiles_style(url) # Use default style\n", + "\n", "m.add_pmtiles(\n", " url, name='PMTiles', style=style, overlay=True, show=True, zoom_to_layer=True\n", ")\n", @@ -223,7 +227,9 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "m = leafmap.Map(center=[20, 0], zoom=2, height='800px')\n", @@ -250,6 +256,8 @@ " ],\n", "}\n", "\n", + "# style = leafmap.pmtiles_style(url) # Use default style\n", + "\n", "m.add_pmtiles(\n", " url, name='Buildings', style=style, overlay=True, show=True, zoom_to_layer=False\n", ")\n", @@ -384,6 +392,8 @@ " ],\n", "}\n", "\n", + "# style = leafmap.pmtiles_style(url) # Use default style\n", + "\n", "m.add_pmtiles(url, name='Buildings', show=True, zoom_to_layer=True, style=style)\n", "m" ] @@ -405,7 +415,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.11.5" } }, "nbformat": 4, diff --git a/examples/notebooks/82_pmtiles.ipynb b/examples/notebooks/82_pmtiles.ipynb index c4fe65eb64..20a3223b45 100644 --- a/examples/notebooks/82_pmtiles.ipynb +++ b/examples/notebooks/82_pmtiles.ipynb @@ -98,6 +98,8 @@ " ],\n", "}\n", "\n", + "# style = leafmap.pmtiles_style(url) # Use default style\n", + "\n", "m.add_pmtiles(\n", " url, name='PMTiles', style=style, overlay=True, show=True, zoom_to_layer=True\n", ")\n", @@ -184,6 +186,8 @@ " ],\n", "}\n", "\n", + "# style = leafmap.pmtiles_style(url) # Use default style\n", + "\n", "m.add_pmtiles(\n", " url, name='PMTiles', style=style, overlay=True, show=True, zoom_to_layer=True\n", ")\n", @@ -223,7 +227,9 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "m = leafmap.Map(center=[20, 0], zoom=2, height='800px')\n", @@ -250,6 +256,8 @@ " ],\n", "}\n", "\n", + "# style = leafmap.pmtiles_style(url) # Use default style\n", + "\n", "m.add_pmtiles(\n", " url, name='Buildings', style=style, overlay=True, show=True, zoom_to_layer=False\n", ")\n", @@ -384,6 +392,8 @@ " ],\n", "}\n", "\n", + "# style = leafmap.pmtiles_style(url) # Use default style\n", + "\n", "m.add_pmtiles(url, name='Buildings', show=True, zoom_to_layer=True, style=style)\n", "m" ] diff --git a/leafmap/common.py b/leafmap/common.py index 8a7ad047e8..0c5fdf2e86 100644 --- a/leafmap/common.py +++ b/leafmap/common.py @@ -11363,6 +11363,132 @@ def pmtiles_metadata(input_file: str) -> Dict[str, Union[str, int, List[str]]]: return metadata +def pmtiles_style( + url: str, + layers: Optional[Union[str, List[str]]] = None, + cmap: str = "Set3", + n_class: Optional[int] = None, + opacity: float = 0.5, + circle_radius: int = 5, + line_width: int = 1, + attribution: str = "PMTiles", + **kwargs, +): + """ + Generates a Mapbox style JSON for rendering PMTiles data. + + Args: + url (str): The URL of the PMTiles file. + layers (str or list[str], optional): The layers to include in the style. If None, all layers will be included. + Defaults to None. + cmap (str, optional): The color map to use for styling the layers. Defaults to "Set3". + n_class (int, optional): The number of classes to use for styling. If None, the number of classes will be + determined automatically based on the color map. Defaults to None. + opacity (float, optional): The fill opacity for polygon layers. Defaults to 0.5. + circle_radius (int, optional): The circle radius for point layers. Defaults to 5. + line_width (int, optional): The line width for line layers. Defaults to 1. + attribution (str, optional): The attribution text for the data source. Defaults to "PMTiles". + + Returns: + dict: The Mapbox style JSON. + + Raises: + ValueError: If the layers argument is not a string or a list. + ValueError: If a layer specified in the layers argument does not exist in the PMTiles file. + """ + + if cmap == "Set3": + palette = [ + "#8dd3c7", + "#ffffb3", + "#bebada", + "#fb8072", + "#80b1d3", + "#fdb462", + "#b3de69", + "#fccde5", + "#d9d9d9", + "#bc80bd", + "#ccebc5", + "#ffed6f", + ] + elif isinstance(cmap, list): + palette = cmap + else: + from .colormaps import get_palette + + palette = ["#" + c for c in get_palette(cmap, n_class)] + + n_class = len(palette) + + metadata = pmtiles_metadata(url) + layer_names = metadata["layer_names"] + + style = { + "version": 8, + "sources": { + "source": { + "type": "vector", + "url": "pmtiles://" + url, + "attribution": attribution, + } + }, + "layers": [], + } + + if layers is None: + layers = layer_names + elif isinstance(layers, str): + layers = [layers] + elif isinstance(layers, list): + for layer in layers: + if layer not in layer_names: + raise ValueError(f"Layer {layer} does not exist in the PMTiles file.") + else: + raise ValueError("The layers argument must be a string or a list.") + + for i, layer_name in enumerate(layers): + layer_point = { + "id": f"{layer_name}_point", + "source": "source", + "source-layer": layer_name, + "type": "circle", + "paint": { + "circle-color": palette[i % n_class], + "circle-radius": circle_radius, + }, + "filter": ["==", ["geometry-type"], "Point"], + } + + layer_stroke = { + "id": f"{layer_name}_stroke", + "source": "source", + "source-layer": layer_name, + "type": "line", + "paint": { + "line-color": palette[i % n_class], + "line-width": line_width, + }, + "filter": ["==", ["geometry-type"], "LineString"], + } + + layer_fill = { + "id": f"{layer_name}_fill", + "source": "source", + "source-layer": layer_name, + "type": "fill", + "paint": { + "fill-color": palette[i % n_class], + "fill-opacity": opacity, + }, + "filter": ["==", ["geometry-type"], "Polygon"], + } + + style["layers"].extend([layer_point, layer_stroke, layer_fill]) + + return style + + def raster_to_vector( source, output, simplify_tolerance=None, dst_crs=None, open_args={}, **kwargs ): diff --git a/leafmap/foliumap.py b/leafmap/foliumap.py index edf8df3303..d316065c13 100644 --- a/leafmap/foliumap.py +++ b/leafmap/foliumap.py @@ -243,6 +243,8 @@ def add_pmtiles( """ try: + if style is None: + style = pmtiles_style(url) layer = PMTilesLayer( url, style=style, diff --git a/leafmap/leafmap.py b/leafmap/leafmap.py index e7c044365c..f6619a8f84 100644 --- a/leafmap/leafmap.py +++ b/leafmap/leafmap.py @@ -597,7 +597,7 @@ def add_vector_tile( def add_pmtiles( self, url, - style={}, + style=None, name="PMTiles", show=True, zoom_to_layer=True, @@ -628,6 +628,9 @@ def add_pmtiles( if "version" in kwargs: del kwargs["version"] + if style is None: + style = pmtiles_style(url) + layer = ipyleaflet.PMTilesLayer( url=url, style=style,