Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inclusion of basemaps in geographic plots #5758

Closed
mattijn opened this issue Jan 19, 2020 · 15 comments
Closed

Inclusion of basemaps in geographic plots #5758

mattijn opened this issue Jan 19, 2020 · 15 comments

Comments

@mattijn
Copy link
Contributor

mattijn commented Jan 19, 2020

This might in the end already be possible, but let me create an issue anyway.

To add a basemap to your projected data seems currently only documented to be possible in Vega in this issue with this working vega-editor example.

Since vega-lite 4.0 there is the possibility to use the image mark as well. This might be the moment to make this type of feature possible in Vega-lite as well.

Maybe not yet with the interactive zoom option, but only a static image for a specific zoom level.

@domoritz
Copy link
Member

That would be cool. The core team won't have cycles to do this but we would be happy to review an API design proposal and pull request. Would you be willing to do this?

@mattijn
Copy link
Contributor Author

mattijn commented Jan 20, 2020

The issue is aimed at the wider community. Original proof of concept also came from outside the core team.
I am sure trying as well, but anyone with the capabilities may takeover.

@cankadir
Copy link

This would have been cool.

@mattijn
Copy link
Contributor Author

mattijn commented Apr 21, 2021

I tried to transfer this working vega code, editor url:
image

To vega-lite code (editor url). I'm getting close but not close enough.
image

Not sure exactly how I could improve the vega-lite code to get it working (maybe something with scale that is added to the x/y encoding in the image mark in the compiled vega?)

@domoritz
Copy link
Member

Nice. Take a look at the compiled Vega to see what may be going on.

@mattijn
Copy link
Contributor Author

mattijn commented Apr 21, 2021

Yep, this vega-lite piece:

       "x": {"field": "x", "type": "quantitative"},
       "y": {"field": "y", "type": "quantitative"},

Is compiled into:

          "xc": {"scale": "x", "field": "x"},
          "yc": {"scale": "y", "field": "y"},

If I manually change it (in vega) into:

          "x": {"field": "x"},
          "y": {"field": "y"},   

it works.

@mattijn
Copy link
Contributor Author

mattijn commented Apr 21, 2021

Getting closer, I can set the scale to null. Open the Chart in the Vega Editor
image

@domoritz
Copy link
Member

Sweet. I think https://github.com/vega/vega-lite/issues/7397 should also help. I can prioritize it.

@mattijn
Copy link
Contributor Author

mattijn commented Apr 21, 2021

Got it 🥳🎉
Open the Chart in the Vega Editor

See animated gif:
ezgif com-gif-maker

Used spec

```json { "$schema": "https://vega.github.io/schema/vega-lite/v5.json", "width": 400, "height": 400, "autosize": "none", "padding": {"top": 0, "bottom": 0, "left": 0, "right": 0}, "params": [ {"name": "tx", "expr": "width/2"}, {"name": "ty", "expr": "height/2"}, { "name": "zoom_precise", "value": 6.7, "bind": {"input": "range", "min": 2, "max": 20, "step": 0.1} }, { "name": "rotateX", "value": -5.3, "bind": {"input": "range", "min": -180, "max": 180, "step": 0.1} }, { "name": "centerY", "value": 52, "bind": {"input": "range", "min": -60, "max": 60, "step": 0.05} }, {"name": "baseTileSize", "value": 256}, {"name": "tileUrl", "value": "https://a.tile.openstreetmap.org/"}, {"name": "zoom", "expr": "ceil(zoom_precise)"}, {"name": "tilesCount", "expr": "pow(2,zoom)"}, {"name": "tileSize", "expr": "baseTileSize*pow(2,zoom_precise-zoom)"}, {"name": "maxTiles", "expr": "ceil(max(height,width)/tileSize +1)"}, {"name": "basePoint", "expr": "invert('projection',[0,0])"}, {"name": "dii", "expr": "((basePoint[0]+180)/360*tilesCount)"}, {"name": "di", "expr": "floor(dii)"}, {"name": "dx", "expr": "round((floor(dii)-dii)*tileSize)"}, { "name": "djj", "expr": "((1-log(tan(basePoint[1]*PI/180) + 1/cos(basePoint[1]*PI/180))/PI)/2 *tilesCount)" }, {"name": "dj", "expr": "floor(djj)"}, {"name": "dy", "expr": "round((floor(djj)-djj)*tileSize)"}, {"name": "scale", "expr": "baseTileSize * pow(2,zoom_precise) / (2 * PI)"} ], "layer": [ { "data": { "name": "tile_list", "sequence": {"start": 0, "stop": {"signal": "maxTiles"}, "as": "a"} }, "transform": [ {"calculate": "sequence(0,maxTiles)", "as": "b"}, {"flatten": ["b"]}, { "calculate": "tileUrl+zoom+'/'+(datum.a+di+tilesCount)%tilesCount+'/'+((datum.b+dj))+'.png'", "as": "url" }, {"calculate": "(datum.a * tileSize + dx)+(tileSize/2)", "as": "x"}, {"calculate": "(datum.b * tileSize + dy)+(tileSize/2)", "as": "y"} ], "mark": { "type": "image", "width": {"signal": "tileSize"}, "height": {"signal": "tileSize"} }, "encoding": { "x": {"field": "x", "type": "quantitative", "scale": null}, "y": {"field": "y", "type": "quantitative", "scale": null}, "url": {"field": "url", "type": "nominal"} } }, { "data": { "name": "world", "url": "https://vega.github.io/vega-datasets/data/world-110m.json", "format": {"type": "topojson", "feature": "countries"} }, "mark": "geoshape", "encoding": { "fill": {"value": "orange"}, "fillOpacity": {"value": 0.1}, "stroke": {"value": "orange"}, "strokeWidth": {"value": 2} }, "projection": { "type": "mercator", "scale": {"expr": "scale"}, "rotate": [{"signal": "rotateX"}, 0, 0], "center": [0, {"signal": "centerY"}], "translate": [{"signal": "tx"}, {"signal": "ty"}] } } ] } ```

@jwoLondon
Copy link
Contributor

Wow! I didn't think that would be possible in VL. Very impressive.

@domoritz
Copy link
Member

Very cool. Looks like we need to fix a few places to support expr instead of signal.

cc @nyurik

@jwoLondon
Copy link
Contributor

jwoLondon commented Apr 28, 2021

I think the only property in @mattijn 's spec that needs signal updating to expr is for the sequence generator. I will log a separate issue for that so it can be tracked.

For the projection properties that take arrays, they can all be expressed as a single expression (thanks @jheer for the tip). For example, instead of "rotate": [{"signal": "rotateX"}, 0, 0] you can use "rotate": {"expr": "[rotateX, 0, 0]"}, Jeff points out that this is slightly more performant as well as being a little more compact. It's also worth noting that this is currently the only way of setting clipExtent containing expressions as it uses nested arrays that cannot be mixed with expressions (vega/vega#3184).

Here's a version of Mattijn's spec that uses the concatenate pattern.

It might be worth updating the documentation for exprRef indicating this concatenation pattern for array-valued expressions that contain one or more expressions within them.

@wortzmanb
Copy link

This is a super-cool workaround. Thanks for figuring it out!

Can anyone help explain why this fails if the map is included in a vconcat? (For example, as here.) I'm a Vega-Lite novice, so it's possible I have a syntax issue. But it seems that if I leave the params at the top-level, the map fails to render, and if I move them into either the vconcat view spec or the layer view spec, the "maxTiles" signal on line 51 can no longer be found.

For context, I'm trying to produce a similar map that is paired with another viz where the two visualizations have some selectors in common. I can provide the full spec for that if needed, but I'm pretty sure I've tracked the issue down to this vconcat thing.

Thanks!

@mattijn
Copy link
Contributor Author

mattijn commented Mar 9, 2023

I'm going to close this issue as I managed to get the tiles for basemaps working properly in altair (gist altair_osm_tiles.py) and thus also in vega-lite:

Open the Chart in the Vega Editor

Animated gif
tiles vegalite

I'll open a new issue with a feature request how to introduce tiles in a declarative manner instead of the current imperative.

@binste
Copy link

binste commented Nov 24, 2023

I just released the first version of altair_tiles, an official Vega-Altair project. It builds on the specification created by @mattijn. Feedback is very welcome! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants