eodaGmbH / py-maplibregl

Python bindings for MapLibre GL JS
https://eodagmbh.github.io/py-maplibregl/
MIT License
27 stars 2 forks source link

Interleaving Data and Basemap Layers? #45

Closed diehl closed 1 month ago

diehl commented 1 month ago

Is it possible to use this library to interleave a data layer into the basemap such that place names and administrative boundaries at a minimum remain on top? To date, I've not found a Python-based solution to generating this type of visualization. Data layers always appear on top of the basemap, occluding the geospatial context, leaving one to resort to transparency as the only option for exposing the geospatial context below the data layer.

crazycapivara commented 1 month ago

Hi @diehl

Yes, this is possible. You can create a custom basemap, see this example for details. At a bare minimum you need to use a background layer:

from maplibre.ipywidget import MapWidget, MapOptions
from maplibre.basemaps import background, construct_basemap_style

m = MapWidget(MapOptions(style=background("yellow")))

Furthermore it is also possible to adjust any given basemap style. This example will change the water color to "yellow":

from maplibre.ipywidget import MapWidget, MapOptions
from maplibre.basemaps import Carto

m = MapWidget(MapOptions(style=Carto.DARK_MATTER_NOLABELS))
m.set_paint_property("water", "fill-color", "yellow")

grafik

diehl commented 1 month ago

@crazycapivara Hi Stefan, This is brilliant news!!

Let me add a little more color about what I'm trying to achieve in terms of workflow.

I would love to be able to design a custom map style either with Mapbox Studio or Maputnik and then utilize your library to interleave a data layer into the stack of layers defining the basemap. If this is possible, I'd appreciate pointers on how to load the style and then specify where in the basemap layer stack the data layer should go.

If it is helpful, I'd be more than happy to write up an example with your guidance that could be included in the documentation. This capability coupled with the option to use the library in Shiny applications is of immense value!!

crazycapivara commented 1 month ago

@diehl

I am not sure, why you need to add the data layers to the basemap but you can do it this way:

from maplibre.ipywidget import MapWidget as Map
from maplibre import MapOptions, Layer, LayerType
from maplibre.sources import GeoJSONSource
import requests

# Load style
style = requests.get('https://basemaps.cartocdn.com/gl/dark-matter-nolabels-gl-style/style.json').json()

# Add data source to style
style["sources"]["earthquakes"] = GeoJSONSource(data="https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson").to_dict()

# Add layer to style using the added source above
earthquakes_layer = Layer(type=LayerType.CIRCLE, id="earthquakes", source="earthquakes", paint={"circle-color": "yellow", "circle-radius": 5})
style["layers"].append(earthquakes_layer.to_dict())

# Use basemap
m = Map(style=style)

If this is a usecase, we can add a function to the package for this.

diehl commented 1 month ago

Hi @crazycapivara,

Let me try to clarify in case my terminology is causing confusion. All the existing Python geospatial visualization libraries that I'm aware of place the data layer(s) on top of the basemap. Therefore one cannot see state/country boundaries, place names, road networks etc. that provide geospatial context. What I'm looking for is the ability to slot the data layer(s) into the stack of layers that comprise the basemap such that state/country boundaries, place names and any other desired geospatial context remains on top and readily visible.

I'm assuming that

style["layers"].append(earthquakes_layer.to_dict())

will result in the earthquake layer being placed on top of all the other layers that define the basemap. If I wanted to insert it such that some layers remain on top, I assume that would be accomplished by adding earthquake_layer somewhere in the middle of that list. Is that correct?

Please let me know if that helps clarify the use case. Thank you again!

crazycapivara commented 1 month ago

@diehl

Ah, ok, yes you are right, you just need to insert it somewhere in the middle. But then for your use case, it might be easier if I add the before_id parameter in the add_layer function:

https://maplibre.org/maplibre-gl-js/docs/API/classes/Map/#addlayer

I just forgot to add the param, when implenting the binding function.

See also Example in JS.

diehl commented 1 month ago

@crazycapivara

Boom. That's exactly what is needed here. If you could add the before_id parameter into the mix, that would be brilliant!

crazycapivara commented 1 month ago

@diehl

This PR adds the needed parameter, just tested it, maybe you have a nice example that I can add to the docs.

diehl commented 1 month ago

Awesome @crazycapivara!

I've got an idea for a map. Can I get your email so we can discuss a possibility? Happy to help craft something.

crazycapivara commented 1 month ago

Just added an example based on the JS example above. @diehl feel free to add another example via PR or issue. Thanks for the great input.

diehl commented 1 month ago

My pleasure @crazycapivara - thank you!