vega-lite
vega-lite copied to clipboard
Inclusion of basemaps in geographic plots
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.
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?
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.
This would have been cool.
I tried to transfer this working vega code, editor url:
To vega-lite code (editor url). I'm getting close but not close enough.
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?)
Nice. Take a look at the compiled Vega to see what may be going on.
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.
Getting closer, I can set the scale to null
. Open the Chart in the Vega Editor
Sweet. I think https://github.com/vega/vega-lite/issues/7397 should also help. I can prioritize it.
Got it 🥳🎉 Open the Chart in the Vega Editor
See animated gif:
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"}] } } ] } ```
Wow! I didn't think that would be possible in VL. Very impressive.
Very cool. Looks like we need to fix a few places to support expr
instead of signal
.
cc @nyurik
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 (https://github.com/vega/vega/issues/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.
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!
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
I'll open a new issue with a feature request how to introduce tiles in a declarative manner instead of the current imperative.
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! :)