在Python的Mapbox中覆盖跟踪

2024-05-15 18:29:50 发布

您现在位置:Python中文网/ 问答频道 /正文

我目前正在从事一个地理项目,为此我必须对迁移流进行一些研究

我想使用Python和Mapbox表示迁移流,基于我以前下载的全球GeoJSON。但是,我在工作质量方面遇到了一些问题,无法找到合适的解决方案

我首先上传了world GeoJSON:

countries = json.load(open("countries_without_antartica.geojson"))

然后,我用一个函数提取坐标并将它们分组到一个名为countries_coords的列表中,其中countries_lons, countries_lats = zip(*countries_coords)

然后我开始创建图形

首先,我倡议:

fig = go.Figure()

然后,我将之前提取的信息放入ScatterMapbox环境:

fig.add_trace(go.Scattermapbox(
        mode='lines',
        name='Countries',
        fill='toself',
        fillcolor='lightgray',
        line=dict(color='black', width=1),
        lat=countries_lats,
        lon=countries_lons,
        opacity=1,
        showlegend=False,
        hoverinfo='skip',
))

然后我用:fig.update_layout(mapbox=dict(style='white-bg'))指定Mapbox样式

这使得地图只剩下GeoJSON数据,如图所示:IMAGE 1

然而,问题就从这里开始:然后我尝试在地图上添加一条线,指示第一次迁移流(在本例中,从西班牙到澳大利亚)。我使用以下代码执行此操作:

fig.add_trace(
        go.Scattermapbox(
            name='flow1',
            lon = [134.340916, -3.704239],
            lat = [-25.039402, 40.415887],
            mode = 'lines',
            line = dict(width = 8,color = 'green')
        )
)

然而,结果是:IMAGE 2

我对此有几个问题,因为迁移线应该是一条有点弯曲的线,而不是一条直线

我意识到解决这个问题的方法是使用go.Scattergeo而不是go.Scattermapbox来表示线,所以我做到了:

fig.add_trace(
        go.Scattergeo(
            name='flow1',
            lon = [134.340916, -3.704239],
            lat = [-25.039402, 40.415887],
            mode = 'lines',
            line = dict(width = 8,color = 'green')
        )
)

但是该线现在位于地图本身的“后面”,因此它不可见(再次生成图1)

带有go.Scattergeo的线是弯曲的,它确实代表了我想要它代表的东西,但它不可见,因为它在带有地图的go.ScatterMapbox图形后面“分层”

如何更改跟踪的顺序?有没有办法防止第一个跟踪“高于”第二个跟踪?我试着改变外观的顺序,但没有效果

编辑1 按照@NikolasStevenson Molnar和@BasvanderLinden提供的解决方案,我使用go.Scattergeo呈现了世界和迁移流。代码如下:

fig.add_trace(go.Scattergeo(
        mode='lines',
        name='Countries',
        fill='toself',
        fillcolor='lightgray',
        line=dict(color='black', width=1),
        lat=countries_lats,
        lon=countries_lons,
        opacity=1,
        showlegend=False,
        hoverinfo='skip',
))

fig.add_trace(
        go.Scattergeo(
            name='flow1',
            lon = [134.340916, -3.704239],
            lat = [-25.039402, 40.415887],
            mode = 'lines',
            line = dict(width = 8,color = 'green')
        )
)

这里,结果:IMAGE 3

正如你所看到的,这张地图并不像它应该的那样“伟大”。有关it质量的一些问题包括:

  1. 国家用与背景相同的颜色填充(即海洋)。我找不到一种方法,只能填补所有国家的空缺。在使用go.Scattermapbox时,可以通过指定所需的样式(fig.update_layout(mapbox=dict(style='white-bg')))轻松完成此操作。但是,“go.Scattergeo”没有该功能
  2. 地图似乎是水平延伸的(请参见图3中的所有国家如何比图1中的更宽)。这在北半球尤为明显

然后我想到问题1应该通过“关闭”填充心房来解决,所以我编码:

fig.add_trace(go.Scattergeo(
        mode='lines',
        name='Countries',
        line=dict(color='black', width=1),
        lat=countries_lats,
        lon=countries_lons,
        opacity=1,
        showlegend=False,
        hoverinfo='skip',
))

结果也是不可取的,因为GeoJSON是“go.Scattergeo”提供的默认映射。例如,当我放大西班牙时,我得到:IMAGE 4很明显,两个跟踪(default和GeoJSON)同时运行,使得最终结果不那么整洁。最重要的是,默认跟踪只显示“领土”,而不是“政治分裂”,因此-例如-葡萄牙不是在默认跟踪中绘制的,而是在GeoJSON中绘制的

希望这些额外的信息对达成适当的解决方案是有价值的

提前感谢您为我提供的任何帮助、建议或解决方案


Tags: nameaddgomodegeojsonlinefigtrace
1条回答
网友
1楼 · 发布于 2024-05-15 18:29:50
import requests
import geopandas as gpd
import plotly.express as px
from pathlib import Path
from zipfile import ZipFile
import json, io
from geographiclib.geodesic import Geodesic
import math

# source geojson for country boundaries so we can calc centroids
geosrc = pd.json_normalize(
    requests.get(
        "https://pkgstore.datahub.io/core/geo-countries/7/datapackage.json"
    ).json()["resources"]
)
fn = Path(geosrc.loc[geosrc["name"].eq("geo-countries_zip"), "path"].values[0]).name

if not Path.cwd().joinpath(fn).exists():
    r = requests.get(
        geosrc.loc[geosrc["name"].eq("geo-countries_zip"), "path"].values[0],
        stream=True,
    )
    with open(fn, "wb") as fd:
        for chunk in r.iter_content(chunk_size=128):
            fd.write(chunk)

zfile = ZipFile(fn)
with zfile.open(zfile.infolist()[0]) as f:
    geojson = json.load(f)

gdf = gpd.GeoDataFrame.from_features(geojson).set_index("ISO_A3")
# centroids...
gdf["lon"] = gdf.apply(lambda r: r.geometry.centroid.x, axis=1)
gdf["lat"] = gdf.apply(lambda r: r.geometry.centroid.y, axis=1)


def worldcircleline(gdf, country1, country2, fig=None, color="blue"):
    geod = Geodesic.WGS84  # define the WGS84 ellipsoid

    l = geod.InverseLine(
        gdf.loc[country1, "lat"],
        gdf.loc[country1, "lon"],
        gdf.loc[country2, "lat"],
        gdf.loc[country2, "lon"],
        Geodesic.LATITUDE | Geodesic.LONGITUDE,
    )

    da = 1
    n = int(math.ceil(l.a13 / da))
    da = l.a13 / n

    lat = [
        l.ArcPosition(
            da * i, Geodesic.LATITUDE | Geodesic.LONGITUDE | Geodesic.LONG_UNROLL
        )["lat2"]
        for i in range(n + 1)
    ]
    lon = [
        l.ArcPosition(
            da * i, Geodesic.LATITUDE | Geodesic.LONGITUDE | Geodesic.LONG_UNROLL
        )["lon2"]
        for i in range(n + 1)
    ]

    tfig = px.line_mapbox(
        lat=lat,
        lon=lon,
        mapbox_style="carto-positron",
        zoom=1,
    ).update_traces(line={"color":color})

    if fig is None:
        return tfig.update_layout(margin={"l": 0, "r": 0, "b": 0, "t": 0})
    else:
        return fig.add_traces(tfig.data)


fig = worldcircleline(gdf, "ESP", "AUS")
worldcircleline(gdf, "GBR", "SGP", fig=fig, color="red")
worldcircleline(gdf, "IRL", "USA", fig=fig, color="green")

enter image description here

相关问题 更多 >