diff --git a/leafmap/common.py b/leafmap/common.py index b871037810..01ec21e1f7 100644 --- a/leafmap/common.py +++ b/leafmap/common.py @@ -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. @@ -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: diff --git a/leafmap/leafmap.py b/leafmap/leafmap.py index 23c5f56ece..37140c9997 100644 --- a/leafmap/leafmap.py +++ b/leafmap/leafmap.py @@ -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: @@ -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: diff --git a/leafmap/toolbar.py b/leafmap/toolbar.py index c47d9206e6..3b50ad0950 100644 --- a/leafmap/toolbar.py +++ b/leafmap/toolbar.py @@ -4103,6 +4103,13 @@ 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", @@ -4110,13 +4117,21 @@ def edit_draw_gui(m): 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, ) @@ -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() @@ -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, ] @@ -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}") @@ -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: @@ -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":