Editing GeoJSON features (linestrings) interactively in a map

Hello all,

I am aiming to create a simple, interactive editor of geometric features within the context of a map, and I am looking at various options for doing so. I only need to edit linestrings (polylines) and points. This will be based on GeoJSON objects.

I am currently looking at Dash Leaflet, which looks (to me) to be the most promising solution for my need. I have also looked at other solutions. A precursor regarding myself is that I am not familiar with the use of Leaflet natively, so I am on a learning curve both with regards to Dash Leaflet and Leaflet itself.

So - I think I can figure out how to interactively edit (i.e. move) a point that has been declared explicitly. But I canā€™t figure out how to interactively edit a polyline which has come into Dash Leaflet as a GeoJSON object by file. I havenā€™t tried points coming from a GeoJSON yet. I have tried a rather naive example based on an example on the Dash Leaflet Documentation Site, and whilst this small app is doing partly what I want it to do, it does not allow me to edit points of that polyline. What I wanted to achieve with the example attached was to:

  • allow for creation of new features from scratch using the editor. I have that one under control through Emilā€™s example - at least I believe so.
  • ā€˜promoteā€™ existing features imported from a GeoJSON object to be edited by ā€™ deleting it from the GeoJSON object and then putting it into the editControl variable.
  • allow for dragging and dropping of every point along that polyline.

I did not get that far - I can append to the editable version, but I canā€™t edit. Anyone who can point me in the right direction? The attached example is for illustration purposes only - I may well be barking up the wrong tree.

Best regards,
KjetilF

Code for illustration purposes:

import geojson
import dash_html_components as html
import dash_leaflet as dl
from dash import Dash
from dash.dependencies import Output, Input, State
from dash_extensions.javascript import assign
from dash.exceptions import PreventUpdate
from dash_extensions.javascript import arrow_function

How to render geojson.

point_to_layer = assign(""ā€œfunction(feature, latlng, context){
const p = feature.properties;
if(p.type === ā€˜circlemarkerā€™){return L.circleMarker(latlng, radius=p._radius)}
if(p.type === ā€˜circleā€™){return L.circle(latlng, radius=p._mRadius)}
return L.marker(latlng);
}ā€"")

filename = ā€˜ā€¦/data/builder/TwoLineStringFeatures.geojsonā€™
with open(filename) as json_file:
cableNetwork = geojson.load(json_file)
print(cableNetwork)

cables_in_map = dl.GeoJSON(data=cableNetwork,
zoomToBounds=True,
zoomToBoundsOnClick=True,
options=dict(color=ā€˜redā€™, pointToLayer=point_to_layer),
hoverStyle=arrow_function(dict(weight=5, color=ā€™#666ā€™, dashArray=ā€™ā€™)),
id=ā€˜builder-cables-in-mapā€™
)

app = Dash()
app.layout = html.Div([
# Setup a map with the edit control.
dl.Map(center=[58, 10.2], zoom=4, children=[
dl.TileLayer(), dl.FeatureGroup([
dl.EditControl(id=ā€œedit_controlā€), cables_in_map, dl.Marker(id=ā€œmarkerā€, position=[59.9


, 10.5], draggable=True)]),
], style={ā€˜widthā€™: ā€˜50%ā€™, ā€˜heightā€™: ā€˜50vhā€™, ā€˜marginā€™: ā€œautoā€, ā€œdisplayā€: ā€œinline-blockā€}, id=ā€œmapā€),
# Setup another map to that mirrors the edit control geometries using the GeoJSON component.
dl.Map(center=[56, 10], zoom=4, children=[
dl.TileLayer(), dl.GeoJSON(id=ā€œgeojsonā€, options=dict(pointToLayer=point_to_layer), zoomToBounds=True),
], style={ā€˜widthā€™: ā€˜50%ā€™, ā€˜heightā€™: ā€˜50vhā€™, ā€˜marginā€™: ā€œautoā€, ā€œdisplayā€: ā€œinline-blockā€}, id=ā€œmirrorā€),
])

@app.callback(Output(component_id=ā€œgeojsonā€, component_property=ā€œdataā€),
Input(component_id=ā€œedit_controlā€, component_property=ā€œgeojsonā€))
def mirror(x):
if not x:
raise PreventUpdate
print(x)
return x

@app.callback(Output(component_id=ā€˜builder-cables-in-mapā€™, component_property=ā€˜dataā€™),
Output(component_id=ā€œedit_controlā€, component_property=ā€œgeojsonā€),
Input(component_id=ā€œbuilder-cables-in-mapā€, component_property=ā€œclick_featureā€),
State(component_id=ā€˜builder-cables-in-mapā€™, component_property=ā€˜dataā€™),
State(component_id=ā€˜edit_controlā€™, component_property=ā€˜geojsonā€™),
prevent_initial_call=True)
def update_map_view(clickFeature, cablesInMap, editControl):
if clickFeature is not None and editControl is not None:
editControl[ā€˜featuresā€™].append(clickFeature)
cablesInMap[ā€˜featuresā€™].remove(clickFeature)
return cablesInMap, editControl

if name == ā€˜mainā€™:
app.run_server()

I am not sure i understand your question? The example in the documentation illustrates how to create features from scratch (via the draw buttons), how to promote existing features (by placing them as children of the featuregroup) and how edit existing features (via the edit button). Could you clarify what is missing?
Jul-08-2021 20-56-16

1 Like

The most probable answer to what is missing is that nothing is missing, and that I have missed something, but I have spent some time on it. Keep in mind that I am on a learning curve here.

Yes, I can create features from scratch, and I can edit points in existing point features. But I just canā€™t figure out how to edit individual points in polylines. I have a suspicious feeling I am thinking that it must be more complex than it is. I import those polylines as GeoJSON features.

KjetilF

1 Like