Skip to content

Commit

Permalink
Added support for editing vector data opengeos#178 opengeos#179
Browse files Browse the repository at this point in the history
  • Loading branch information
giswqs committed Jan 29, 2022
1 parent 546c968 commit f632176
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 13 deletions.
13 changes: 12 additions & 1 deletion leafmap/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2408,13 +2408,15 @@ def screen_capture(outfile, monitor=1):
raise Exception(e)


def gdf_to_geojson(gdf, out_geojson=None, epsg=None):
def gdf_to_geojson(gdf, out_geojson=None, epsg=None, tuple_to_list=False):
"""Converts a GeoDataFame to GeoJSON.
Args:
gdf (GeoDataFrame): A GeoPandas GeoDataFrame.
out_geojson (str, optional): File path to he output GeoJSON. Defaults to None.
epsg (str, optional): An EPSG string, e.g., "4326". Defaults to None.
tuple_to_list (bool, optional): Whether to convert tuples to lists. Defaults to False.
Raises:
TypeError: When the output file extension is incorrect.
Expand All @@ -2425,11 +2427,20 @@ def gdf_to_geojson(gdf, out_geojson=None, epsg=None):
"""
check_package(name="geopandas", URL="https://geopandas.org")

def listit(t):
return list(map(listit, t)) if isinstance(t, (list, tuple)) else t

try:
if epsg is not None:
gdf = gdf.to_crs(epsg=epsg)
geojson = gdf.__geo_interface__

if tuple_to_list:
for feature in geojson["features"]:
feature["geometry"]["coordinates"] = listit(
feature["geometry"]["coordinates"]
)

if out_geojson is None:
return geojson
else:
Expand Down
18 changes: 9 additions & 9 deletions leafmap/leafmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,20 +134,20 @@ def handle_draw(target, action, geo_json):
for feature in self.draw_features:
if feature["geometry"] not in geometries:
self.draw_features.remove(feature)
self.user_rois = {
"type": "FeatureCollection",
"features": self.draw_features,
}

if self.edit_mode:
with self.edit_output:
self.edit_output.clear_output()
# ipysheet.column(1, [""] * self.num_attributes)
self.edit_sheet = ipysheet.from_dataframe(
self.get_draw_props(n=self.num_attributes, return_df=True)
)
display(self.edit_sheet)

self.user_rois = {
"type": "FeatureCollection",
"features": self.draw_features,
}

draw_control.on_draw(handle_draw)

if "measure_control" not in kwargs:
Expand Down Expand Up @@ -2862,10 +2862,10 @@ def update_draw_props(self, df):
self.draw_features[-1]["properties"] = props
elif self.draw_control.last_action == "edited":
for feature in self.draw_features:
if self.draw_control.last_draw:
self.draw_control.last_draw["geometry"] == feature[
"geometry"
]
if (
self.draw_control.last_draw["geometry"]
== feature["geometry"]
):
feature["properties"] = props
for prop in list(props.keys()):
if prop not in self.edit_props:
Expand Down
93 changes: 90 additions & 3 deletions leafmap/toolbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -4103,20 +4103,35 @@ def edit_draw_gui(m):
layout=widgets.Layout(height="28px", width="28px", padding="0px 0px 0px 4px"),
)

open_button = widgets.ToggleButton(
value=False,
tooltip="Open vector data",
icon="folder-open",
layout=widgets.Layout(height="28px", width="28px", padding="0px 0px 0px 4px"),
)

save_button = widgets.ToggleButton(
value=False,
tooltip="Save to file",
icon="floppy-o",
layout=widgets.Layout(height="28px", width="28px", padding="0px 0px 0px 4px"),
)

refresh_button = widgets.ToggleButton(
value=False,
tooltip="Get attribute",
icon="refresh",
layout=widgets.Layout(height="28px", width="28px", padding="0px 0px 0px 4px"),
)
m.edit_refresh = refresh_button

int_slider = widgets.IntSlider(
min=n_props,
max=n_props + 10,
description="Attributes:",
description="Rows:",
readout=False,
continuous_update=True,
layout=widgets.Layout(width="135px", padding=padding),
layout=widgets.Layout(width="85px", padding=padding),
style=style,
)

Expand All @@ -4129,7 +4144,7 @@ def edit_draw_gui(m):
tooltips=["Apply", "Reset", "Close"],
button_style="primary",
)
buttons.style.button_width = "60px"
buttons.style.button_width = "64px"

with output:
output.clear_output()
Expand All @@ -4155,7 +4170,9 @@ def int_slider_changed(change):
toolbar_header.children = [
close_button,
toolbar_button,
open_button,
save_button,
refresh_button,
int_slider,
int_slider_label,
]
Expand Down Expand Up @@ -4204,10 +4221,46 @@ def close_btn_click(change):

close_button.observe(close_btn_click, "value")

def open_chooser_callback(chooser):
with output:
import geopandas as gpd

gdf = gpd.read_file(chooser.selected)
geojson = gdf_to_geojson(gdf, epsg=4326, tuple_to_list=True)
m.draw_control.data = m.draw_control.data + (geojson["features"])
m.draw_features = m.draw_features + (geojson["features"])
open_button.value = False

if m.open_control in m.controls:
m.remove_control(m.open_control)
delattr(m, "open_control")

def open_btn_click(change):
if change["new"]:
save_button.value = False

open_chooser = FileChooser(
os.getcwd(),
sandbox_path=m.sandbox_path,
layout=widgets.Layout(width="454px"),
)
open_chooser.filter_pattern = ["*.shp", "*.geojson", "*.gpkg"]
open_chooser.use_dir_icons = True
open_chooser.register_callback(open_chooser_callback)

open_control = ipyleaflet.WidgetControl(
widget=open_chooser, position="topright"
)
m.add_control(open_control)
m.open_control = open_control

open_button.observe(open_btn_click, "value")

def chooser_callback(chooser):
m.save_draw_features(chooser.selected, indent=None)
if m.file_control in m.controls:
m.remove_control(m.file_control)
delattr(m, "file_control")
with output:
print(f"Saved to {chooser.selected}")

Expand All @@ -4233,6 +4286,38 @@ def save_btn_click(change):

save_button.observe(save_btn_click, "value")

def refresh_btn_click(change):
if change["new"]:
refresh_button.value = False
if m.draw_control.last_action == "edited":
with output:
geometries = [
feature["geometry"] for feature in m.draw_control.data
]
if len(m.draw_features) > 0:
if (
m.draw_features[-1]["geometry"]
== m.draw_control.last_draw["geometry"]
):
m.draw_features.pop()
for feature in m.draw_features:
if feature["geometry"] not in geometries:
feature["geometry"] = m.draw_control.last_draw["geometry"]
values = []
props = ipysheet.to_dataframe(m.edit_sheet)["Key"].tolist()
for prop in props:
if prop in feature["properties"]:
values.append(feature["properties"][prop])
else:
values.append("")
df = pd.DataFrame({"Key": props, "Value": values})
df.index += 1
m.edit_sheet = ipysheet.from_dataframe(df)
output.clear_output()
display(m.edit_sheet)

refresh_button.observe(refresh_btn_click, "value")

def button_clicked(change):
if change["new"] == "Apply":
with output:
Expand All @@ -4241,6 +4326,8 @@ def button_clicked(change):
if len(m.draw_control.data) == 0:
print("Please draw a feature first.")
else:
if m.draw_control.last_action == "edited":
m.update_draw_features()
m.update_draw_props(ipysheet.to_dataframe(m.edit_sheet))
elif change["new"] == "Reset":

Expand Down

0 comments on commit f632176

Please sign in to comment.