英文:
Add New Polygon to Dash Leaflet Map via a Callback
问题
我对使用GIS数据(使用Dash Leaflet和GeoPandas)还很陌生,目前遇到了一些困难。
我的目标是创建一个简单的应用程序,实现以下功能:
- 应用程序以空的
dash_leaflet.Map()
图形和一个名为“Buffer Distance”的数字输入框(默认值为100
)开始。 - 用户在地图上绘制多边形,触发回调。
- 回调从地图中获取GeoJSON数据和“缓冲距离”。
- 使用GeoPandas导入GeoJSON数据并创建一个比用户绘制的多边形小的新多边形,距离为“缓冲距离”。
- 将这两个多边形(原始绘制的和经过缓冲处理的多边形)返回到地图,以便现在两者都显示在地图上。
我在最后一步遇到了困难,无法通过某种形式的“Output”将这两个多边形推送回地图。
这是我目前正在使用的应用程序代码:
import pandas as pd
from dash import Dash, dcc, html, Input, Output, State
import dash_leaflet as dl
import geopandas as gpd
lat1, lon1 = 36.215487, -81.674006
app = Dash()
input_details = html.Div([
html.Div([
html.Div(['Buffer Distance'], style={'width': '37%', 'display': 'inline-block'}),
dcc.Input(
value=100,
id="buffer-distance",
type='number',
placeholder='Required',
),
]),
])
default_map_children = [
dl.TileLayer(),
dl.FeatureGroup([
dl.EditControl(id="edit_control"),
]),
dl.GeoJSON(id='map-geojsons')
]
map_input_results_tab = html.Div(
[
html.H2('Add Shapes to Map an Area of Interest'),
dl.Map(
id='leaflet-map',
style={'width': '100%', 'height': '50vh'},
center=[lat1, lon1],
zoom=16,
children=default_map_children
)
])
app.layout = html.Div([input_details, map_input_results_tab])
@app.callback(
Output('map-geojsons', 'data'),
Input('edit_control', 'geojson'),
State('buffer-distance', 'value'),
)
def update_estimates(drawn_geojson, perim_clear):
if any([x is None for x in [drawn_geojson, perim_clear]]):
# 未提供某个值,因此不继续计算
return drawn_geojson
elif not drawn_geojson["features"]:
# 未提供某个值,因此不继续计算
return drawn_geojson
gdf = gpd.GeoDataFrame.from_features(drawn_geojson["features"]) # 从UI中提取用户绘制的几何数据
gdf = gdf.set_crs(crs=4326) # 将初始CRS设置为指定这是经度/纬度数据
gdf = gdf.to_crs(
crs=gdf.estimate_utm_crs()) # 让GeoPandas估算最佳CRS并将其用于面积计算
# 创建一个使用缓冲区包含周边的新地理数据框
gdf_minus_perim_buffer = gdf['geometry'].buffer(-perim_clear)
combine_gdf = pd.concat([gdf['geometry'], gdf_minus_perim_buffer])
# 转回经度和纬度
combine_gdf = combine_gdf.to_crs(crs=4326)
# 转回GeoJSON以在dash leaflet地图中呈现
return_geojson_data = combine_gdf.to_json()
return return_geojson_data
if __name__ == '__main__':
app.run_server(debug=True, port=8052)
我认为我已经接近了,但似乎还缺少一些东西。在此提前感谢您的任何帮助!
英文:
Im very new to working with GIS data (using Dash Leaflet and GeoPandas) and am currently stumped.
My goal is to create a simple app which does the following:
- App starts with an empty dash_leaflet.Map() figure and a numeric input box titled "Buffer Distance" (with a default of
100
) - User draws a polygon on the map which fires a callback
- Callback takes in the GeoJSON data from the map and the "buffer distance"
- Use Geopandas to import the GeoJSON data and create a new polygon which is smaller than the user drawn polygon by "Buffer Distance"
- Pass these 2 polygons (originally drawn & post processed polygon with buffer) back to the map so that both are now displayed on the map
Im having trouble with the last step of pushing the two polygons back the map via some kind of Output
This is the app i am currently working with:
import pandas as pd
from dash import Dash, dcc, html, Input, Output, State
import dash_leaflet as dl
import geopandas as gpd
lat1, lon1 = 36.215487, -81.674006
app = Dash()
input_details = html.Div([
html.Div([
html.Div(['Buffer Distance'], style={'width': '37%', 'display': 'inline-block'}),
dcc.Input(
value=100,
id="buffer-distance",
type='number',
placeholder='Required',
),
]),
])
default_map_children = [
dl.TileLayer(),
dl.FeatureGroup([
dl.EditControl(id="edit_control"),
]),
dl.GeoJSON(id='map-geojsons')
]
map_input_results_tab = html.Div(
[
html.H2('Add Shapes to Map an Area of Interest'),
dl.Map(
id='leaflet-map',
style={'width': '100%', 'height': '50vh'},
center=[lat1, lon1],
zoom=16,
children=default_map_children
)
])
app.layout = html.Div([input_details, map_input_results_tab])
@app.callback(
Output('map-geojsons', 'data'),
Input('edit_control', 'geojson'),
State('buffer-distance', 'value'),
)
def update_estimates(drawn_geojson, perim_clear):
if any([x is None for x in [drawn_geojson, perim_clear]]):
# some value has not been provided, so do not continue with calculations
return drawn_geojson
elif not drawn_geojson["features"]:
# some value has not been provided, so do not continue with calculations
return drawn_geojson
gdf = gpd.GeoDataFrame.from_features(drawn_geojson["features"]) # extract user drawn geometry data from UI
gdf = gdf.set_crs(crs=4326) # Set the initial CRS to specify that this is lat/lon data
gdf = gdf.to_crs(
crs=gdf.estimate_utm_crs()) # Let GeoPandas estimate the best CRS and use that for the area calculation
# create a new geodataframe using buffer that incorporates the perimeter
gdf_minus_perim_buffer = gdf['geometry'].buffer(-perim_clear)
combine_gdf = pd.concat([gdf['geometry'], gdf_minus_perim_buffer])
# convert back to lat, long
combine_gdf = combine_gdf.to_crs(crs=4326)
# convert back to GeoJSON to be rendered in the dash leaflet map
return_geojson_data = combine_gdf.to_json()
return return_geojson_data
if __name__ == '__main__':
app.run_server(debug=True, port=8052)
I think I am close, but am just missing something.. Thanks in advance for any help!
答案1
得分: 0
看起来上面的回调方法是有效的,我只是向dl.GeoJSON
的data
属性返回了错误的数据类型。
将这行代码更改为:
# 转换回 GeoJSON 以在 Dash Leaflet 地图中呈现
return_geojson_data = combine_gdf.__geo_interface__
完美地解决了问题!
英文:
It looks like the callback approach above is valid, I was just providing the wrong data type back to the dl.GeoJSON
's data
attribute .
Changing this line:
# convert back to GeoJSON to be rendered in the dash leaflet map
return_geojson_data = combine_gdf.to_json()
to
# convert back to GeoJSON to be rendered in the dash leaflet map
return_geojson_data = combine_gdf.__geo_interface__
worked perfectly!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论