如何使用 Plotly Express Choropleth Mapbox 在 Streamlit 中绘制 GeoJSON 几何数据?

问题描述 投票:0回答:2

我正在尝试使用 Streamlit 和 Plotly Express 绘制印度尼西亚城市和省份的 GeoJSON 数据。我的数据来自 DuckDB 数据库,我在绘图之前将其与一些附加数据合并。我将合并的数据转换为 GeoJSON 格式以进行绘图,但地图显示为空。

我尝试过的:

  1. 检查 GeoJSON 和 DataFrame 索引是否匹配。
  2. featureidkey
    中使用了
    px.choropleth_mapbox
  3. 验证索引和 GeoJSON id 的数据类型是否匹配。

这是与绘图相关的片段:

class InteractiveMap:
    def __call__(self):
        return self.interactive_map()

    def interactive_map(self):
        st.title('Interactive Map of Indonesia')

        choice = st.selectbox("Choose between Cities and Provinces", ["Cities", "Provinces"])

        conn = duckdb.connect('oeroenremboog.db')
        cursor = conn.cursor()

        if choice == "Cities":
            cursor.execute("SELECT * FROM indonesia_cities;")
        else:
            cursor.execute("SELECT * FROM indonesia_provinces;")

        columns = [desc[0] for desc in cursor.description]
        indonesia_map = pd.DataFrame(cursor.fetchall(), columns=columns)

        if 'geometry' in indonesia_map.columns:
            indonesia_map['geometry'] = indonesia_map['geometry'].apply(wkb.loads, hex=True)

        indonesia_map = gpd.GeoDataFrame(indonesia_map, geometry='geometry')

        # Load the JSON data
        with open('src/data/diff_percentage_dm1_dm2.json') as f:
            diff_data = json.load(f)
        diff_df = pd.DataFrame(diff_data)

        name_column = 'Name' if choice == "Cities" else 'Propinsi'

        if name_column in indonesia_map.columns and 'kabupaten_tinggal' in diff_df.columns:
            merged_data = pd.merge(indonesia_map, diff_df, left_on=name_column, right_on='kabupaten_tinggal', how='left')
        else:
            st.error(f"Missing column {name_column} in either of the DataFrames.")
            return

        # Convert the 'diff_percentage' column to numeric
        merged_data['diff_percentage'] = pd.to_numeric(merged_data['diff_percentage'], errors='coerce')
        merged_data['diff_percentage'].fillna(0, inplace=True)

        indonesia_map.crs = "EPSG:4326"


        # Convert GeoDataFrame to GeoJSON
        geojson_data = json.loads(merged_data.to_json())

        # Debugging
        st.write(f"Sample GeoJSON: {str(geojson_data)[:500]}")
        st.write(f"Sample merged_data: {str(merged_data)[:500]}")
        
        # Plotting
        fig = px.choropleth_mapbox(merged_data, 
                                   geojson=geojson_data, 
                                   locations=merged_data.index,  # DataFrame index
                                   color='diff_percentage',
                                   color_continuous_scale="Viridis",
                                   range_color=(-100, 100),
                                   mapbox_style="carto-positron",
                                   opacity=0.5, 
                                   labels={'diff_percentage':'Difference Percentage'},
                                   center={"lat": -2, "lon": 118},
                                   zoom=3.4,
                                   featureidkey="properties.id")

        st.plotly_chart(fig)

        cursor.close()

        return merged_data

调试信息:

  • 示例 GeoJSON:(GeoJSON 数据的片段)
  • 唯一位置:(数据帧中的唯一位置)
  • 唯一 diff_percentage:(diff_percentage 列中的唯一值)

这是我的 indonesia_map DataFrame 转换为字典格式的前 10 行的示例:

Sample indonesia_map (first 10 rows as dict): {'Name': {0: 'SIMEULUE', 1: 'ACEH SINGKIL', 2: 'ACEH SELATAN', 3: 'ACEH TENGGARA', 4: 'ACEH TIMUR', 5: 'ACEH TENGAH', 6: 'ACEH BARAT', 7: 'ACEH BESAR', 8: 'PIDIE', 9: 'BIREUEN'}, 'latitude': {0: 2.613334894180298, 1: 2.349949598312378, 2: 3.1632587909698486, 3: 3.369655132293701, 4: 4.628895282745361, 5: 4.530141830444336, 6: 4.456692218780518, 7: 5.3799920082092285, 8: 5.068343639373779, 9: 5.093278884887695}, 'longitude': {0: 96.08564758300781, 1: 97.84710693359375, 2: 97.43519592285156, 3: 97.69552612304688, 4: 97.62864685058594, 5: 96.85894012451172, 6: 96.18546295166016, 7: 95.51558685302734, 8: 96.00715637207031, 9: 96.60938262939453}, 'geometry': {0: <POINT (96.086 2.613)>, 1: <POINT (97.847 2.35)>, 2: <POINT (97.435 3.163)>, 3: <POINT (97.696 3.37)>, 4: <POINT (97.629 4.629)>, 5: <POINT (96.859 4.53)>, 6: <POINT (96.185 4.457)>, 7: <POINT (95.516 5.38)>, 8: <POINT (96.007 5.068)>, 9: <POINT (96.609 5.093)>}}

Sample merged_data (first 10 rows as dict): {'Name': {0: 'SIMEULUE', 1: 'ACEH SINGKIL', 2: 'ACEH SELATAN', 3: 'ACEH TENGGARA', 4: 'ACEH TIMUR', 5: 'ACEH TENGAH', 6: 'ACEH BARAT', 7: 'ACEH BESAR', 8: 'PIDIE', 9: 'BIREUEN'}, 'latitude': {0: 2.613334894180298, 1: 2.349949598312378, 2: 3.1632587909698486, 3: 3.369655132293701, 4: 4.628895282745361, 5: 4.530141830444336, 6: 4.456692218780518, 7: 5.3799920082092285, 8: 5.068343639373779, 9: 5.093278884887695}, 'longitude': {0: 96.08564758300781, 1: 97.84710693359375, 2: 97.43519592285156, 3: 97.69552612304688, 4: 97.62864685058594, 5: 96.85894012451172, 6: 96.18546295166016, 7: 95.51558685302734, 8: 96.00715637207031, 9: 96.60938262939453}, 'geometry': {0: <POINT (96.086 2.613)>, 1: <POINT (97.847 2.35)>, 2: <POINT (97.435 3.163)>, 3: <POINT (97.696 3.37)>, 4: <POINT (97.629 4.629)>, 5: <POINT (96.859 4.53)>, 6: <POINT (96.185 4.457)>, 7: <POINT (95.516 5.38)>, 8: <POINT (96.007 5.068)>, 9: <POINT (96.609 5.093)>}, 'kabupaten_tinggal': {0: 'SIMEULUE', 1: 'ACEH SINGKIL', 2: 'ACEH SELATAN', 3: 'ACEH TENGGARA', 4: 'ACEH TIMUR', 5: 'ACEH TENGAH', 6: 'ACEH BARAT', 7: 'ACEH BESAR', 8: 'PIDIE', 9: 'BIREUEN'}, 'total_dm_tipe_i': {0: '236', 1: '165', 2: '464', 3: '194', 4: '117', 5: '231', 6: '167', 7: '1216', 8: '221', 9: '200'}, 'total_dm_tipe_ii': {0: '180', 1: '321', 2: '1076', 3: '256', 4: '447', 5: '343', 6: '916', 7: '1788', 8: '731', 9: '706'}, 'diff_percentage': {0: 0.1346153846153846, 1: -0.3209876543209876, 2: -0.3974025974025974, 3: -0.1377777777777777, 4: -0.5851063829787234, 5: -0.1951219512195122, 6: -0.6915974145891042, 7: -0.1904127829560586, 8: -0.5357142857142857, 9: -0.5584988962472405}}

Sample GeoJSON (first 500 characters): {'type': 'FeatureCollection', 'features': [{'id': '0', 'type': 'Feature', 'properties': {'Name': 'SIMEULUE', 'latitude': 2.613334894180298, 'longitude': 96.08564758300781, 'kabupaten_tinggal': 'SIMEULUE', 'total_dm_tipe_i': '236', 'total_dm_tipe_ii': '180', 'diff_percentage': 0.1346153846153846}, 'geometry': {'type': 'Point', 'coordinates': [96.08564793225175, 2.6133349583186596]}}, {'id': '1', 'type': 'Feature', 'properties': {'Name': 'ACEH SINGKIL', 'latitude': 2.349949598312378, 'longitude':

错误: 地图显示了,但它是空的——没有绘制任何数据。

如何使用 Streamlit 和 Plotly Express 正确绘制 GeoJSON 几何数据?

python plotly geojson streamlit plotly-express
2个回答
0
投票

看着 Mapbox Choropleth 地图,我看到:

如果几何图形包含多边形,

px.choropleth_mapbox 接受 GeoPandas 数据框的几何图形作为 geojson 的输入。

但是在您的数据集中,几何列包含点几何。
等值线地图根据数据集中的变量为多边形着色,但在这里,您提供的点几何图形不能被着色为区域。

要可视化点数据,您应该在地图上使用 散点图Plotly Express 为此提供了一个函数:

px.scatter_mapbox()

# Plotting
fig = px.scatter_mapbox(merged_data, 
                        lat='latitude', 
                        lon='longitude', 
                        color='diff_percentage',
                        color_continuous_scale="Viridis",
                        range_color=(-100, 100),
                        mapbox_style="carto-positron",
                        opacity=0.5, 
                        labels={'diff_percentage':'Difference Percentage'},
                        center={"lat": -2, "lon": 118},
                        zoom=3.4)

st.plotly_chart(fig)

与:

  • lat
    参数应该是你的纬度列的名称
  • lon
    参数应该是你的经度列的名称
  • color
    参数应该是包含要用于为点着色的变量的列的名称

如果您希望表示区域(例如省份或城市),则这些区域需要多边形(或多多边形)几何图形,而不是点几何图形。如果您拥有或可以获得此类数据,您可以按照最初的意图使用

px.choropleth_mapbox()
。您可以将此多边形数据与相应键上的现有数据合并,然后像进行分区统计图可视化一样继续操作。


0
投票
  1. 检查CRS(坐标参考系) 您已为 indonesia_map 设置 CRS,但未为 merged_data 设置 CRS。您应该确保两个 GeoDataFrame 的 CRS 相同。

    merged_data.crs =“EPSG:4326”

  2. GeoJSON 和 DataFrame 不匹配 GeoJSON 数据和 DataFrame 可能与预期不匹配。

您可以让 Plotly 处理它,而不是手动将 merged_data 转换为 GeoJSON。将 GeoDataFrame 直接传递给 geojson 参数。

fig = px.choropleth_mapbox(merged_data, 
                       geojson=merged_data.geometry, 
                       locations=merged_data.index,
                       ...)
  1. 功能 ID 密钥 确保 GeoJSON 中的要素 ID 与 DataFrame 的索引或 featureidkey 中指定的列匹配。

    featureidkey="属性。"

当然!您遇到的问题可能是由多种原因造成的。让我们分解一些可能的解决方案:

  1. 检查CRS(坐标参考系) 您已为 indonesia_map 设置 CRS,但未为 merged_data 设置 CRS。您应该确保两个 GeoDataFrame 的 CRS 相同。

蟒蛇 复制代码 merged_data.crs =“EPSG:4326” 2. GeoJSON 和 DataFrame 不匹配 GeoJSON 数据和 DataFrame 可能与预期不匹配。

您可以让 Plotly 处理它,而不是手动将 merged_data 转换为 GeoJSON。将 GeoDataFrame 直接传递给 geojson 参数。 蟒蛇

fig = px.choropleth_mapbox(merged_data, 
                               geojson=merged_data.geometry, 
                               locations=merged_data.index,  # DataFrame index
                               color='diff_percentage',
                               color_continuous_scale="Viridis",
                               range_color=(-100, 100),
                               mapbox_style="carto-positron",
                               opacity=0.5, 
                               labels={'diff_percentage':'Difference Percentage'},
                               featureidkey="properties.id",
                               center={"lat": -2, "lon": 118},
                               zoom=3.4)
  1. 功能 ID 密钥 确保 GeoJSON 中的要素 ID 与 DataFrame 的索引或 featureidkey 中指定的列匹配。

featureidkey=“属性”。 4. 数据类型 您提到检查数据类型,但请仔细检查 merged_data 中的索引和 GeoJSON 中的 ID 是否属于同一类型。

  1. 几何类型 确保 GeoJSON 几何类型(点、多边形等)符合您的预期。

  2. 调试 您可以通过绘制数据子集或将其打印出来来检查异常情况来进行调试。

以下是如何根据以下一些注意事项修改绘图部分:

fig = px.choropleth_mapbox(merged_data, 
                       geojson=merged_data.geometry, 
                       locations=merged_data.index,  # DataFrame index
                       color='diff_percentage',
                       color_continuous_scale="Viridis",
                       range_color=(-100, 100),
                       mapbox_style="carto-positron",
                       opacity=0.5, 
                       labels={'diff_percentage':'Difference Percentage'},
                       featureidkey="properties.id")  # Make sure this matches your DataFrame index or relevant column

如果您已尝试所有这些方法但仍然面临问题,那么查看数据示例以进一步调试会很有帮助。

© www.soinside.com 2019 - 2024. All rights reserved.