向Dash Leaflet地图通过回调添加新多边形

huangapple go评论62阅读模式
英文:

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.GeoJSONdata属性返回了错误的数据类型。

将这行代码更改为:

# 转换回 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!

huangapple
  • 本文由 发表于 2023年6月1日 11:03:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/76378414.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定