使用 python dash-leaflet 高效过滤 GeoJSON 组件

Efficient filtering of GeoJSON components with python dash-leaflet

我正在使用 Dash Leaflet 地图组件 dash-leaflet 进行交互式地图可视化。

我的目标是通过破折号组件(例如 dcc.Slider)的值过滤大型 GeoJSON 组件(dl.GeoJSON)。

我目前的做法如下:

import random
import dash
import dash_html_components as html
import dash_leaflet as dl
import dash_leaflet.express as dlx
import dash_core_components as dcc
from dash.dependencies import Input, Output

# Create some markers.
points = [dict(lat=55.5 + random.random(), lon=9.5 + random.random(), value=random.random()*100) for i in range(100)]
data = dlx.dicts_to_geojson(points)

app = dash.Dash()
app.layout = html.Div([
    dl.Map([
        dl.TileLayer(),
        dl.GeoJSON(id="data-id", data=data)
        ], center=(56, 10), zoom=8, style={'height': '50vh'}),
    html.Div([
        html.H5('Filtering'),
        dcc.Slider(id='my-slider', min=0, max=100, step=1, value=100),
        html.Div(id='slider-output-container')
        ], style={'width': '30%'}),
    ])

@app.callback(
    Output('slider-output-container', 'children'),
    Output('data-id', 'data'),
    [Input('my-slider', 'value')])
def update_output(value):
    points_new = [p for p in points if p['value'] <= value]
    data_new = dlx.dicts_to_geojson(points_new)
    return 'You have selected value "{}"'.format(value), data_new

if __name__ == '__main__':
    app.run_server()

在此示例中,GeoJSON 组件“data-id”的数据对象通过根据输入值过滤点列表并返回新的破折号组件“my-slider”的值进行过滤使用 dlx.dicts_to_geojson 函数创建的 geoJSON 对象:

points_new = [p for p in points if p['value'] <= value]
data_new = dlx.dicts_to_geojson(points_new)

这是过滤 geoJSON 对象的正确方法吗?

我可以想象有更好的方法可以在客户端使用 GeoJSON 组件的 options-feature 和 javascript 函数来定义这样的过滤器函数,但我不知道怎么样。

我很感激我能得到的任何 advice/code 例子。

虽然可以过滤 Python 中的数据,但它会根据数据大小引入显着的网络开销(每次过滤器更改时,数据都会从服务器传输到客户端)。如果你做过滤客户端,你只需要传输一次数据,即性能差异可能是巨大的。

客户端过滤可以通过添加 JavaScript 资产(即放置在资产文件夹中的 .js 文件)实现,过滤功能按照 the documentation,

window.myNamespace = Object.assign({}, window.myNamespace, {  
    mySubNamespace: {  
        filter_features: function(feature, context) {
            // code should return true if feature is included, otherwise false
            const value = context.props.hideout['value']
            ...
        }  
    }  
});

要应用过滤,将函数句柄传递给过滤函数到 GeoJSON 组件,

import dash_leaflet as dl
from dash_extensions.javascript import Namespace
...
ns = Namespace("myNamespace ", "mySubNamespace")
dl.GeoJSON(id="geojson", options=dict(filter=ns("filter_features"), ...)

最后,通过使过滤器依赖于 hideout 道具(如上面的示例代码所示),您可以通过回调更新此道具来实现交互性,

@app.callback(Output("geojson", "hideout"), ...)
def update(...):
    ...
    return {"value": value}

编辑:根据评论中的要求,这是一个独立的小示例,演示仅使用客户端逻辑进行交互式 geojson 过滤,

import dash_html_components as html
import dash_leaflet as dl
import dash_core_components as dcc
import dash_leaflet.express as dlx
from dash import Dash
from dash.dependencies import Output, Input
from dash_extensions.javascript import assign

# A few cities in Denmark.
cities = [dict(name="Aalborg", lat=57.0268172, lon=9.837735),
          dict(name="Aarhus", lat=56.1780842, lon=10.1119354),
          dict(name="Copenhagen", lat=55.6712474, lon=12.5237848)]
# Create drop down options.
dd_options = [dict(value=c["name"], label=c["name"]) for c in cities]
dd_defaults = [o["value"] for o in dd_options]
# Generate geojson with a maker for each city and name as tooltip.
geojson = dlx.dicts_to_geojson([{**c, **dict(tooltip=c['name'])} for c in cities])
# Create javascript function that filters on feature name.
geojson_filter = assign("function(feature, context){return context.props.hideout.includes(feature.properties.name);}")
# Create example app.
app = Dash()
app.layout = html.Div([
    dl.Map(children=[
        dl.TileLayer(),
        dl.GeoJSON(data=geojson, options=dict(filter=geojson_filter), hideout=dd_defaults, id="geojson")
    ], style={'width': '100%', 'height': '50vh', 'margin': "auto", "display": "block"}, id="map"),
    dcc.Dropdown(id="dd", value=dd_defaults, options=dd_options, clearable=False, multi=True)
])
# Link drop down to geojson hideout prop (could also be done with a normal callback).
app.clientside_callback("function(x){return x;}", Output("geojson", "hideout"), Input("dd", "value"))

if __name__ == '__main__':
    app.run_server()

注意需要dash-extensions==0.0.55.