vega / altair_tiles

https://vega.github.io/altair_tiles/
BSD 3-Clause "New" or "Revised" License
10 stars 1 forks source link

enable options to select a layer from a provider #35

Open mattijn opened 6 months ago

mattijn commented 6 months ago

Feature request! I'm trying to use altair_tiles within a coastal project for monitoring. I've multiple satellite images of this location that are available as wmts-links. Currently it is easy to add a single tile-layer, but could this become more dynamic so multiple layers of a provider can be selected as parameter?

An example. The following works

import geopandas as gpd
import altair as alt
import altair_tiles as til
import xyzservices as xyz

features = [{"type": "Feature", "properties": {}, "geometry": {"type": "Polygon", "coordinates": [[[4.9794, 53.2789], [4.9794, 53.2701], [4.9717, 53.2657], [4.9717, 53.2613], [4.9485, 53.2613], [4.9563, 53.27014], [4.9794, 53.2789]]]}}]
gdf = gpd.GeoDataFrame.from_features(features)
geoshape = alt.Chart(gdf).mark_geoshape(filled=False, stroke='white', strokeWidth=3)

wmts_url = "https://tiles1.geoserve.eu/Pleiades-NEO_NRG/tileserver/20231229_110351_PNEO-03_1_62_30cm_RD_8bit_NRG_Vlieland/{z}/{x}/{y}"
sdp_provider = xyz.TileProvider(name="sdp", url=wmts_url, attribution="(C) geoserve")
chart = til.create_tiles_chart(provider=sdp_provider, zoom=17) + geoshape
chart

image

But now I like to add more options for the tile provider, like something as such:

wmts_urls = [
    "https://tiles1.geoserve.eu/Pleiades-NEO_NRG/tileserver/20231229_110351_PNEO-03_1_62_30cm_RD_8bit_NRG_Vlieland/",
    "https://tiles1.geoserve.eu/Pleiades-NEO_NRG/tileserver/20230905_105214_PNEO-03_1_9_30cm_RD_8bit_NRG_Waddenzee/",
    "https://tiles1.geoserve.eu/Pleiades-NEO_NRG/tileserver/20230815_104917_PNEO-03_1_1_30cm_RD_8bit_NRG_Vlieland/",
    "https://tiles1.geoserve.eu/Pleiades-NEO_NRG/tileserver/20230813_110015_PNEO-04_1_1_30cm_RD_8bit_NRG_Vlieland/"
]
wmts_options = alt.binding_select(options=wmts_urls)
param_wmts = alt.param(name="base_url_options", value=wmts_urls[0], bind=wmts_options)
chart = chart.add_params(param_wmts)
chart

image

This is just adding the options, but to make this actual work, something needs to modified to utilize this base_url_options param. I was inspecting the created vega-lite specification and noticed the following, hardcoded in a transform calculate

chart.to_dict()["layer"][1]["layer"][0]["transform"][2]
{'calculate': "'https://tiles1.geoserve.eu/Pleiades-NEO_NRG/tileserver/20231229_110351_PNEO-03_1_62_30cm_RD_8bit_NRG_Vlieland/' + zoom_ceil + '/' + ((datum.a + dii_floor + max_one_side_tiles_count) % max_one_side_tiles_count) + '/' + (datum.b + djj_floor) + ''",
 'as': 'url'}

Maybe we can update altair_tiles, so this URL is referenced by a parameter, which can be easily modified to listen to the base_url_options param. Something as such:

import re

param_base_url = alt.param(name="base_url", value="https://tiles1.geoserve.eu/Pleiades-NEO_NRG/tileserver/20231229_110351_PNEO-03_1_62_30cm_RD_8bit_NRG_Vlieland/")
chart = chart.add_params(param_base_url)

for ix, par in enumerate(chart.params):
    if par.name == 'base_url':
        chart.params[ix] = alt.param(name='base_url', expr='base_url_options').param

pattern = re.compile(r"'(https?://[^']+)'\s*\+")
tf_calculate = chart.layer[1].layer[0].transform[2].calculate
new_tf_calculate = re.sub(pattern, "base_url +", tf_calculate)
chart.layer[1].layer[0].transform[2].calculate = new_tf_calculate
chart

https://github.com/altair-viz/altair_tiles/assets/5186265/b9c05abe-8ca0-41d1-87b8-b4c1fd28ba43

I think this feature require a change in the code around here: https://github.com/altair-viz/altair_tiles/blob/main/altair_tiles/__init__.py#L262

(btw, I'm not sure if these wmts-URLs are accessible outside the Netherlands.)

binste commented 6 months ago

Nice to see altair-tiles in action and great idea for a feature! I'd also like to make this happen but not yet sure how. Replacing the base url part with a parameter requires regex as shown in your example as a provider does not have a base_url parameter or similar, just a full url which includes all relevant parameters. I'd assume that these are similarly structured but it's probably the safest to continue to use xyzservices.Providers.build_url to create the actual URL, right?

Maybe we could pass in multiple providers and then altair_tiles builds this by itself? That might not require any regex as we can use build_url internally to pregenerate all URLs and then use a dropdown to select the proper ones.

I'll think some more about this and will play around with it. Ideas are welcome! :)

mattijn commented 6 months ago

I'm trying to explore some more use-cases! Ah I see, the build_url is delegated to the xyzservices build_url, which is more advanced than just formatting z, x, y. Not sure how to generalize this then. At least I found a way for now..