vega-lite icon indicating copy to clipboard operation
vega-lite copied to clipboard

Inclusion of basemaps in geographic plots

Open mattijn opened this issue 4 years ago • 13 comments

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.

mattijn avatar Jan 19 '20 17:01 mattijn

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?

domoritz avatar Jan 19 '20 19:01 domoritz

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.

mattijn avatar Jan 20 '20 06:01 mattijn

This would have been cool.

cankadir avatar Mar 18 '20 00:03 cankadir

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?)

mattijn avatar Apr 21 '21 14:04 mattijn

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

domoritz avatar Apr 21 '21 14:04 domoritz

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 avatar Apr 21 '21 15:04 mattijn

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

mattijn avatar Apr 21 '21 15:04 mattijn

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

domoritz avatar Apr 21 '21 15:04 domoritz

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"}] } } ] } ```

mattijn avatar Apr 21 '21 18:04 mattijn

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

jwoLondon avatar Apr 21 '21 18:04 jwoLondon

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

cc @nyurik

domoritz avatar Apr 21 '21 18:04 domoritz

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.

jwoLondon avatar Apr 28 '21 14:04 jwoLondon

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!

wortzmanb avatar Jun 07 '22 01:06 wortzmanb

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.

mattijn avatar Mar 09 '23 19:03 mattijn

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! :)

binste avatar Nov 24 '23 18:11 binste