Plotly - 在 Density Mapbox 顶部添加 Scatter Geo 点和轨迹

Plotly - Adding Scatter Geo points and traces on top of Density Mapbox

我正在尝试在 white-bg 密度地图框顶部添加 Scattergeo 轨迹或叠加层,以获取通用美国各州轮廓上的热图。

我使用 scattergeo 的原因是我想在密度图框的顶部绘制一个星号,而 add_scattermapbox 接受的唯一符号是一个点。如果您选择 star 符号,则没有添加任何符号。

我也知道 add_scattermapboxdensity_scattermapbox 的 p mapbox_styles 可以接受星号,但目前我无法按网站付费试用金额用完后加载

有没有巧妙的方法在 density_mapbox 图上添加星号?

工作 ScatterGeo

fig = go.Figure(go.Scattergeo())

fig.add_scattergeo(lat = [30, 40]
                      ,lon = [-90, -80]
                      ,hoverinfo = 'none'
                      ,marker_size = 10
                      ,marker_color = 'rgb(65, 105, 225)' # blue
                      ,marker_symbol = 'star'
                      ,showlegend = False
                     )

fig.update_geos(
    visible=False, resolution=110, scope="usa",
    showcountries=True, countrycolor="Black",
    showsubunits=True, subunitcolor="Black"
)

fig.show()

工作密度地图框

d = {'Location': ['Point A', 'Point B'], 'lat': [30, 40], 'long': [-90, -80], 'z': [100,200]}

df = pd.DataFrame(data=d)

fig = px.density_mapbox(df
                        ,lat='lat'
                        ,lon='long'
                        ,z='z'
                        ,hover_name='Location'
                        ,center=dict(lat=38.5, lon=-96)
                        ,range_color = [0, 200]
                        ,zoom=2
                        ,radius=50
                        ,opacity=.5
                        ,mapbox_style='open-street-map')

fig.add_scattermapbox(lat = [30, 40]
                      ,lon = [-90, -80]
                      ,hoverinfo = 'none'
                      ,marker_size = 6
                      ,marker_color = 'rgb(0, 0, 0)'
#                       ,marker_symbol = 'star'
                      ,showlegend = False
                     )

fig.show()



尝试 #1 - 只需设置 marker_symbol = 'star'

取消注释 marker_symbol = 'star',这将适用于 mapbox 的高级样式,完全删除散点。

d = {'Location': ['Point A', 'Point B'], 'lat': [30, 40], 'long': [-90, -80], 'z': [100,200]}

df = pd.DataFrame(data=d)

fig = px.density_mapbox(df
                        ,lat='lat'
                        ,lon='long'
                        ,z='z'
                        ,hover_name='Location'
                        ,center=dict(lat=38.5, lon=-96)
                        ,range_color = [0, 200]
                        ,zoom=2
                        ,radius=50
                        ,opacity=.5
                        ,mapbox_style='open-street-map')

fig.add_scattermapbox(lat = [30, 40]
                      ,lon = [-90, -80]
                      ,hoverinfo = 'none'
                      ,marker_size = 6
                      ,marker_color = 'rgb(0, 0, 0)'
                      ,marker_symbol = 'star'
                      ,showlegend = False
                     )

fig.show()

尝试 #2 - 在散点图顶部添加密度地图框

scattergeo 之上添加 density_mapbox 会生成相同的地理图,但仅此而已。有密度图框图例,但没有热图。

d = {'Location': ['Point A', 'Point B'], 'lat': [30, 40], 'long': [-90, -80], 'z': [100,200]}

df = pd.DataFrame(data=d)

fig = go.Figure(go.Scattergeo())

fig.add_scattergeo(lat = [30, 40]
                      ,lon = [-90, -80]
                      ,hoverinfo = 'none'
                      ,marker_size = 10
                      ,marker_color = 'rgb(65, 105, 225)' # blue
                      ,marker_symbol = 'star'
                      ,showlegend = False
                     )

fig.add_densitymapbox(lat=df['lat'],
                     lon=df['long'],
                      z=df['z'],
                      radius=50,
                      opacity=.5
                     )

fig.update_geos(
    visible=False, resolution=110, scope="usa",
    showcountries=True, countrycolor="Black",
    showsubunits=True, subunitcolor="Black"
)

fig.show()

  • 瓦片地图和图层地图不能一起使用。因此,您不能在 mapbox

    上使用 geo 中的标记
  • 横向思考,您可以将自己的 geojson 图层添加到 mapbox

  • 生成几何图形。为此提供了两个选项

    1. 一个简单的三角形
      • get_geom(df["long"], df["lat"], marker=None, size=k)
    2. https://labs.mapbox.com/maki-icons/
      • get_geom(df["long"], df["lat"], marker="star", size=k) 其中 marker 是 MAKI 图标名称。 NB有空洞的图标可以填写-例如caution
  • 正在向 mapbox 图布局添加图层。这被参数化以生成多个图层以支持不同的缩放级别。更多层,更多开销。

import geopandas as gpd
import pandas as pd
import shapely.geometry
import math
import json
import plotly.express as px
import svgpath2mpl
import requests
import numpy as np

d = {
    "Location": ["Point A", "Point B"],
    "lat": [30, 40],
    "long": [-90, -80],
    "z": [100, 200],
}
df = pd.DataFrame(data=d)

fig = px.density_mapbox(
    df,
    lat="lat",
    lon="long",
    z="z",
    hover_name="Location",
    center=dict(lat=38.5, lon=-96),
    range_color=[0, 200],
    zoom=2,
    radius=50,
    opacity=0.5,
    mapbox_style="open-street-map",
)

# 
def polygon(sides, radius=1, rotation=0, translation=None):
    one_segment = math.pi * 2 / sides

    points = [(math.sin(one_segment * i + rotation) * radius,
               math.cos(one_segment * i + rotation) * radius,)
              for i in range(sides)]

    if translation:
        points = [[sum(pair) for pair in zip(point, translation)] for point in points]

    return shapely.geometry.Polygon(points)

def makimarker(makiname="star", geo=(0, 0), size=0.1):
    url = f"https://raw.githubusercontent.com/mapbox/maki/main/icons/{makiname}.svg"
    svgpath = pd.read_xml(requests.get(url).text).loc[0, "d"]
    p = svgpath2mpl.parse_path(svgpath).to_polygons()
    # need centroid to adjust marked to be centred on geo location
    c = shapely.affinity.scale(
        shapely.geometry.Polygon(p[0]), xfact=size, yfact=size
    ).centroid
    # centre and place marker
    marker = shapely.geometry.Polygon(
        [[sum(triple) for triple in zip(point, geo, (-c.x, -c.y))] for point in p[0]]
    )
    # finally size geometry
    return shapely.affinity.scale(marker, xfact=size, yfact=size)


def get_geom(long_a: list, lat_a: list, marker=None, size=0.15) -> list:
    if marker:
        geo = [
            makimarker(marker, geo=(long, lat), size=size)
            for long, lat in zip(long_a, lat_a)
        ]
    else:
        geo = [
            polygon(3, translation=(long, lat), radius=size*10)
            for long, lat in zip(long_a, lat_a)
        ]
    return json.loads(gpd.GeoDataFrame(geometry=geo).to_json())

# basing math on this https://wiki.openstreetmap.org/wiki/Zoom_levels
# dict is keyed by size with min/max zoom levels covered by this size
MINZOOM=.1
MAXZOOM=18
LAYERS=7
zoom = 512**np.linspace(math.log(MINZOOM,512), math.log(MAXZOOM, 512), LAYERS)
zoom = {
    (200/(2**(np.percentile(zoom[i:i+2],25)+9))): {"minzoom":zoom[i], "maxzoom":zoom[i+1], "name":i}
    for i in range(LAYERS-1)
}

# add a layers to density plot that are the markers
fig.update_layout(
    mapbox={
        "layers": [
            {
                "source": get_geom(df["long"], df["lat"], marker="star", size=k),
                "type": "fill",
                "color": "blue",
                **zoom[k],
            }
            for k in zoom.keys()
        ]
    },
    margin={"t": 0, "b": 0, "l": 0, "r": 0},
)
fig