简体   繁体   English

plotly mapbox 中多边形的数据着色器集成

[英]Datashader integration for polygons in plotly mapbox

I'm using plotly's Scattermapbox to overlay a map with a shaded image of polygons created by datashader's shade function (based on https://plotly.com/python/datashader/ ), but the projections do not seem to align, see picture below.我正在使用 plotly 的 Scattermapbox 将 map 与由数据着色器的阴影 function 创建的多边形阴影图像叠加(基于https://plotly.com/python/datashader/ ),但投影似乎没有对齐,请参见下图. Any suggestions how I can overcome this problem using plotly's Scattermapbox and datashader?有什么建议可以使用 plotly 的 Scattermapbox 和数据着色器来解决这个问题吗?

Reproducible example:可重现的例子:

import geopandas as gpd
import plotly.graph_objects as go
import spatialpandas as spd
import datashader as ds
from colorcet import fire
import datashader.transfer_functions as tf

# load data
world = gpd.read_file(
    gpd.datasets.get_path('naturalearth_lowres')
)
# world = world.to_crs(epsg=3857)
# create spatialpandas DataFrame
df_world = spd.GeoDataFrame(world)
# create datashader canvas and aggregate
cvs = ds.Canvas(plot_width=1000, plot_height=1000)
agg = cvs.polygons(df_world, geometry='geometry', agg=ds.mean('pop_est'))
# create shaded image
tf.shade(agg, cmap=fire)

shaded image阴影图像

# create shaded image and convert to Python image
img = tf.shade(agg, cmap=fire)[::-1].to_pil()

coords_lat, coords_lon = agg.coords["y"].values, agg.coords["x"].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]],
]

fig = go.Figure(go.Scattermapbox())
fig.update_layout(
    mapbox_style="open-street-map",
    mapbox_layers=[
        {
            "sourcetype": "image",
            "source": img,
            "coordinates": coordinates,
        }
    ]
)
fig.show()

overlayed map覆盖 map

I read that Scattermapbox only supports Mercator projection which I found confusing as the examples in plotly's documentation seem to be in long/lat format, but I tried converting the coordinates of the GeoDataFrame to epsg 3857, see我读到 Scattermapbox 只支持 Mercator 投影,我发现它令人困惑,因为 plotly 文档中的示例似乎是 long/lat 格式,但我尝试将 GeoDataFrame 的坐标转换为 epsg 3857,请参阅

# world = world.to_crs(epsg=3857)

The results is that the shaded image becomes invisible.结果是阴影图像变得不可见。 Any help would be highly appreciated.任何帮助将不胜感激。

Have you tried with epsg:4326?你试过 epsg:4326 了吗? In my case, I use this one and the geometries are placed correctly.在我的例子中,我使用了这个并且几何图形被正确放置。

On the other hand, with geopandas to convert the geometry column of the dataframe you have to use the parameter "inplace=True".另一方面,使用 geopandas 转换 dataframe 的几何列时,您必须使用参数“inplace=True”。

We have discovered the solution to this issue: Below is each step / function code and description:我们已经找到了解决这个问题的方法:下面是每个步骤/function 代码和说明:

Imports for reference:导入参考:

import datashader as ds
import datashader.transfer_functions as tf
import numpy as np
import pandas as pd
import plotly.graph_objects as go
import rasterio
import shapely.geometry
import xarray as xr

_helper_add_pseudomercator_optimized : Creates array from the meshgrid with the proper mercator coordinates from the original raster with epsg:4326 . _helper_add_pseudomercator_optimized :使用epsg:4326的原始栅格中的适当墨卡托坐标从 meshgrid 创建数组。

def _helper_add_pseudomercator_optimized(raster):
    """Adds mercator coordinates epsg:3857 from a raster with epsg:4326.

    Originally defined as `add_psuedomercator_adam_manuel_optimized`

    Args:
        raster: xr.DataArray: `xr.DataArray` to transform coordinates

    Returns:
        `xr.DataArray` with coordinates (x, y) transformed from epsg:4326 to epsg:3857
    """
    # Transformer that converts coordinates from epsg 4326 to 3857
    gcs_to_3857 = Transformer.from_crs(4326, 3857, always_xy=True)

    x_vals = list(raster.x.values.squeeze())  # x values from the raster dimension x
    y_vals = list(raster.y.values.squeeze())  # x values from the raster dimension x

    # Allows transformation of non-square coordinates
    y_dummy_vals = [raster.y.values[0] for v in raster.x.values]  # dummy values
    x_dummy_vals = [raster.x.values[0] for v in raster.y.values]  # dummy values

    x, _ = gcs_to_3857.transform(x_vals, y_dummy_vals)  # Obtain x output here only
    _, y = gcs_to_3857.transform(x_dummy_vals, y_vals)  # Obtain y output here only\

    # Create meshgrid with the x and y mercator converted coordinates
    lon, lat = np.meshgrid(x, y)

    # Add meshgrid to raster -> raster now has mercator coordinates for every point
    raster["x_mercator"] = xr.DataArray(lon, dims=("y", "x"))
    raster["y_mercator"] = xr.DataArray(lat, dims=("y", "x"))

    return raster
def _helper_affine_transform(raster):
    """Create new affine from a raster. Used to get new affine from the transformed affine.

    Args:
        raster: xr.DataArray: `xr.DataArray` to get the original affine and then transform

    Returns:
        New affine transform for a coarsened array
    """
    res = (raster.x[-1].values - raster.x[0].values) / raster.x.shape[0]
    scale = Affine.scale(res, -res)

    transform = (
        Affine.translation(raster.x[0].values - res / 2, raster.y[0].values - res / 2)
        * scale
    )

    return transform
def _helper_to_datashader_quadmesh(raster, y="lat", x="lon"):
    """Create lower level quadmesh with data based on flood raster. Map Flooding
    to lower level map.

    Args:
        raster: xr.DataArray: `xr.DataArray` raster of flooded regions

    Returns:
        `datashader.Canvas` based on quadmesh from original flood raster
    """
    cvs = ds.Canvas(plot_height=5000, plot_width=5000)

    z = xr.DataArray(
        raster.values.squeeze(),
        dims=["y", "x"],
        coords={
            "Qy": (["y", "x"], raster[y].values),
            "Qx": (["y", "x"], raster[x].values),
        },
        name="z",
    )

    return cvs.quadmesh(z, x="Qx", y="Qy")
def _helper_img_coordinates(raster):
    """Get coordinates of the corners of the baseline raster.

    Args:
        raster: xr.DataArray: `xr.DataArray` to get corner coordinates from

    Returns:
        coordinates of where to plot the flooded raster on the map
    """
    coords_lat, coords_lon = (raster.y.values, raster.x.values)

    if len(coords_lat.shape) > 1:
        coords_lat = coords_lat[:, 0]
        coords_lon = coords_lon[0, :]

    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]],
    ]

    return coordinates

All operations together for the below sequence:以下顺序的所有操作一起:

# Add mercator coordinates to the raster
raster = _helper_add_pseudomercator_optimized(raster)

# Create quadmesh from the burned raster
agg_mesh = _helper_to_datashader_quadmesh(raster, x="x_mercator", y="y_mercator")

# Don't plot values where the flooding is zero
agg_mesh = agg_mesh.where(agg_mesh < 0)

# Convert to datashader shade
im = tf.shade(agg_mesh, Theme.color_scale)

# Convert to image
img = im.to_pil()

# Get coordinates to plot raster on map
coordinates = _helper_img_coordinates(baseline_raster)

Then this image produced by datashader can be added to a plotly plot using the plotly objects layer, and providing this layer to the figure然后可以使用 plotly 对象层将数据着色器生成的图像添加到 plotly plot,并将该层提供给图形

layer = go.layout.mapbox.Layer(
        below="water",
        coordinates=coordinates,
        sourcetype="image",
        source=img,
    )

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM