【问题标题】:Draw a map and color the linestring based on column value绘制地图并根据列值为线串着色
【发布时间】:2021-12-22 07:03:57
【问题描述】:

我有一个 GPS 点“poyline_gdf”的地理数据框:

| geometry | 
| -------- | 
| POINT (7.30161 52.56024)    | 
| POINT (7.30007 52.55877)    | 
| ...     |

我想从这些点制作一个线串并将它们绘制在地图上。对于可视化,我使用了 folium(找到了一些教程)。我用这段代码画了点:

def add_markers(mapobj, gdf):
    coords = []
    for i, row in gdf.iterrows():
        coords.append([row.geometry.y, row.geometry.x])
    for coord in coords:
        folium.CircleMarker(location = coord,
                            radius = 2.5, 
                            fill = True,
                            fill_color = '#F50057',
                            fill_opacity = 0.75,
                            color = 'whitesmoke',
                            weight = 0.5).add_to(mapobj)
    return mapobj

f = folium.Figure(height = 400)
m = folium.Map([52.303333,8.02], zoom_start = 15, tiles='Cartodb dark_matter')
m.add_to(f)

add_markers(m, poyline_gdf)

然后,我找到了从这些点制作线串的方法:

def make_lines(gdf, df_out, i, geometry = 'geometry'):
    geom0 = gdf.loc[i][geometry]
    geom1 = gdf.loc[i + 1][geometry]

    
    start, end = [(geom0.x, geom0.y), (geom1.x, geom1.y)]
    line = LineString([start, end])
    
    # Create a DataFrame to hold record
    data = {'id': i,
            'geometry': [line]}
    df_line = pd.DataFrame(data, columns = ['id', 'geometry'])
    
    # Add record DataFrame of compiled records
    df_out = pd.concat([df_out, df_line])
    return df_out

df = pd.DataFrame(columns = ['id', 'geometry'])

# Loop through each row of the input point GeoDataFrame
x = 1
while x < len(polyline_gdf) - 1:
    df = make_lines(polyline_gdf, df, x)
    x = x + 1
    
crs = {'init': 'epsg:4326'}
gdf_line = GeoDataFrame(df, crs=crs)
gdf_line  = gdf_line.reset_index().drop(['index','id'],axis=1)

我得到了 Linestrings "gdf_line" 的这个地理数据框:

| geometry| 
| -------- | 
| LINESTRING (7.30007 52.55877, 7.29891 52.55521)   | 
| LINESTRING (7.29891 52.55521, 7.29502 52.55436)   | 
| ...   |

我可以在地图上显示它:

folium.GeoJson(gdf_line).add_to(m)
m

我在地图上看到了这条线: LineStrings on the map

我做了一些计算并向数据框添加了新列 - “coverage”,其中包含 0 到 1 之间的数值。现在,我的数据框如下所示:

| geometry| coverage|
| -------- | ---------|
| LINESTRING (7.30007 52.55877, 7.29891 52.55521)   | 0.86|
| LINESTRING (7.29891 52.55521, 7.29502 52.55436)   | 0.32|
|...|...|

我想对 LineStrings 进行与上图相同的可视化,但每个 LineString 的颜色应根据“coverage”列中的值而变化。例如,如果值超过 0.5 接近 1 (0.5-1),它们将从浅蓝色变为深蓝色。并且,如果值小于 0.5 接近 0 (0.5-0),它们将从深红色变为浅红色。任何帮助将不胜感激。如果有另一个可视化库的代码,那也适用于我。

【问题讨论】:

    标签: python geopandas


    【解决方案1】:
    • 您的问题并未真正提供可用的样本数据。已从 LINESTRING(法国边界)生成一组 POINT
    • 使用 Plotly Express 可以轻松满足您的映射要求。颜色要求实际上是关于从连续系列中创建离散值。为此,我使用了https://pandas.pydata.org/docs/reference/api/pandas.cut.html
    • 通常恕我直言,从 POINT 序列而不是 LINESTRING 创建线更简单。我在两个绘图示例中有效地提供了两种方式。 LINESTRING 在第二个解决方案中用作额外的 GEOJSON 层

    样本数据

    import shapely.wkt
    import shapely.geometry
    import geopandas as gpd
    import pandas as pd
    import numpy as np
    import plotly.express as px
    
    # make sample data as describe in question... points with coverage
    # just happens to be france boundary...
    ls = shapely.wkt.loads(
        "LINESTRING (6.186320428094177 49.46380280211451, 6.658229607783568 49.20195831969157, 8.099278598674744 49.01778351500333, 7.593676385131062 48.33301911070372, 7.466759067422231 47.62058197691181, 7.192202182655507 47.44976552997102, 6.736571079138059 47.54180125588285, 6.768713820023606 47.2877082383037, 6.037388950229001 46.72577871356187, 6.022609490593538 46.27298981382047, 6.500099724970426 46.42967275652944, 6.843592970414505 45.99114655210061, 6.802355177445605 45.70857982032864, 7.096652459347837 45.33309886329589, 6.749955275101655 45.02851797136758, 7.007562290076635 44.25476675066136, 7.549596388386107 44.12790110938481, 7.435184767291872 43.69384491634922, 6.52924523278304 43.12889232031831, 4.556962517931424 43.3996509873116, 3.100410597352663 43.07520050716705, 2.985998976258458 42.47301504166986, 1.826793247087153 42.34338471126569, 0.7015906103638941 42.79573436133261, 0.3380469091905809 42.57954600683955, -1.502770961910528 43.03401439063043, -1.901351284177764 43.42280202897834, -1.384225226232985 44.02261037859012, -1.193797573237418 46.01491771095486, -2.225724249673846 47.06436269793822, -2.963276129559603 47.57032664650795, -4.491554938159481 47.95495433205637, -4.592349819344776 48.68416046812699, -3.295813971357802 48.90169240985963, -1.616510789384961 48.64442129169454, -1.933494025063311 49.77634186461574, -0.98946895995536 49.34737580016091, 1.338761020522696 50.12717316344526, 1.6390010921385 50.9466063502975, 2.513573032246143 51.14850617126183, 2.658422071960274 50.79684804951575, 3.123251580425688 50.78036326761455, 3.588184441755658 50.37899241800356, 4.286022983425084 49.90749664977255, 4.799221632515724 49.98537303323637, 5.674051954784829 49.5294835475575, 5.897759230176348 49.44266714130711, 6.186320428094177 49.46380280211451)"
    )
    gdf = gpd.GeoDataFrame(geometry=[shapely.geometry.Point(p) for p in ls.coords]).assign(
        coverage=lambda d: np.linspace(0, 1, len(d))
    )
    
    geometry coverage
    5 POINT (7.192202182655507 47.44976552997102) 0.106383
    22 POINT (1.826793247087153 42.34338471126569) 0.468085
    0 POINT (6.186320428094177 49.46380280211451) 0
    24 POINT (0.3380469091905809 42.57954600683955) 0.510638
    25 POINT (-1.502770961910528 43.03401439063043) 0.531915
    16 POINT (7.549596388386107 44.12790110938481) 0.340426
    28 POINT (-1.193797573237418 46.01491771095486) 0.595745
    13 POINT (7.096652459347837 45.33309886329589) 0.276596
    40 POINT (2.658422071960274 50.79684804951575) 0.851064
    34 POINT (-1.616510789384961 48.64442129169454) 0.723404

    离散颜色

    # discrete colors for markers and lines
    px.line_mapbox(
        lat=gdf.geometry.y,
        lon=gdf.geometry.x,
        color=pd.cut(
            gdf["coverage"], bins=[0, 0.25, 0.5, 0.75, 1], labels=False, include_lowest=True
        ),
        color_discrete_map={0: "red", 1: "palevioletred", 2: "skyblue", 3: "blue"},
    ).update_traces(mode="lines+markers").update_layout(
        mapbox={
            "style": "carto-positron",
            "zoom": 4,
        },
        margin={"l": 0, "r": 0, "t": 0, "r": 0},
    )
    
    

    标记的颜色连续,线条的颜色离散

    # aggregate to lines based on coverage...
    gdf2 = gdf.groupby(
        pd.cut(
            gdf["coverage"], bins=[0, 0.25, 0.5, 0.75, 1], labels=False, include_lowest=True
        )
    ).agg({"geometry": lambda s: shapely.geometry.LineString(s.values)})
    
    # continuous colors for markers and discrete for lines
    px.scatter_mapbox(
        gdf,
        lat=gdf.geometry.y,
        lon=gdf.geometry.x,
        color="coverage",
        color_continuous_scale=[
            (0, "red"),
            (0.33, "palevioletred"),
            (0.66, "skyblue"),
            (1, "blue"),
        ],
        hover_data={"coverage":":.2f"}
    ).update_layout(
        mapbox={
            "style": "carto-positron",
            "zoom": 4,
            "layers": [
                {
                    "source": gdf2.loc[i, "geometry"].__geo_interface__,
                    "type": "line",
                    "color": {0: "red", 1: "palevioletred", 2: "skyblue", 3: "blue"}[i],
                }
                for i in gdf2.index
            ],
        },
        margin={"l": 0, "r": 0, "t": 0, "r": 0},
    )
    

    【讨论】:

    • 感谢您的回答。在我的情况下点没有覆盖的东西。只有最后一个地理数据名具有覆盖范围。带有 LineStrings 的那个,所以一个 LineString 连接两个 Points,并且覆盖范围仅适用于这条线。我尝试将您的代码修改为 LineStrings,我将在下面显示数据示例
    • 我已经修改并添加为新答案。我会删除这个答案。
    【解决方案2】:

    我认为如果我输入整个 LineString,而不是 lat,lng,它可能会起作用:

    px.line_mapbox(
        gdf['geometry'],
        color=pd.cut(
            gdf["coverage"], bins=[0, 0.25, 0.5, 0.75, 1], labels=False, include_lowest=True
        ),
        color_discrete_map={0: "red", 1: "palevioletred", 2: "skyblue", 3: "blue"},
    ).update_traces(mode="lines+markers").update_layout(
        mapbox={
            "style": "carto-positron",
            "zoom": 4,
        },
        margin={"l": 0, "r": 0, "t": 0, "r": 0},
    )
    

    下面,我想显示我要绘制的数据框: Dataframe-gdf

    【讨论】:

      【解决方案3】:

      样本数据gdf2

      geometry coverage
      LINESTRING (6.186320428094177 49.46380280211451, 6.658229607783568 49.20195831969157) 0.506365
      LINESTRING (6.658229607783568 49.20195831969157, 8.099278598674744 49.01778351500333) 0.692759
      LINESTRING (7.593676385131062 48.33301911070372, 7.466759067422231 47.62058197691181) 0.0902211
      LINESTRING (8.099278598674744 49.01778351500333, 7.593676385131062 48.33301911070372) 0.692394
      LINESTRING (7.466759067422231 47.62058197691181, 7.192202182655507 47.44976552997102) 0.798098
      LINESTRING (7.192202182655507 47.44976552997102, 6.736571079138059 47.54180125588285) 0.0847655
      LINESTRING (6.736571079138059 47.54180125588285, 6.768713820023606 47.2877082383037) 0.17487
      LINESTRING (6.768713820023606 47.2877082383037, 6.037388950229001 46.72577871356187) 0.438581
      LINESTRING (6.037388950229001 46.72577871356187, 6.022609490593538 46.27298981382047) 0.157142
      LINESTRING (6.022609490593538 46.27298981382047, 6.500099724970426 46.42967275652944) 0.207318

      解决方案

      import shapely.wkt
      import shapely.geometry
      import geopandas as gpd
      import pandas as pd
      import numpy as np
      import plotly.express as px
      
      # make sample data as describe in question... points with coverage
      # just happens to be france boundary...
      ls = shapely.wkt.loads(
          "LINESTRING (6.186320428094177 49.46380280211451, 6.658229607783568 49.20195831969157, 8.099278598674744 49.01778351500333, 7.593676385131062 48.33301911070372, 7.466759067422231 47.62058197691181, 7.192202182655507 47.44976552997102, 6.736571079138059 47.54180125588285, 6.768713820023606 47.2877082383037, 6.037388950229001 46.72577871356187, 6.022609490593538 46.27298981382047, 6.500099724970426 46.42967275652944, 6.843592970414505 45.99114655210061, 6.802355177445605 45.70857982032864, 7.096652459347837 45.33309886329589, 6.749955275101655 45.02851797136758, 7.007562290076635 44.25476675066136, 7.549596388386107 44.12790110938481, 7.435184767291872 43.69384491634922, 6.52924523278304 43.12889232031831, 4.556962517931424 43.3996509873116, 3.100410597352663 43.07520050716705, 2.985998976258458 42.47301504166986, 1.826793247087153 42.34338471126569, 0.7015906103638941 42.79573436133261, 0.3380469091905809 42.57954600683955, -1.502770961910528 43.03401439063043, -1.901351284177764 43.42280202897834, -1.384225226232985 44.02261037859012, -1.193797573237418 46.01491771095486, -2.225724249673846 47.06436269793822, -2.963276129559603 47.57032664650795, -4.491554938159481 47.95495433205637, -4.592349819344776 48.68416046812699, -3.295813971357802 48.90169240985963, -1.616510789384961 48.64442129169454, -1.933494025063311 49.77634186461574, -0.98946895995536 49.34737580016091, 1.338761020522696 50.12717316344526, 1.6390010921385 50.9466063502975, 2.513573032246143 51.14850617126183, 2.658422071960274 50.79684804951575, 3.123251580425688 50.78036326761455, 3.588184441755658 50.37899241800356, 4.286022983425084 49.90749664977255, 4.799221632515724 49.98537303323637, 5.674051954784829 49.5294835475575, 5.897759230176348 49.44266714130711, 6.186320428094177 49.46380280211451)"
      )
      
      # start with points...
      gdf = gpd.GeoDataFrame(geometry=[shapely.geometry.Point(p) for p in ls.coords])
      
      # linestrings, pairs or points, A to B, B to C, ....
      gdf2 = pd.concat(
          [
              gdf.groupby(g).agg(
                  {
                      "geometry": lambda s: shapely.geometry.LineString(s.values),
                  }
              )
              for g in [gdf.index // 2, np.roll((gdf.index // 2).values, 1)]
          ]
      ).sort_index().reset_index(drop=True)
      
      # simulate coverage for a LINESTRING
      gdf2["coverage"] = np.random.uniform(0,1, len(gdf2))
      
      # prep data frame for plotting... sequences of co-ordinates delimited by None/NaN
      # using: https://stackoverflow.com/questions/30885005/pandas-series-of-lists-to-one-series
      df = pd.DataFrame(
          {
              col: gdf2.geometry.apply(lambda x: [p for p in x.xy[n]] + [None])
              .apply(pd.Series)
              .stack(dropna=False)
              .reset_index(drop=True)
              for n, col in enumerate(["lon", "lat"])
          }
      ).assign(
          coverage=np.repeat(gdf2["coverage"].values, 3), # assumes each linestring is two points
          coverage_bin=lambda d: pd.cut(
              d["coverage"], bins=[0, 0.25, 0.5, 0.75, 1], labels=False, include_lowest=True
          ),
      )
      
      # lines with discrete colors based of coverage_bin
      figl = px.line_mapbox(
          df,
          lat="lat",
          lon="lon",
          color="coverage_bin",
          color_discrete_map={0: "red", 1: "palevioletred", 2: "skyblue", 3: "blue"},
          hover_data={"coverage":":.2f"}
      ).update_layout(
          mapbox={
              "style": "carto-positron",
              "zoom": 4,
          },
          margin={"l": 0, "r": 0, "t": 0, "r": 0},
      )
      
      # markers based on continuous value of coverage
      figs = px.scatter_mapbox(
          df,
          lat="lat",
          lon="lon",
          color="coverage",
          color_continuous_scale=[
              (0, "red"),
              (0.33, "palevioletred"),
              (0.66, "skyblue"),
              (1, "blue"),
          ],
      ).update_layout(
          mapbox={
              "style": "carto-positron",
              "zoom": 4,
          },
          margin={"l": 0, "r": 0, "t": 0, "r": 0},
      )
      
      # bring lines and markers together
      figs.add_traces(figl.data).update_layout(showlegend=False)
      
      
      

      【讨论】:

      • 谢谢,很有帮助
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-07-19
      • 2016-09-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多