如何用Streamlit与Plotly Express绘制印尼GeoJSON地理数据?
印尼城市/省份GeoJSON地图在Streamlit+Plotly中显示为空的解决方法
问题描述
尝试用Streamlit和Plotly Express绘制印尼城市与省份的GeoJSON数据,数据来自DuckDB数据库,绘图前会与额外数据合并。转换为GeoJSON后地图能正常显示,但无任何数据渲染。
已尝试的操作
- 检查GeoJSON与DataFrame的索引是否匹配
- 在
px.choropleth_mapbox中使用featureidkey参数 - 验证索引与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
调试信息样本
indonesia_map前10行数据
{'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)>}}
merged_data前10行数据
{'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}}
GeoJSON前500字符
{'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':
问题分析与解决方案
核心问题
你使用了px.choropleth_mapbox来绘制点要素,但该函数是专门用于渲染**面状区域(如省份、城市边界)**的填充地图,无法识别点几何数据,因此导致数据无法渲染。
修正步骤
- 替换绘图函数:将
px.choropleth_mapbox改为px.scatter_mapbox,该函数支持点数据的可视化,可根据数值设置点的颜色、大小等属性。 - 简化参数:散点图不需要
geojson、locations、featureidkey这些参数,直接使用数据中的纬度和经度列即可。 - 修正CRS设置:将CRS设置应用到最终的
merged_data,而非原始的indonesia_map。
修改后的绘图代码片段
# 修正CRS设置到最终数据集 merged_data.crs = "EPSG:4326" # 调试信息保留 st.write(f"Sample merged_data: {str(merged_data)[:500]}") # 替换为散点图绘制 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.8, size_max=15, # 可根据需求调整点的最大尺寸 labels={'diff_percentage':'Difference Percentage'}, center={"lat": -2, "lon": 118}, zoom=3.4, hover_data=['Name', 'diff_percentage']) # 鼠标悬停显示额外信息 st.plotly_chart(fig)
额外说明
- 如果你的目标是绘制区域填充地图,需要确保GeoDataFrame中的
geometry字段是多边形(Polygon/MultiPolygon)类型,而非点(Point)。 - 若坚持使用
choropleth_mapbox,需确认GeoJSON中的要素是面状,且featureidkey参数需指向GeoJSON要素的顶级id(即featureidkey="id",而非properties.id),同时locations参数需匹配DataFrame中对应的值。
内容的提问来源于stack exchange,提问作者ebuzz168
相关产品推荐
相关产品推荐

