Python/Plotly: 如何在散点图框中注释 geojson 图层

Python/Plotly: How to annotate geojson layers in a scattermapbox

我正在尝试使用 Python/Plotly 在 Mapbox 地图中绘制来自两个 pandas 数据框的数据,同时还包括两个数据集的注释。第一个数据集包含具有关联值的点,我可以使用 px.scatter_mapbox 自动为这些点提供悬停文本注释来绘制它们。

第二个数据集包含多边形,我可以使用 update_layout 在第一个数据集上放置它们。但是,我还想标记多边形。理想情况下,我希望有一个与单击时出现的每个多边形相关联的注释,同时还让悬停文本标签出现在第一个数据集中最接近光标的位置。下面的示例代码显示了两个数据集以及位置数据集的悬停文本,但不包括使用 cur_name 变量标记多边形的方法。

如有任何建议,我们将不胜感激。

layer_list = []
for idx,row in polygon_df.iterrows():
    cur_name = row['Name']
    cur_geom = row['Polygon']
    cur_source = json.loads(gpd.GeoSeries(cur_geom).to_json())
    layer_list.append({'name': str(cur_name),
                       'sourcetype': 'geojson',
                       'source': cur_source,
                       'type': "fill",
                       'below': "traces",
                       'opacity':0.2,
                       'color': "red"})

fig = px.scatter_mapbox(location_df,
                        lat="Latitude",
                        lon="Longitude",
                        color="Value",
                        opacity=0.6,
                        zoom=8,
                        mapbox_style="carto-positron")

fig.update_layout(mapbox = {'layers': layer_list})

fig.update_layout(
    margin=dict(l=20, r=20, t=20, b=20),
    paper_bgcolor="LightSteelBlue",
)

fig.show()

为了清楚起见,这里有一个代码当前状态的独立示例:

import plotly.express as px
import numpy as np
import pandas as pd
import geojson
import geopandas as gpd
import json
from io import StringIO
import shapely.wkt

location_csv = """
Latitude,Longitude,Value
33.4484110068466,-112.06200256059738,1.643
33.42952535283222,-112.06512832474168,3.531
33.43606430916848,-112.09102376100617,5.347
33.44372793070293,-112.01068625893538,8.424
"""
polygon_csv = """
Name,PolygonString
Example1,"MULTIPOLYGON(((-112.087104 33.435576,-112.089254 33.435576,-112.090329 33.436477,-112.090329 33.43828,-112.088179 33.440084,-112.090329 33.441887,-112.089254 33.442788,-112.084954 33.442788,-112.082803 33.444592,-112.080653 33.442788,-112.075278 33.447297,-112.077428 33.4491,-112.075278 33.450903,-112.075278 33.458116,-112.073128 33.459919,-112.075278 33.461722,-112.074203 33.462624,-112.069903 33.462624,-112.068828 33.463526,-112.068828 33.465329,-112.066678 33.467132,-112.066678 33.468935,-112.065602 33.469837,-112.064527 33.468935,-112.064527 33.467132,-112.063452 33.466231,-112.061302 33.466231,-112.059152 33.468034,-112.054852 33.468034,-112.050552 33.464427,-112.048401 33.464427,-112.047326 33.463526,-112.047326 33.461722,-112.041951 33.457214,-112.037651 33.457214,-112.033351 33.453608,-112.0312 33.453608,-112.027975 33.450903,-112.02905 33.450001,-112.0312 33.450001,-112.032276 33.4491,-112.0312 33.448198,-112.0226 33.448198,-112.021525 33.447297,-112.0226 33.446395,-112.0269 33.446395,-112.027975 33.445493,-112.025825 33.44369,-112.0269 33.442788,-112.0312 33.442788,-112.032276 33.441887,-112.030125 33.440084,-112.030125 33.43828,-112.034426 33.434674,-112.034426 33.431067,-112.035501 33.430166,-112.036576 33.431067,-112.036576 33.432871,-112.037651 33.433772,-112.038726 33.432871,-112.038726 33.431067,-112.039801 33.430166,-112.041951 33.430166,-112.043026 33.429264,-112.043026 33.427461,-112.044101 33.426559,-112.050552 33.426559,-112.051627 33.427461,-112.051627 33.429264,-112.054852 33.431969,-112.057002 33.431969,-112.059152 33.430166,-112.063452 33.430166,-112.064527 33.429264,-112.064527 33.427461,-112.062377 33.425658,-112.063452 33.424756,-112.067753 33.424756,-112.068828 33.425658,-112.068828 33.427461,-112.069903 33.428363,-112.074203 33.428363,-112.078503 33.431969,-112.080653 33.431969,-112.082803 33.433772,-112.084954 33.433772,-112.087104 33.435576)),((-112.0269 33.435576,-112.027975 33.436477,-112.02475 33.439182,-112.0226 33.437379,-112.02045 33.439182,-112.01615 33.439182,-112.013999 33.437379,-112.011849 33.437379,-112.010774 33.436477,-112.011849 33.435576,-112.0183 33.435576,-112.02045 33.437379,-112.0226 33.435576,-112.0269 33.435576)))"
Example2,"MULTIPOLYGON(((-112.118214 33.437402,-112.116064 33.437402,-112.113914 33.439205,-112.111764 33.437402,-112.109614 33.437402,-112.108539 33.438303,-112.108539 33.445516,-112.107464 33.446418,-112.106389 33.445516,-112.106389 33.443713,-112.105314 33.442811,-112.103164 33.442811,-112.102089 33.443713,-112.104239 33.445516,-112.102089 33.447319,-112.102089 33.449123,-112.101014 33.450024,-112.096714 33.450024,-112.095639 33.450926,-112.095639 33.452729,-112.094564 33.453631,-112.092414 33.453631,-112.091339 33.454532,-112.091339 33.456336,-112.093489 33.458139,-112.093489 33.459942,-112.095639 33.461745,-112.094564 33.462647,-112.090264 33.45904,-112.079514 33.45904,-112.078439 33.458139,-112.078439 33.456336,-112.077364 33.455434,-112.075214 33.455434,-112.074139 33.456336,-112.074139 33.458139,-112.073063 33.45904,-112.071988 33.458139,-112.071988 33.456336,-112.070913 33.455434,-112.069838 33.456336,-112.069838 33.459942,-112.068763 33.460844,-112.066613 33.45904,-112.064463 33.460844,-112.062313 33.45904,-112.060163 33.45904,-112.059088 33.458139,-112.059088 33.456336,-112.058013 33.455434,-112.055863 33.455434,-112.047263 33.448221,-112.040813 33.448221,-112.039738 33.447319,-112.042963 33.444615,-112.047263 33.444615,-112.048338 33.443713,-112.047263 33.442811,-112.045113 33.442811,-112.044038 33.44191,-112.046188 33.440107,-112.046188 33.438303,-112.042963 33.435598,-112.040813 33.437402,-112.038663 33.437402,-112.037588 33.4365,-112.038663 33.435598,-112.040813 33.435598,-112.042963 33.433795,-112.047263 33.433795,-112.048338 33.432894,-112.046188 33.43109,-112.046188 33.429287,-112.044038 33.427484,-112.044038 33.425681,-112.045113 33.424779,-112.047263 33.424779,-112.049413 33.422976,-112.051563 33.424779,-112.053713 33.424779,-112.054788 33.423877,-112.054788 33.422074,-112.055863 33.421173,-112.062313 33.421173,-112.064463 33.422976,-112.066613 33.421173,-112.068763 33.422976,-112.070913 33.422976,-112.073063 33.424779,-112.075214 33.422976,-112.081664 33.422976,-112.083814 33.421173,-112.085964 33.422976,-112.088114 33.422976,-112.090264 33.421173,-112.094564 33.421173,-112.095639 33.422074,-112.095639 33.423877,-112.096714 33.424779,-112.097789 33.423877,-112.097789 33.422074,-112.098864 33.421173,-112.103164 33.421173,-112.104239 33.422074,-112.102089 33.423877,-112.102089 33.427484,-112.103164 33.428385,-112.111764 33.428385,-112.112839 33.429287,-112.111764 33.430189,-112.109614 33.430189,-112.108539 33.43109,-112.108539 33.434697,-112.109614 33.435598,-112.118214 33.435598,-112.119289 33.4365,-112.118214 33.437402)),((-112.077364 33.412156,-112.078439 33.413058,-112.078439 33.414861,-112.076289 33.416664,-112.076289 33.420271,-112.075214 33.421173,-112.074139 33.420271,-112.074139 33.418468,-112.073063 33.417566,-112.070913 33.419369,-112.067688 33.416664,-112.067688 33.414861,-112.068763 33.41396,-112.070913 33.41396,-112.071988 33.413058,-112.069838 33.411255,-112.071988 33.409452,-112.071988 33.407648,-112.073063 33.406747,-112.074139 33.407648,-112.074139 33.411255,-112.075214 33.412156,-112.077364 33.412156)))"
"""
location_df = pd.read_csv(StringIO(location_csv))
polygon_df = pd.read_csv(StringIO(polygon_csv))
polygon_df['Polygon'] = polygon_df['PolygonString'].apply(shapely.wkt.loads)

layer_list = []
for idx,row in polygon_df.iterrows():
    cur_name = row['Name']
    cur_geom = row['Polygon']
    cur_source = json.loads(gpd.GeoSeries(cur_geom).to_json())
    layer_list.append({'name': str(cur_name),
                       'sourcetype': 'geojson',
                       'source': cur_source,
                       'type': "fill",
                       'below': "traces",
                       'opacity':0.2,
                       'color': "red"})

fig = px.scatter_mapbox(location_df,
                        lat="Latitude",
                        lon="Longitude",
                        color="Value",
                        opacity=0.6,
                        zoom=12,
                        mapbox_style="carto-positron")

fig.update_layout(mapbox = {'layers': layer_list})

fig.update_layout(
    margin=dict(l=20, r=20, t=20, b=20),
    paper_bgcolor="LightSteelBlue",
)

fig.show()

生成以下内容:

Example Screenshot

您可以将多边形添加为等值线,以便它们具有交互性。它与图层不太一样,因为您不能以相同的方式实现 opacity。悬停文本将位于多边形/多边形的中心。也看过爆炸多边形,所以文本更接近指针。

import plotly.express as px
import numpy as np
import pandas as pd
import plotly.graph_objects as go
import geopandas as gpd
import json
from io import StringIO
import shapely.wkt

location_csv = """
Latitude,Longitude,Value
33.4484110068466,-112.06200256059738,1.643
33.42952535283222,-112.06512832474168,3.531
33.43606430916848,-112.09102376100617,5.347
33.44372793070293,-112.01068625893538,8.424
"""
polygon_csv = """
Name,PolygonString
Example1,"MULTIPOLYGON(((-112.087104 33.435576,-112.089254 33.435576,-112.090329 33.436477,-112.090329 33.43828,-112.088179 33.440084,-112.090329 33.441887,-112.089254 33.442788,-112.084954 33.442788,-112.082803 33.444592,-112.080653 33.442788,-112.075278 33.447297,-112.077428 33.4491,-112.075278 33.450903,-112.075278 33.458116,-112.073128 33.459919,-112.075278 33.461722,-112.074203 33.462624,-112.069903 33.462624,-112.068828 33.463526,-112.068828 33.465329,-112.066678 33.467132,-112.066678 33.468935,-112.065602 33.469837,-112.064527 33.468935,-112.064527 33.467132,-112.063452 33.466231,-112.061302 33.466231,-112.059152 33.468034,-112.054852 33.468034,-112.050552 33.464427,-112.048401 33.464427,-112.047326 33.463526,-112.047326 33.461722,-112.041951 33.457214,-112.037651 33.457214,-112.033351 33.453608,-112.0312 33.453608,-112.027975 33.450903,-112.02905 33.450001,-112.0312 33.450001,-112.032276 33.4491,-112.0312 33.448198,-112.0226 33.448198,-112.021525 33.447297,-112.0226 33.446395,-112.0269 33.446395,-112.027975 33.445493,-112.025825 33.44369,-112.0269 33.442788,-112.0312 33.442788,-112.032276 33.441887,-112.030125 33.440084,-112.030125 33.43828,-112.034426 33.434674,-112.034426 33.431067,-112.035501 33.430166,-112.036576 33.431067,-112.036576 33.432871,-112.037651 33.433772,-112.038726 33.432871,-112.038726 33.431067,-112.039801 33.430166,-112.041951 33.430166,-112.043026 33.429264,-112.043026 33.427461,-112.044101 33.426559,-112.050552 33.426559,-112.051627 33.427461,-112.051627 33.429264,-112.054852 33.431969,-112.057002 33.431969,-112.059152 33.430166,-112.063452 33.430166,-112.064527 33.429264,-112.064527 33.427461,-112.062377 33.425658,-112.063452 33.424756,-112.067753 33.424756,-112.068828 33.425658,-112.068828 33.427461,-112.069903 33.428363,-112.074203 33.428363,-112.078503 33.431969,-112.080653 33.431969,-112.082803 33.433772,-112.084954 33.433772,-112.087104 33.435576)),((-112.0269 33.435576,-112.027975 33.436477,-112.02475 33.439182,-112.0226 33.437379,-112.02045 33.439182,-112.01615 33.439182,-112.013999 33.437379,-112.011849 33.437379,-112.010774 33.436477,-112.011849 33.435576,-112.0183 33.435576,-112.02045 33.437379,-112.0226 33.435576,-112.0269 33.435576)))"
Example2,"MULTIPOLYGON(((-112.118214 33.437402,-112.116064 33.437402,-112.113914 33.439205,-112.111764 33.437402,-112.109614 33.437402,-112.108539 33.438303,-112.108539 33.445516,-112.107464 33.446418,-112.106389 33.445516,-112.106389 33.443713,-112.105314 33.442811,-112.103164 33.442811,-112.102089 33.443713,-112.104239 33.445516,-112.102089 33.447319,-112.102089 33.449123,-112.101014 33.450024,-112.096714 33.450024,-112.095639 33.450926,-112.095639 33.452729,-112.094564 33.453631,-112.092414 33.453631,-112.091339 33.454532,-112.091339 33.456336,-112.093489 33.458139,-112.093489 33.459942,-112.095639 33.461745,-112.094564 33.462647,-112.090264 33.45904,-112.079514 33.45904,-112.078439 33.458139,-112.078439 33.456336,-112.077364 33.455434,-112.075214 33.455434,-112.074139 33.456336,-112.074139 33.458139,-112.073063 33.45904,-112.071988 33.458139,-112.071988 33.456336,-112.070913 33.455434,-112.069838 33.456336,-112.069838 33.459942,-112.068763 33.460844,-112.066613 33.45904,-112.064463 33.460844,-112.062313 33.45904,-112.060163 33.45904,-112.059088 33.458139,-112.059088 33.456336,-112.058013 33.455434,-112.055863 33.455434,-112.047263 33.448221,-112.040813 33.448221,-112.039738 33.447319,-112.042963 33.444615,-112.047263 33.444615,-112.048338 33.443713,-112.047263 33.442811,-112.045113 33.442811,-112.044038 33.44191,-112.046188 33.440107,-112.046188 33.438303,-112.042963 33.435598,-112.040813 33.437402,-112.038663 33.437402,-112.037588 33.4365,-112.038663 33.435598,-112.040813 33.435598,-112.042963 33.433795,-112.047263 33.433795,-112.048338 33.432894,-112.046188 33.43109,-112.046188 33.429287,-112.044038 33.427484,-112.044038 33.425681,-112.045113 33.424779,-112.047263 33.424779,-112.049413 33.422976,-112.051563 33.424779,-112.053713 33.424779,-112.054788 33.423877,-112.054788 33.422074,-112.055863 33.421173,-112.062313 33.421173,-112.064463 33.422976,-112.066613 33.421173,-112.068763 33.422976,-112.070913 33.422976,-112.073063 33.424779,-112.075214 33.422976,-112.081664 33.422976,-112.083814 33.421173,-112.085964 33.422976,-112.088114 33.422976,-112.090264 33.421173,-112.094564 33.421173,-112.095639 33.422074,-112.095639 33.423877,-112.096714 33.424779,-112.097789 33.423877,-112.097789 33.422074,-112.098864 33.421173,-112.103164 33.421173,-112.104239 33.422074,-112.102089 33.423877,-112.102089 33.427484,-112.103164 33.428385,-112.111764 33.428385,-112.112839 33.429287,-112.111764 33.430189,-112.109614 33.430189,-112.108539 33.43109,-112.108539 33.434697,-112.109614 33.435598,-112.118214 33.435598,-112.119289 33.4365,-112.118214 33.437402)),((-112.077364 33.412156,-112.078439 33.413058,-112.078439 33.414861,-112.076289 33.416664,-112.076289 33.420271,-112.075214 33.421173,-112.074139 33.420271,-112.074139 33.418468,-112.073063 33.417566,-112.070913 33.419369,-112.067688 33.416664,-112.067688 33.414861,-112.068763 33.41396,-112.070913 33.41396,-112.071988 33.413058,-112.069838 33.411255,-112.071988 33.409452,-112.071988 33.407648,-112.073063 33.406747,-112.074139 33.407648,-112.074139 33.411255,-112.075214 33.412156,-112.077364 33.412156)))"
"""
location_df = pd.read_csv(StringIO(location_csv))
polygon_df = pd.read_csv(StringIO(polygon_csv))
polygon_df["Polygon"] = polygon_df["PolygonString"].apply(shapely.wkt.loads)

# polygon "layers"
traces = []
for i, (n, d) in enumerate(polygon_df.groupby("Name")):
    gs = gpd.GeoSeries(d["Polygon"].apply(lambda g: g.geoms).explode()).reset_index(drop=True)
    # gs = gpd.GeoSeries(d["Polygon"])

    traces.append(
        go.Choroplethmapbox(
            name=n,
            uid=i + 1,
            below=i,
            geojson=gs.__geo_interface__,
            locations=gs.index,
            z=np.full(len(gs),i),
            hovertemplate=f"{n}<extra></extra>",
            showscale=True,
            coloraxis="coloraxis2",
        )
    )

fig = px.scatter_mapbox(
    location_df,
    lat="Latitude",
    lon="Longitude",
    color="Value",
    opacity=0.6,
    zoom=12,
    mapbox_style="carto-positron",
).update_traces(uid=0, below="")

fig.add_traces(traces)

fig.update_layout(
    margin=dict(l=20, r=20, t=20, b=20),
    paper_bgcolor="LightSteelBlue",
    coloraxis2={"colorscale": [[0, "rgb(255,200,200)"], [1, "rgb(255,250,250)"]], "showscale": False},
)
# re-order traces so scatter is at top
fig.data = fig.data[::-1]
fig.show()