✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.
⚡️ Concerned about the grid? Kyle Baranko teaches how to predicting peak loads using XGBoost. Register for the August webinar!

Datashader image distorted when passed to mapbox

I think I am lacking a fundamental understanding of how images are passed to scatter_mapbox as a mapbox_layer.

I am trying to pass a datashader raster to mapbox. When following the example, but using my own data, my image is completely distorted. I have tried re-sizing but no luck. I think I am missing something simple and fundamental but cannot figure out what it is.

uniqueStations = pd.read_csv('https://raw.githubusercontent.com/ghavranek/geoData/master/geoData.csv')


import datashader as ds
cvs = ds.Canvas(plot_width=1000, plot_height=1000)
agg = cvs.points(uniqueStations, x='longitude', y='latitude')

# agg is an xarray object, see http://xarray.pydata.org/en/stable/ for more details
coords_lat, coords_lon = agg.coords['latitude'].values, agg.coords['longitude'].values

# Corners of the image, which need to be passed to mapbox
coordinates = [[coords_lon[0], coords_lat[0]],
               [coords_lon[-1], coords_lat[0]],
               [coords_lon[-1], coords_lat[-1]],
               [coords_lon[0], coords_lat[-1]]]


from colorcet import fire
import datashader.transfer_functions as tf
img = tf.shade(agg, cmap=fire).to_pil()

img = img.resize((1400, 800))

import plotly.express as px

# Trick to create rapidly a figure with mapbox axes
fig = px.scatter_mapbox(uniqueStations[:1], lat='latitude', lon='longitude',zoom=2)

# Add the datashader image as a mapbox layer image
fig.update_layout(mapbox_style="carto-darkmatter",                  
                 mapbox_layers = [
                {
                    "sourcetype": "image",
                    "source": img,
                    "coordinates": coordinates
                }]
)
fig.show()

If you run this, you will see a crazy image on top of the mapbox. However if you just call the image, it appears fine.

img


Am I not anchoring the corners correctly? Am I not resizing correctly? I have been stumped for two days.

Hi @ghavranek it is probably a projection problem; if you try to limit the range of latitude and longitude it works better, for example

uniqueStations = pd.read_csv('https://raw.githubusercontent.com/ghavranek/geoData/master/geoData.csv')
import datashader as ds
cvs = ds.Canvas(plot_width=1000, plot_height=1000)
stations = uniqueStations.query("longitude < -40").query("longitude > -100").query("latitude < 50").query("latitude > 30")
agg = cvs.points(stations, x='longitude', y='latitude')

# agg is an xarray object, see http://xarray.pydata.org/en/stable/ for more details
coords_lat, coords_lon = agg.coords['latitude'].values, agg.coords['longitude'].values

# Corners of the image, which need to be passed to mapbox
coordinates = [[coords_lon[0], coords_lat[0]],
               [coords_lon[-1], coords_lat[0]],
               [coords_lon[-1], coords_lat[-1]],
               [coords_lon[0], coords_lat[-1]]]


from colorcet import fire
import datashader.transfer_functions as tf
img = tf.shade(agg, cmap=fire)[::-1].to_pil()

img = img.resize((1400, 800))

import plotly.express as px

# Trick to create rapidly a figure with mapbox axes
fig = px.scatter_mapbox(uniqueStations[:1], lat='latitude', lon='longitude')

# Add the datashader image as a mapbox layer image
fig.update_layout(mapbox_style="carto-darkmatter",                  
                 mapbox_layers = [
                {
                    "sourcetype": "image",
                    "source": img,
                    "coordinates": coordinates
                }]
)
fig.show()

It is indeed hard to fit the sphere on a rectangle and I don’t know which projection mapbox is using but there is probably a mismatch between the two. What you could do is separate the latitude and longitude grid into smaller tiles and compute one image per tile, using datashader, and add all these images to mapbox. This would reduce the projection distortion, I hope.

In fact, what you could do is first project your latitude and longitude coordinates so that they are in the same projection as mapbox. To overlay on mapbox, the data need to be in Web Mercator (EPSG:3857) coordinates (https://proj.org/operations/projections/webmerc.html) before running it through DataShader. You can use pyproj to do the projection.

Thanks so much. I’m betting your second answer is the key. I will give it a try.

That’s right; Mapbox uses Web Mercator, and so lat/lon coordinates need to be converted to that before you datashade your image. Datashader includes a simple utility to do that conversion; ds.util.lnglat_to_meters.

1 Like

I have the same problem. Did you find a solution to it ?

I tried a method that uses pyproj - https://stackoverflow.com/questions/42777086/how-to-convert-geospatial-coordinates-dataframe-to-native-x-y-projection

However, it was taking forever. So I had to abort.

The solution using ds.util.lnglat_to_meters did not work for me.

Any help on this would be very much appreciated. So far I have been unable to align the datashader map with the mapbox map.

Sorry, I meant to update this post. Converting to Web Mercator coordinates as @Emmanuelle and @jbednar suggested did work.