From 8e22fd07b3bf36c450078fdcc7c45bb9590e4e80 Mon Sep 17 00:00:00 2001 From: 29riyasaxena <29riyasaxena@gmail.com> Date: Mon, 8 Jul 2024 19:12:57 +0530 Subject: [PATCH 01/15] Allow Users to draw simple geometries --- python/grass/jupyter/interactivemap.py | 91 ++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/python/grass/jupyter/interactivemap.py b/python/grass/jupyter/interactivemap.py index f5eae8588ff..1b76fb7f5a8 100644 --- a/python/grass/jupyter/interactivemap.py +++ b/python/grass/jupyter/interactivemap.py @@ -346,6 +346,95 @@ def add_layer_control(self, **kwargs): else: self.layer_control_object = self._ipyleaflet.LayersControl(**kwargs) + def setup_drawing_interface(self): + """ + Allow Users to draw Geometry and Save them. + """ + import ipywidgets as widgets + from IPython.display import display + + draw_control = self._ipyleaflet.DrawControl() + drawn_geometries = [] + out = widgets.Output() + save_button_control = None + + def handle_draw(target, action, geo_json): + with out: + if action == "created": + drawn_geometries.append(geo_json) + print(f"Geometry created: {geo_json}") + elif action == "deleted": + drawn_geometries[:] = [ + g + for g in drawn_geometries + if g["properties"]["id"] != geo_json["properties"]["id"] + ] + print(f"Geometry deleted: {geo_json}") + + draw_control.on_draw(handle_draw) + + draw_button = widgets.ToggleButton( + icon="pencil", + value=False, + tooltip="Click to draw geometries", + layout=widgets.Layout(width="33px", margin="0px 0px 0px 0px"), + ) + + def toggle_geometry(change): + if change["new"]: + self.map.add_control(draw_control) + show_interface() + else: + self.map.remove_control(draw_control) + hide_interface() + + draw_button.observe(toggle_geometry, names="value") + + def show_interface(): + nonlocal save_button_control + + name_input = widgets.Text(description="Name:") + save_button = widgets.Button( + description="Save Geometries", button_style="success" + ) + + def save_geometries(b): + name = name_input.value + if name and drawn_geometries: + for geometry in drawn_geometries: + geometry["properties"]["name"] = name + geo_json = { + "type": "FeatureCollection", + "features": drawn_geometries, + } + geo_json_layer = self._ipyleaflet.GeoJSON(data=geo_json, name=name) + self.map.add_layer(geo_json_layer) + print( + f"Saved {len(drawn_geometries)} geometries with name '{name}'" + ) + + save_button.on_click(save_geometries) + + save_button_control = self._ipyleaflet.WidgetControl( + widget=widgets.VBox([name_input, save_button]), position="topright" + ) + self.map.add_control(save_button_control) + + display(out) + + def hide_interface(): + nonlocal save_button_control + + out.clear_output() + if save_button_control: + self.map.remove_control(save_button_control) + save_button_control = None + + toggle_control = self._ipyleaflet.WidgetControl( + widget=draw_button, position="topright" + ) + self.map.add_control(toggle_control) + def show(self): """This function returns a folium figure or ipyleaflet map object with a GRASS raster and/or vector overlaid on a basemap. @@ -367,6 +456,8 @@ def show(self): return fig # ipyleaflet + if self._ipyleaflet: + self.setup_drawing_interface() self.map.add(self.layer_control_object) return self.map From 029fcf04b7216d36c6d0883af8babd740fa70b6d Mon Sep 17 00:00:00 2001 From: 29riyasaxena <29riyasaxena@gmail.com> Date: Tue, 9 Jul 2024 23:12:03 +0530 Subject: [PATCH 02/15] Allow user to save the drawn vectors --- python/grass/jupyter/interactivemap.py | 12 +++++++++--- python/grass/jupyter/utils.py | 10 ++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/python/grass/jupyter/interactivemap.py b/python/grass/jupyter/interactivemap.py index 1b76fb7f5a8..e18eacbfce6 100644 --- a/python/grass/jupyter/interactivemap.py +++ b/python/grass/jupyter/interactivemap.py @@ -16,6 +16,7 @@ import base64 import json from .reprojection_renderer import ReprojectionRenderer +from .utils import save_vector def get_backend(interactive_map): @@ -407,11 +408,16 @@ def save_geometries(b): "type": "FeatureCollection", "features": drawn_geometries, } + geojson_filename = f"/tmp/{name}.geojson" + with open(geojson_filename, "w") as f: + json.dump(geo_json, f) + try: + save_vector(geojson_filename, name) + print(f"Imported geometry with name '{name}' into GRASS GIS.") + except Exception as e: + print(f"Failed to import geometries into GRASS GIS: {e}") geo_json_layer = self._ipyleaflet.GeoJSON(data=geo_json, name=name) self.map.add_layer(geo_json_layer) - print( - f"Saved {len(drawn_geometries)} geometries with name '{name}'" - ) save_button.on_click(save_geometries) diff --git a/python/grass/jupyter/utils.py b/python/grass/jupyter/utils.py index f06adfa36d1..5c43eda9eda 100644 --- a/python/grass/jupyter/utils.py +++ b/python/grass/jupyter/utils.py @@ -202,6 +202,16 @@ def get_rendering_size(region, width, height, default_width=600, default_height= return (default_width, round(default_width * region_height / region_width)) +def save_vector(geojson_filename, name): + """ + Saves the user drawn vector. + + param geojson_filename: name of the geojson file to be saved + param name: name with which vector should be saved + """ + gs.run_command("v.import", input=geojson_filename, output=name) + + def save_gif( input_files, output_filename, From 9b0f836e8a047fde9841566bcd470e4be1be961e Mon Sep 17 00:00:00 2001 From: 29riyasaxena <29riyasaxena@gmail.com> Date: Thu, 11 Jul 2024 22:36:30 +0530 Subject: [PATCH 03/15] Change the way to save geojson file --- python/grass/jupyter/interactivemap.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/python/grass/jupyter/interactivemap.py b/python/grass/jupyter/interactivemap.py index e18eacbfce6..b593b0a749a 100644 --- a/python/grass/jupyter/interactivemap.py +++ b/python/grass/jupyter/interactivemap.py @@ -353,6 +353,7 @@ def setup_drawing_interface(self): """ import ipywidgets as widgets from IPython.display import display + import tempfile draw_control = self._ipyleaflet.DrawControl() drawn_geometries = [] @@ -412,8 +413,17 @@ def save_geometries(b): with open(geojson_filename, "w") as f: json.dump(geo_json, f) try: - save_vector(geojson_filename, name) + with tempfile.NamedTemporaryFile( + suffix=".geojson", delete=False + ) as temp_file: + temp_filename = temp_file.name + json.dump(geo_json, temp_file) + save_vector(temp_filename, name) print(f"Imported geometry with name '{name}' into GRASS GIS.") + geo_json_layer = self._ipyleaflet.GeoJSON( + data=geo_json, name=name + ) + self.map.add_layer(geo_json_layer) except Exception as e: print(f"Failed to import geometries into GRASS GIS: {e}") geo_json_layer = self._ipyleaflet.GeoJSON(data=geo_json, name=name) From 935262ac4f03e248982e9e2816b734c9d64fede0 Mon Sep 17 00:00:00 2001 From: 29riyasaxena <29riyasaxena@gmail.com> Date: Sun, 14 Jul 2024 13:31:54 +0530 Subject: [PATCH 04/15] Use GeomanDrawControl --- python/grass/jupyter/interactivemap.py | 255 ++++++++++++++++--------- 1 file changed, 167 insertions(+), 88 deletions(-) diff --git a/python/grass/jupyter/interactivemap.py b/python/grass/jupyter/interactivemap.py index b593b0a749a..184e47e4160 100644 --- a/python/grass/jupyter/interactivemap.py +++ b/python/grass/jupyter/interactivemap.py @@ -16,7 +16,6 @@ import base64 import json from .reprojection_renderer import ReprojectionRenderer -from .utils import save_vector def get_backend(interactive_map): @@ -347,34 +346,155 @@ def add_layer_control(self, **kwargs): else: self.layer_control_object = self._ipyleaflet.LayersControl(**kwargs) - def setup_drawing_interface(self): - """ - Allow Users to draw Geometry and Save them. - """ + # def setup_drawing_interface(self): + # """ + # Allow Users to draw Geometry and Save them. + # """ + # import ipywidgets as widgets + # from IPython.display import display + # import tempfile + # from ipyleaflet import WidgetControl, GeoJSON + # from ipyleaflet import GeomanDrawControl + + # draw_control = GeomanDrawControl( + # drawOptions={ + # 'polygon': True, + # 'rectangle': True, + # 'circle': False, + # 'marker': False, + # 'polyline': False + # }, + # editOptions={ + # 'edit': True, + # 'remove': True + # }, + # hide_controls=False # Ensure the toolbar is visible + # ) + + # drawn_geometries = [] + # out = widgets.Output() + # save_button_control = None + # name_input = None + # save_button = None + + # def handle_draw(target, action, geo_json): + # with out: + # if action in ["vertexadded", "featureadded"]: + # drawn_geometries.append(geo_json) + # print(f"Geometry added: {geo_json}") + # elif action == "featureremoved": + # drawn_geometries[:] = [ + # g for g in drawn_geometries if + # g["properties"]["id"] != geo_json["properties"]["id"] + # ] + # print(f"Geometry removed: {geo_json}") + + # draw_control.on_draw(handle_draw) + + # draw_button = widgets.ToggleButton( + # icon="pencil", + # value=False, + # tooltip="Click to draw geometries", + # layout=widgets.Layout(width="33px", margin="0px 0px 0px 0px"), + # ) + + # def toggle_geometry(change): + # if change["new"]: + # # Show drawing interface + # self.map.add_control(draw_control) + # show_interface() + # else: + # # Hide drawing interface + # hide_interface() + # # Remove GeomanDrawControl from the map + # self.map.remove_control(draw_control) + + # draw_button.observe(toggle_geometry, names="value") + + # def show_interface(): + # nonlocal save_button_control, name_input, save_button + + # # Create widgets + # name_input = widgets.Text(description="Name:") + # save_button = widgets.Button( + # description="Save Geometries", button_style="success" + # ) + + # def save_geometries(b): + # name = name_input.value + # if name and drawn_geometries: + # for geometry in drawn_geometries: + # geometry["properties"]["name"] = name + # geo_json = { + # "type": "FeatureCollection", + # "features": drawn_geometries, + # } + # try: + # with tempfile.NamedTemporaryFile( + # suffix=".geojson", delete=False + # ) as temp_file: + # temp_filename = temp_file.name + # json.dump(geo_json, temp_file) + # save_vector(temp_filename, name) + # # Ensure this function is implemented + # print(f"Imported geometry + # with name '{name}' into GRASS GIS.") + # geo_json_layer = GeoJSON(data=geo_json, name=name) + # self.map.add_layer(geo_json_layer) + # except Exception as e: + # print(f"Failed to import geometries into GRASS GIS: {e}") + + # save_button.on_click(save_geometries) + + # # Add control for the widgets + # save_button_control = WidgetControl( + # widget=widgets.VBox([name_input, save_button]), position="topright" + # ) + # self.map.add_control(save_button_control) + + # display(out) + + # def hide_interface(): + # nonlocal save_button_control, name_input, save_button + + # out.clear_output() + # if save_button_control: + # self.map.remove_control(save_button_control) + # save_button_control = None + # # Remove the draw control if it's added + # if draw_control in self.map.controls: + # draw_control = GeomanDrawControl( + # drawOptions={ + # 'polygon': False, + # 'rectangle': False, + # 'circle': False, + # 'marker': False, + # 'polyline': False + # }, + # editOptions={ + # 'edit': True, + # 'remove': True + # }, + # hide_controls=False # Ensure the toolbar is visible + # ) + # self.map.remove_control(draw_control) + # # Remove the widget controls for the name input and save button + # if name_input: + # name_input.close() + # name_input = None + # if save_button: + # save_button.close() + # save_button = None + + # toggle_control = WidgetControl( + # widget=draw_button, position="topright" + # ) + # self.map.add_control(toggle_control) + + def show_control(self): import ipywidgets as widgets - from IPython.display import display - import tempfile - - draw_control = self._ipyleaflet.DrawControl() - drawn_geometries = [] - out = widgets.Output() - save_button_control = None - - def handle_draw(target, action, geo_json): - with out: - if action == "created": - drawn_geometries.append(geo_json) - print(f"Geometry created: {geo_json}") - elif action == "deleted": - drawn_geometries[:] = [ - g - for g in drawn_geometries - if g["properties"]["id"] != geo_json["properties"]["id"] - ] - print(f"Geometry deleted: {geo_json}") - - draw_control.on_draw(handle_draw) + # Create the toggle button draw_button = widgets.ToggleButton( icon="pencil", value=False, @@ -382,69 +502,28 @@ def handle_draw(target, action, geo_json): layout=widgets.Layout(width="33px", margin="0px 0px 0px 0px"), ) - def toggle_geometry(change): + geoman_control = self._ipyleaflet.GeomanDrawControl(position="topright") + + def on_toggle_change(change): if change["new"]: - self.map.add_control(draw_control) - show_interface() + # Set drawOptions and editOptions when the button is toggled on + geoman_control.polyline = {"pathOptions": {}} + geoman_control.polygon = {"pathOptions": {}} + geoman_control.circlemarker = {"pathOptions": {}} + geoman_control.drag = False + geoman_control.cut = False + geoman_control.rotate = False + geoman_control.edit = False + geoman_control.remove = False + self.map.add_control(geoman_control) else: - self.map.remove_control(draw_control) - hide_interface() - - draw_button.observe(toggle_geometry, names="value") - - def show_interface(): - nonlocal save_button_control - - name_input = widgets.Text(description="Name:") - save_button = widgets.Button( - description="Save Geometries", button_style="success" - ) - - def save_geometries(b): - name = name_input.value - if name and drawn_geometries: - for geometry in drawn_geometries: - geometry["properties"]["name"] = name - geo_json = { - "type": "FeatureCollection", - "features": drawn_geometries, - } - geojson_filename = f"/tmp/{name}.geojson" - with open(geojson_filename, "w") as f: - json.dump(geo_json, f) - try: - with tempfile.NamedTemporaryFile( - suffix=".geojson", delete=False - ) as temp_file: - temp_filename = temp_file.name - json.dump(geo_json, temp_file) - save_vector(temp_filename, name) - print(f"Imported geometry with name '{name}' into GRASS GIS.") - geo_json_layer = self._ipyleaflet.GeoJSON( - data=geo_json, name=name - ) - self.map.add_layer(geo_json_layer) - except Exception as e: - print(f"Failed to import geometries into GRASS GIS: {e}") - geo_json_layer = self._ipyleaflet.GeoJSON(data=geo_json, name=name) - self.map.add_layer(geo_json_layer) - - save_button.on_click(save_geometries) - - save_button_control = self._ipyleaflet.WidgetControl( - widget=widgets.VBox([name_input, save_button]), position="topright" - ) - self.map.add_control(save_button_control) - - display(out) - - def hide_interface(): - nonlocal save_button_control + self.map.remove_control(geoman_control.polyline) + self.map.remove_control(geoman_control.polygon) + self.map.remove_control(geoman_control.circlemarker) + geoman_control.hide_controls = True + self.map.remove_layer(geoman_control) - out.clear_output() - if save_button_control: - self.map.remove_control(save_button_control) - save_button_control = None + draw_button.observe(on_toggle_change, names="value") toggle_control = self._ipyleaflet.WidgetControl( widget=draw_button, position="topright" @@ -473,7 +552,7 @@ def show(self): # ipyleaflet if self._ipyleaflet: - self.setup_drawing_interface() + self.show_control() self.map.add(self.layer_control_object) return self.map From e86872980005ab3969c7ee6d7639825a2478b65b Mon Sep 17 00:00:00 2001 From: 29riyasaxena <29riyasaxena@gmail.com> Date: Sun, 21 Jul 2024 19:27:47 +0530 Subject: [PATCH 05/15] Use DrawControl --- python/grass/jupyter/interactivemap.py | 238 +++++++------------------ python/grass/jupyter/utils.py | 16 +- 2 files changed, 83 insertions(+), 171 deletions(-) diff --git a/python/grass/jupyter/interactivemap.py b/python/grass/jupyter/interactivemap.py index 184e47e4160..669c40e660a 100644 --- a/python/grass/jupyter/interactivemap.py +++ b/python/grass/jupyter/interactivemap.py @@ -16,6 +16,7 @@ import base64 import json from .reprojection_renderer import ReprojectionRenderer +from .utils import save_vector def get_backend(interactive_map): @@ -346,155 +347,30 @@ def add_layer_control(self, **kwargs): else: self.layer_control_object = self._ipyleaflet.LayersControl(**kwargs) - # def setup_drawing_interface(self): - # """ - # Allow Users to draw Geometry and Save them. - # """ - # import ipywidgets as widgets - # from IPython.display import display - # import tempfile - # from ipyleaflet import WidgetControl, GeoJSON - # from ipyleaflet import GeomanDrawControl - - # draw_control = GeomanDrawControl( - # drawOptions={ - # 'polygon': True, - # 'rectangle': True, - # 'circle': False, - # 'marker': False, - # 'polyline': False - # }, - # editOptions={ - # 'edit': True, - # 'remove': True - # }, - # hide_controls=False # Ensure the toolbar is visible - # ) - - # drawn_geometries = [] - # out = widgets.Output() - # save_button_control = None - # name_input = None - # save_button = None - - # def handle_draw(target, action, geo_json): - # with out: - # if action in ["vertexadded", "featureadded"]: - # drawn_geometries.append(geo_json) - # print(f"Geometry added: {geo_json}") - # elif action == "featureremoved": - # drawn_geometries[:] = [ - # g for g in drawn_geometries if - # g["properties"]["id"] != geo_json["properties"]["id"] - # ] - # print(f"Geometry removed: {geo_json}") - - # draw_control.on_draw(handle_draw) - - # draw_button = widgets.ToggleButton( - # icon="pencil", - # value=False, - # tooltip="Click to draw geometries", - # layout=widgets.Layout(width="33px", margin="0px 0px 0px 0px"), - # ) - - # def toggle_geometry(change): - # if change["new"]: - # # Show drawing interface - # self.map.add_control(draw_control) - # show_interface() - # else: - # # Hide drawing interface - # hide_interface() - # # Remove GeomanDrawControl from the map - # self.map.remove_control(draw_control) - - # draw_button.observe(toggle_geometry, names="value") - - # def show_interface(): - # nonlocal save_button_control, name_input, save_button - - # # Create widgets - # name_input = widgets.Text(description="Name:") - # save_button = widgets.Button( - # description="Save Geometries", button_style="success" - # ) - - # def save_geometries(b): - # name = name_input.value - # if name and drawn_geometries: - # for geometry in drawn_geometries: - # geometry["properties"]["name"] = name - # geo_json = { - # "type": "FeatureCollection", - # "features": drawn_geometries, - # } - # try: - # with tempfile.NamedTemporaryFile( - # suffix=".geojson", delete=False - # ) as temp_file: - # temp_filename = temp_file.name - # json.dump(geo_json, temp_file) - # save_vector(temp_filename, name) - # # Ensure this function is implemented - # print(f"Imported geometry - # with name '{name}' into GRASS GIS.") - # geo_json_layer = GeoJSON(data=geo_json, name=name) - # self.map.add_layer(geo_json_layer) - # except Exception as e: - # print(f"Failed to import geometries into GRASS GIS: {e}") - - # save_button.on_click(save_geometries) - - # # Add control for the widgets - # save_button_control = WidgetControl( - # widget=widgets.VBox([name_input, save_button]), position="topright" - # ) - # self.map.add_control(save_button_control) - - # display(out) - - # def hide_interface(): - # nonlocal save_button_control, name_input, save_button - - # out.clear_output() - # if save_button_control: - # self.map.remove_control(save_button_control) - # save_button_control = None - # # Remove the draw control if it's added - # if draw_control in self.map.controls: - # draw_control = GeomanDrawControl( - # drawOptions={ - # 'polygon': False, - # 'rectangle': False, - # 'circle': False, - # 'marker': False, - # 'polyline': False - # }, - # editOptions={ - # 'edit': True, - # 'remove': True - # }, - # hide_controls=False # Ensure the toolbar is visible - # ) - # self.map.remove_control(draw_control) - # # Remove the widget controls for the name input and save button - # if name_input: - # name_input.close() - # name_input = None - # if save_button: - # save_button.close() - # save_button = None - - # toggle_control = WidgetControl( - # widget=draw_button, position="topright" - # ) - # self.map.add_control(toggle_control) - - def show_control(self): - import ipywidgets as widgets - - # Create the toggle button + def setup_drawing_interface(self): + """ + Allow Users to draw Geometry and Save/Delete them. + """ + import ipywidgets as widgets # pylint: disable=import-outside-toplevel + + draw_control = self._ipyleaflet.DrawControl() + drawn_geometries = [] + geo_json_layers = {} + save_button_control = None + + def handle_draw(_, action, geo_json): + print(drawn_geometries, "dgb") + if action == "created": + drawn_geometries.append(geo_json) + print(f"Geometry created: {geo_json}") + elif action == "deleted": + drawn_geometries.clear() + for layers in geo_json_layers: + print("layers", layers) + self.map.remove_layer(layers) + + draw_control.on_draw(handle_draw) + draw_button = widgets.ToggleButton( icon="pencil", value=False, @@ -502,28 +378,52 @@ def show_control(self): layout=widgets.Layout(width="33px", margin="0px 0px 0px 0px"), ) - geoman_control = self._ipyleaflet.GeomanDrawControl(position="topright") - - def on_toggle_change(change): + def toggle_geometry(change): if change["new"]: - # Set drawOptions and editOptions when the button is toggled on - geoman_control.polyline = {"pathOptions": {}} - geoman_control.polygon = {"pathOptions": {}} - geoman_control.circlemarker = {"pathOptions": {}} - geoman_control.drag = False - geoman_control.cut = False - geoman_control.rotate = False - geoman_control.edit = False - geoman_control.remove = False - self.map.add_control(geoman_control) + self.map.add_control(draw_control) + show_interface() else: - self.map.remove_control(geoman_control.polyline) - self.map.remove_control(geoman_control.polygon) - self.map.remove_control(geoman_control.circlemarker) - geoman_control.hide_controls = True - self.map.remove_layer(geoman_control) + drawn_geometries.clear() + self.map.remove_control(draw_control) + hide_interface() + + draw_button.observe(toggle_geometry, names="value") + + def show_interface(): + nonlocal save_button_control + + name_input = widgets.Text(description="Name:") + save_button = widgets.Button( + description="Save Geometries", button_style="success" + ) + + def save_geometries(b): + name = name_input.value + if name and drawn_geometries: + for geometry in drawn_geometries: + geometry["properties"]["name"] = name + geo_json = { + "type": "FeatureCollection", + "features": drawn_geometries, + } + save_vector(name, geo_json) + geo_json_layer = self._ipyleaflet.GeoJSON(data=geo_json, name=name) + geo_json_layers[name] = geo_json_layer + self.map.add_layer(geo_json_layer) + + save_button.on_click(save_geometries) + + save_button_control = self._ipyleaflet.WidgetControl( + widget=widgets.VBox([name_input, save_button]), position="topright" + ) + self.map.add_control(save_button_control) - draw_button.observe(on_toggle_change, names="value") + def hide_interface(): + nonlocal save_button_control + drawn_geometries.clear() + if save_button_control: + self.map.remove_control(save_button_control) + save_button_control = None toggle_control = self._ipyleaflet.WidgetControl( widget=draw_button, position="topright" @@ -552,7 +452,7 @@ def show(self): # ipyleaflet if self._ipyleaflet: - self.show_control() + self.setup_drawing_interface() self.map.add(self.layer_control_object) return self.map diff --git a/python/grass/jupyter/utils.py b/python/grass/jupyter/utils.py index 5c43eda9eda..48fdf8a9d92 100644 --- a/python/grass/jupyter/utils.py +++ b/python/grass/jupyter/utils.py @@ -202,14 +202,26 @@ def get_rendering_size(region, width, height, default_width=600, default_height= return (default_width, round(default_width * region_height / region_width)) -def save_vector(geojson_filename, name): +def save_vector(name, geo_json): """ Saves the user drawn vector. param geojson_filename: name of the geojson file to be saved param name: name with which vector should be saved """ - gs.run_command("v.import", input=geojson_filename, output=name) + import tempfile + import json + + try: + with tempfile.NamedTemporaryFile( + suffix=".geojson", delete=False, mode="w" + ) as temp_file: + temp_filename = temp_file.name + json.dump(geo_json, temp_file) + gs.run_command("v.import", input=temp_filename, output=name) + print(f"Imported geometry with name '{name}' into GRASS GIS.") + except Exception as e: + print(f"Failed to import geometries into GRASS GIS: {e}") def save_gif( From c88eb47cba7ed35b72f95b79a10dfcdfd94dc5be Mon Sep 17 00:00:00 2001 From: 29riyasaxena <29riyasaxena@gmail.com> Date: Mon, 22 Jul 2024 12:26:04 +0530 Subject: [PATCH 06/15] Update display of buttons --- python/grass/jupyter/interactivemap.py | 36 +++++++++++++++----------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/python/grass/jupyter/interactivemap.py b/python/grass/jupyter/interactivemap.py index 669c40e660a..4fd8dee018c 100644 --- a/python/grass/jupyter/interactivemap.py +++ b/python/grass/jupyter/interactivemap.py @@ -353,21 +353,15 @@ def setup_drawing_interface(self): """ import ipywidgets as widgets # pylint: disable=import-outside-toplevel - draw_control = self._ipyleaflet.DrawControl() + draw_control = self._ipyleaflet.DrawControl(edit=False, remove=False) drawn_geometries = [] geo_json_layers = {} save_button_control = None - def handle_draw(_, action, geo_json): - print(drawn_geometries, "dgb") + def handle_draw(_target, action, geo_json): if action == "created": drawn_geometries.append(geo_json) print(f"Geometry created: {geo_json}") - elif action == "deleted": - drawn_geometries.clear() - for layers in geo_json_layers: - print("layers", layers) - self.map.remove_layer(layers) draw_control.on_draw(handle_draw) @@ -392,12 +386,20 @@ def toggle_geometry(change): def show_interface(): nonlocal save_button_control - name_input = widgets.Text(description="Name:") + name_input = widgets.Text( + description="Name:", + style={"description_width": "initial"}, + layout=widgets.Layout( + width="50%", + margin="1px 1px 1px 1px", + ), + ) save_button = widgets.Button( - description="Save Geometries", button_style="success" + description="Save Geometries", + layout=widgets.Layout(width="50%", margin="1px 1px 1px 1px"), ) - def save_geometries(b): + def save_geometries(_b): name = name_input.value if name and drawn_geometries: for geometry in drawn_geometries: @@ -413,9 +415,15 @@ def save_geometries(b): save_button.on_click(save_geometries) + hbox_layout = widgets.Layout( + display="flex", flex_flow="row", align_items="stretch", width="300px" + ) + save_button_control = self._ipyleaflet.WidgetControl( - widget=widgets.VBox([name_input, save_button]), position="topright" + widget=widgets.HBox([name_input, save_button], layout=hbox_layout), + position="topright", ) + self.map.add_control(save_button_control) def hide_interface(): @@ -425,9 +433,7 @@ def hide_interface(): self.map.remove_control(save_button_control) save_button_control = None - toggle_control = self._ipyleaflet.WidgetControl( - widget=draw_button, position="topright" - ) + toggle_control = self._ipyleaflet.WidgetControl(widget=draw_button) self.map.add_control(toggle_control) def show(self): From f9040ed150efc74334d1b2a2060f6466a5414249 Mon Sep 17 00:00:00 2001 From: 29riyasaxena <29riyasaxena@gmail.com> Date: Thu, 1 Aug 2024 18:06:39 +0530 Subject: [PATCH 07/15] Add changes --- python/grass/jupyter/interactivemap.py | 16 +++++++++++----- python/grass/jupyter/utils.py | 23 ++++++++++------------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/python/grass/jupyter/interactivemap.py b/python/grass/jupyter/interactivemap.py index 4fd8dee018c..f6216ce18f6 100644 --- a/python/grass/jupyter/interactivemap.py +++ b/python/grass/jupyter/interactivemap.py @@ -387,16 +387,16 @@ def show_interface(): nonlocal save_button_control name_input = widgets.Text( - description="Name:", + description="New vector map name:", style={"description_width": "initial"}, layout=widgets.Layout( - width="50%", + width="80%", margin="1px 1px 1px 1px", ), ) save_button = widgets.Button( - description="Save Geometries", - layout=widgets.Layout(width="50%", margin="1px 1px 1px 1px"), + description="Save", + layout=widgets.Layout(width="20%", margin="1px 1px 1px 1px"), ) def save_geometries(_b): @@ -412,11 +412,17 @@ def save_geometries(_b): geo_json_layer = self._ipyleaflet.GeoJSON(data=geo_json, name=name) geo_json_layers[name] = geo_json_layer self.map.add_layer(geo_json_layer) + draw_control.clear() + drawn_geometries.clear() save_button.on_click(save_geometries) hbox_layout = widgets.Layout( - display="flex", flex_flow="row", align_items="stretch", width="300px" + display="flex", + flex_flow="row", + align_items="stretch", + width="300px", + justify_content="space-between", ) save_button_control = self._ipyleaflet.WidgetControl( diff --git a/python/grass/jupyter/utils.py b/python/grass/jupyter/utils.py index 48fdf8a9d92..11e912ea94d 100644 --- a/python/grass/jupyter/utils.py +++ b/python/grass/jupyter/utils.py @@ -10,6 +10,9 @@ # for details. """Utility functions warpping existing processes in a suitable way""" +import tempfile +import json + from pathlib import Path import grass.script as gs @@ -209,19 +212,13 @@ def save_vector(name, geo_json): param geojson_filename: name of the geojson file to be saved param name: name with which vector should be saved """ - import tempfile - import json - - try: - with tempfile.NamedTemporaryFile( - suffix=".geojson", delete=False, mode="w" - ) as temp_file: - temp_filename = temp_file.name - json.dump(geo_json, temp_file) - gs.run_command("v.import", input=temp_filename, output=name) - print(f"Imported geometry with name '{name}' into GRASS GIS.") - except Exception as e: - print(f"Failed to import geometries into GRASS GIS: {e}") + with tempfile.NamedTemporaryFile( + suffix=".geojson", delete=False, mode="w" + ) as temp_file: + temp_filename = temp_file.name + json.dump(geo_json, temp_file) + gs.run_command("v.import", input=temp_filename, output=name) + print(f"Imported geometry with name '{name}' into GRASS GIS.") def save_gif( From 64124bad44d6e606b6e400157481b605eb399bc2 Mon Sep 17 00:00:00 2001 From: 29riyasaxena <29riyasaxena@gmail.com> Date: Thu, 1 Aug 2024 21:09:01 +0530 Subject: [PATCH 08/15] Modify utils --- python/grass/jupyter/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/grass/jupyter/utils.py b/python/grass/jupyter/utils.py index f8b42fd128e..93c2fd78a09 100644 --- a/python/grass/jupyter/utils.py +++ b/python/grass/jupyter/utils.py @@ -215,9 +215,10 @@ def save_vector(name, geo_json): suffix=".geojson", delete=False, mode="w" ) as temp_file: temp_filename = temp_file.name + for each in geo_json["features"]: + each["properties"].clear() json.dump(geo_json, temp_file) gs.run_command("v.import", input=temp_filename, output=name) - print(f"Imported geometry with name '{name}' into GRASS GIS.") def get_region_bounds_latlon(): From e8631b50114db9e1f75cd2aa3a380da090e69d89 Mon Sep 17 00:00:00 2001 From: 29riyasaxena <29riyasaxena@gmail.com> Date: Sat, 3 Aug 2024 00:00:21 +0530 Subject: [PATCH 09/15] Delete Tempfile --- python/grass/jupyter/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/grass/jupyter/utils.py b/python/grass/jupyter/utils.py index 93c2fd78a09..b86d600862b 100644 --- a/python/grass/jupyter/utils.py +++ b/python/grass/jupyter/utils.py @@ -12,6 +12,7 @@ """Utility functions warpping existing processes in a suitable way""" import tempfile import json +import os from pathlib import Path import grass.script as gs @@ -219,6 +220,7 @@ def save_vector(name, geo_json): each["properties"].clear() json.dump(geo_json, temp_file) gs.run_command("v.import", input=temp_filename, output=name) + os.remove(temp_filename) def get_region_bounds_latlon(): From 0e37ab1c2267a352fa9a2c91cc5a4e2c20e3501a Mon Sep 17 00:00:00 2001 From: 29riyasaxena <29riyasaxena@gmail.com> Date: Mon, 5 Aug 2024 23:56:01 +0530 Subject: [PATCH 10/15] setup toggle change --- python/grass/jupyter/interactivemap.py | 186 ++++++++++++++----------- 1 file changed, 104 insertions(+), 82 deletions(-) diff --git a/python/grass/jupyter/interactivemap.py b/python/grass/jupyter/interactivemap.py index a422a4cdbab..d777fc92c22 100644 --- a/python/grass/jupyter/interactivemap.py +++ b/python/grass/jupyter/interactivemap.py @@ -372,25 +372,7 @@ def handle_draw(_target, action, geo_json): draw_control.on_draw(handle_draw) - draw_button = widgets.ToggleButton( - icon="pencil", - value=False, - tooltip="Click to draw geometries", - layout=widgets.Layout(width="33px", margin="0px 0px 0px 0px"), - ) - - def toggle_geometry(change): - if change["new"]: - self.map.add_control(draw_control) - show_interface() - else: - drawn_geometries.clear() - self.map.remove_control(draw_control) - hide_interface() - - draw_button.observe(toggle_geometry, names="value") - - def show_interface(): + def show_draw_interface(): nonlocal save_button_control name_input = widgets.Text( @@ -439,15 +421,22 @@ def save_geometries(_b): self.map.add_control(save_button_control) - def hide_interface(): + def hide_draw_interface(): nonlocal save_button_control drawn_geometries.clear() if save_button_control: self.map.remove_control(save_button_control) save_button_control = None - toggle_control = self._ipyleaflet.WidgetControl(widget=draw_button) - self.map.add_control(toggle_control) + def toggle_draw_interface(visible): + if visible: + self.map.add_control(draw_control) + show_draw_interface() + else: + self.map.remove_control(draw_control) + hide_draw_interface() + + return toggle_draw_interface def draw_computational_region(self): """ @@ -455,19 +444,8 @@ def draw_computational_region(self): """ import ipywidgets as widgets # pylint: disable=import-outside-toplevel - region_mode_button = widgets.ToggleButton( - icon="square-o", - description="", - value=False, - tooltip="Click to show and edit computational region", - layout=widgets.Layout(width="40px", margin="0px 0px 0px 0px"), - ) - - save_button = widgets.Button( - description="Update region", - tooltip="Click to update region", - disabled=True, - ) + region_rectangle = None + save_button_control = None bottom_output_widget = widgets.Output( layout={ "width": "100%", @@ -478,7 +456,6 @@ def draw_computational_region(self): ) changed_region = {} - save_button_control = None def update_output(region): with bottom_output_widget: @@ -499,41 +476,6 @@ def on_rectangle_change(value): changed_region["east"] = latlon_bounds[2]["lng"] changed_region["west"] = latlon_bounds[0]["lng"] - def toggle_region_mode(change): - nonlocal save_button_control - - if change["new"]: - region_bounds = get_region_bounds_latlon() - self.region_rectangle = self._ipyleaflet.Rectangle( - bounds=region_bounds, - color="red", - fill_color="red", - fill_opacity=0.5, - draggable=True, - transform=True, - rotation=False, - name="Computational region", - ) - self.region_rectangle.observe(on_rectangle_change, names="locations") - self.map.fit_bounds(region_bounds) - self.map.add(self.region_rectangle) - - save_button_control = self._ipyleaflet.WidgetControl( - widget=save_button, position="topright" - ) - self.map.add(save_button_control) - else: - if self.region_rectangle: - self.region_rectangle.transform = False - self.map.remove(self.region_rectangle) - self.region_rectangle = None - - save_button.disabled = True - - if save_button_control: - self.map.remove(save_button_control) - bottom_output_widget.layout.display = "none" - def save_region(_change): from_proj = "+proj=longlat +datum=WGS84 +no_defs" to_proj = get_location_proj_string() @@ -542,18 +484,102 @@ def save_region(_change): bottom_output_widget.layout.display = "block" update_output(new) - region_mode_button.observe(toggle_region_mode, names="value") + save_button = widgets.Button( + description="Update region", + tooltip="Click to update region", + disabled=True, + ) save_button.on_click(save_region) - region_mode_control = self._ipyleaflet.WidgetControl( - widget=region_mode_button, position="topright" - ) - self.map.add(region_mode_control) + def show_region_interface(): + nonlocal region_rectangle, save_button_control + + region_bounds = get_region_bounds_latlon() + region_rectangle = self._ipyleaflet.Rectangle( + bounds=region_bounds, + color="red", + fill_color="red", + fill_opacity=0.5, + draggable=True, + transform=True, + rotation=False, + name="Computational region", + ) + region_rectangle.observe(on_rectangle_change, names="locations") + self.map.fit_bounds(region_bounds) + self.map.add(region_rectangle) + + save_button_control = self._ipyleaflet.WidgetControl( + widget=save_button, position="topright" + ) + self.map.add_control(save_button_control) + + def hide_region_interface(): + nonlocal region_rectangle, save_button_control + + if region_rectangle: + region_rectangle.transform = False + self.map.remove(region_rectangle) + region_rectangle = None + + save_button.disabled = True + + if save_button_control: + self.map.remove(save_button_control) + bottom_output_widget.layout.display = "none" + + def toggle_region_interface(visible): + if visible: + show_region_interface() + else: + hide_region_interface() output_control = self._ipyleaflet.WidgetControl( widget=bottom_output_widget, position="bottomleft" ) - self.map.add(output_control) + self.map.add_control(output_control) + + return toggle_region_interface + + def setup_toggle_buttons(self): + """ + Set up the mutually exclusive toggle buttons. + """ + import ipywidgets as widgets # pylint: disable=import-outside-toplevel + + toggle_draw_interface = self.setup_drawing_interface() + toggle_region_interface = self.draw_computational_region() + + def on_toggle_buttons_change(change): + if change["new"] == "pencil": + toggle_region_interface(False) + toggle_draw_interface(True) + elif change["new"] == "square-o": + toggle_draw_interface(False) + toggle_region_interface(True) + else: + toggle_draw_interface(False) + toggle_region_interface(False) + + mode_selector = widgets.ToggleButtons( + options=[("", "pencil"), ("", "square-o")], + description="", + icons=["pencil", "square-o"], + tooltips=["Draw", "Region"], + layout=widgets.Layout( + display="flex", flex_flow="row", justify_content="center" + ), + ) + + mode_selector.style.button_width = "40px" + mode_selector.style.button_height = "40px" + + mode_selector.observe(on_toggle_buttons_change, names="value") + + mode_control = self._ipyleaflet.WidgetControl( + widget=mode_selector, position="topright" + ) + self.map.add_control(mode_control) def show(self): """This function returns a folium figure or ipyleaflet map object @@ -562,7 +588,7 @@ def show(self): If map has layer control enabled, additional layers cannot be added after calling show().""" if self._ipyleaflet: - self.draw_computational_region() + self.setup_toggle_buttons() self.map.fit_bounds(self._renderer.get_bbox()) if not self.layer_control_object: @@ -575,10 +601,6 @@ def show(self): fig.add_child(self.map) return fig - - # ipyleaflet - if self._ipyleaflet: - self.setup_drawing_interface() self.map.add(self.layer_control_object) return self.map From e9280fac8d04292dee1207550b66920fcb2bedee Mon Sep 17 00:00:00 2001 From: 29riyasaxena <29riyasaxena@gmail.com> Date: Tue, 6 Aug 2024 23:04:46 +0530 Subject: [PATCH 11/15] Modify functions for toggle setup --- python/grass/jupyter/interactivemap.py | 414 +++++++++++++------------ 1 file changed, 211 insertions(+), 203 deletions(-) diff --git a/python/grass/jupyter/interactivemap.py b/python/grass/jupyter/interactivemap.py index d777fc92c22..9f93c6aca58 100644 --- a/python/grass/jupyter/interactivemap.py +++ b/python/grass/jupyter/interactivemap.py @@ -360,83 +360,27 @@ def setup_drawing_interface(self): """ import ipywidgets as widgets # pylint: disable=import-outside-toplevel - draw_control = self._ipyleaflet.DrawControl(edit=False, remove=False) - drawn_geometries = [] - geo_json_layers = {} - save_button_control = None - - def handle_draw(_target, action, geo_json): - if action == "created": - drawn_geometries.append(geo_json) - print(f"Geometry created: {geo_json}") - - draw_control.on_draw(handle_draw) - - def show_draw_interface(): - nonlocal save_button_control - - name_input = widgets.Text( - description="New vector map name:", - style={"description_width": "initial"}, - layout=widgets.Layout( - width="80%", - margin="1px 1px 1px 1px", - ), - ) - save_button = widgets.Button( - description="Save", - layout=widgets.Layout(width="20%", margin="1px 1px 1px 1px"), - ) - - def save_geometries(_b): - name = name_input.value - if name and drawn_geometries: - for geometry in drawn_geometries: - geometry["properties"]["name"] = name - geo_json = { - "type": "FeatureCollection", - "features": drawn_geometries, - } - save_vector(name, geo_json) - geo_json_layer = self._ipyleaflet.GeoJSON(data=geo_json, name=name) - geo_json_layers[name] = geo_json_layer - self.map.add_layer(geo_json_layer) - draw_control.clear() - drawn_geometries.clear() - - save_button.on_click(save_geometries) - - hbox_layout = widgets.Layout( - display="flex", - flex_flow="row", - align_items="stretch", - width="300px", - justify_content="space-between", - ) - - save_button_control = self._ipyleaflet.WidgetControl( - widget=widgets.HBox([name_input, save_button], layout=hbox_layout), - position="topright", - ) - - self.map.add_control(save_button_control) + draw_button = widgets.ToggleButton( + icon="pencil", + value=False, + tooltip="Click to draw geometries", + layout=widgets.Layout(width="33px", margin="0px 0px 0px 0px"), + ) - def hide_draw_interface(): - nonlocal save_button_control - drawn_geometries.clear() - if save_button_control: - self.map.remove_control(save_button_control) - save_button_control = None + draw_controller = InteractiveDrawController(self.map, self._ipyleaflet) - def toggle_draw_interface(visible): - if visible: - self.map.add_control(draw_control) - show_draw_interface() + def toggle_region_mode(change): + if change["new"]: + draw_controller.activate() else: - self.map.remove_control(draw_control) - hide_draw_interface() + draw_controller.deactivate() + + draw_button.observe(toggle_region_mode, names="value") - return toggle_draw_interface + draw_button_control = self._ipyleaflet.WidgetControl( + widget=draw_button, position="bottomright" + ) + self.map.add(draw_button_control) def draw_computational_region(self): """ @@ -444,142 +388,28 @@ def draw_computational_region(self): """ import ipywidgets as widgets # pylint: disable=import-outside-toplevel - region_rectangle = None - save_button_control = None - bottom_output_widget = widgets.Output( - layout={ - "width": "100%", - "max_height": "300px", - "overflow": "auto", - "display": "none", - } - ) - - changed_region = {} - - def update_output(region): - with bottom_output_widget: - bottom_output_widget.clear_output() - print( - _( - "Region changed to: n={n}, s={s}, e={e}, w={w} " - "nsres={nsres} ewres={ewres}" - ).format(**region) - ) - - def on_rectangle_change(value): - save_button.disabled = False - bottom_output_widget.layout.display = "none" - latlon_bounds = value["new"][0] - changed_region["north"] = latlon_bounds[2]["lat"] - changed_region["south"] = latlon_bounds[0]["lat"] - changed_region["east"] = latlon_bounds[2]["lng"] - changed_region["west"] = latlon_bounds[0]["lng"] - - def save_region(_change): - from_proj = "+proj=longlat +datum=WGS84 +no_defs" - to_proj = get_location_proj_string() - reprojected_region = reproject_region(changed_region, from_proj, to_proj) - new = update_region(reprojected_region) - bottom_output_widget.layout.display = "block" - update_output(new) - - save_button = widgets.Button( - description="Update region", - tooltip="Click to update region", - disabled=True, - ) - save_button.on_click(save_region) - - def show_region_interface(): - nonlocal region_rectangle, save_button_control - - region_bounds = get_region_bounds_latlon() - region_rectangle = self._ipyleaflet.Rectangle( - bounds=region_bounds, - color="red", - fill_color="red", - fill_opacity=0.5, - draggable=True, - transform=True, - rotation=False, - name="Computational region", - ) - region_rectangle.observe(on_rectangle_change, names="locations") - self.map.fit_bounds(region_bounds) - self.map.add(region_rectangle) - - save_button_control = self._ipyleaflet.WidgetControl( - widget=save_button, position="topright" - ) - self.map.add_control(save_button_control) - - def hide_region_interface(): - nonlocal region_rectangle, save_button_control - - if region_rectangle: - region_rectangle.transform = False - self.map.remove(region_rectangle) - region_rectangle = None - - save_button.disabled = True - - if save_button_control: - self.map.remove(save_button_control) - bottom_output_widget.layout.display = "none" - - def toggle_region_interface(visible): - if visible: - show_region_interface() - else: - hide_region_interface() - - output_control = self._ipyleaflet.WidgetControl( - widget=bottom_output_widget, position="bottomleft" + region_mode_button = widgets.ToggleButton( + icon="square-o", + description="", + value=False, + tooltip="Click to show and edit computational region", + layout=widgets.Layout(width="40px", margin="0px 0px 0px 0px"), ) - self.map.add_control(output_control) - - return toggle_region_interface - - def setup_toggle_buttons(self): - """ - Set up the mutually exclusive toggle buttons. - """ - import ipywidgets as widgets # pylint: disable=import-outside-toplevel - toggle_draw_interface = self.setup_drawing_interface() - toggle_region_interface = self.draw_computational_region() + region_controller = InteractiveRegionController(self.map, self._ipyleaflet) - def on_toggle_buttons_change(change): - if change["new"] == "pencil": - toggle_region_interface(False) - toggle_draw_interface(True) - elif change["new"] == "square-o": - toggle_draw_interface(False) - toggle_region_interface(True) + def toggle_region_mode(change): + if change["new"]: + region_controller.activate() else: - toggle_draw_interface(False) - toggle_region_interface(False) - - mode_selector = widgets.ToggleButtons( - options=[("", "pencil"), ("", "square-o")], - description="", - icons=["pencil", "square-o"], - tooltips=["Draw", "Region"], - layout=widgets.Layout( - display="flex", flex_flow="row", justify_content="center" - ), - ) + region_controller.deactivate() - mode_selector.style.button_width = "40px" - mode_selector.style.button_height = "40px" + region_mode_button.observe(toggle_region_mode, names="value") - mode_selector.observe(on_toggle_buttons_change, names="value") - - mode_control = self._ipyleaflet.WidgetControl( - widget=mode_selector, position="topright" + region_mode_control = self._ipyleaflet.WidgetControl( + widget=region_mode_button, position="topright" ) - self.map.add_control(mode_control) + self.map.add(region_mode_control) def show(self): """This function returns a folium figure or ipyleaflet map object @@ -588,7 +418,9 @@ def show(self): If map has layer control enabled, additional layers cannot be added after calling show().""" if self._ipyleaflet: - self.setup_toggle_buttons() + self.setup_drawing_interface() + self.draw_computational_region() + self.map.fit_bounds(self._renderer.get_bbox()) if not self.layer_control_object: @@ -601,6 +433,8 @@ def show(self): fig.add_child(self.map) return fig + + # ipyleaflet self.map.add(self.layer_control_object) return self.map @@ -610,3 +444,177 @@ def save(self, filename): :param str filename: name of html file """ self.map.save(filename) + + +class InteractiveRegionController: + def __init__(self, map_obj, ipyleaflet): + self.map = map_obj + self.region_rectangle = None + self._ipyleaflet = ipyleaflet + + import ipywidgets as widgets # pylint: disable=import-outside-toplevel + + self.save_button = widgets.Button( + description="Update region", + tooltip="Click to update region", + disabled=True, + ) + self.bottom_output_widget = widgets.Output( + layout={ + "width": "100%", + "max_height": "300px", + "overflow": "auto", + "display": "none", + } + ) + self.changed_region = {} + self.save_button_control = None + self.save_button.on_click(self.save_region) + + output_control = self._ipyleaflet.WidgetControl( + widget=self.bottom_output_widget, position="bottomleft" + ) + self.map.add(output_control) + + def update_output(self, region): + with self.bottom_output_widget: + self.bottom_output_widget.clear_output() + print( + _( + "Region changed to: n={n}, s={s}, e={e}, w={w} " + "nsres={nsres} ewres={ewres}" + ).format(**region) + ) + + def on_rectangle_change(self, value): + self.save_button.disabled = False + self.bottom_output_widget.layout.display = "none" + latlon_bounds = value["new"][0] + self.changed_region["north"] = latlon_bounds[2]["lat"] + self.changed_region["south"] = latlon_bounds[0]["lat"] + self.changed_region["east"] = latlon_bounds[2]["lng"] + self.changed_region["west"] = latlon_bounds[0]["lng"] + + def activate(self): + region_bounds = get_region_bounds_latlon() + self.region_rectangle = self._ipyleaflet.Rectangle( + bounds=region_bounds, + color="red", + fill_color="red", + fill_opacity=0.5, + draggable=True, + transform=True, + rotation=False, + name="Computational region", + ) + self.region_rectangle.observe(self.on_rectangle_change, names="locations") + self.map.fit_bounds(region_bounds) + self.map.add(self.region_rectangle) + + self.save_button_control = self._ipyleaflet.WidgetControl( + widget=self.save_button, position="topright" + ) + self.map.add(self.save_button_control) + + def deactivate(self): + if self.region_rectangle: + self.region_rectangle.transform = False + self.map.remove(self.region_rectangle) + self.region_rectangle = None + + self.save_button.disabled = True + + if self.save_button_control: + self.map.remove(self.save_button_control) + self.bottom_output_widget.layout.display = "none" + + def save_region(self, _change): + from_proj = "+proj=longlat +datum=WGS84 +no_defs" + to_proj = get_location_proj_string() + reprojected_region = reproject_region(self.changed_region, from_proj, to_proj) + new = update_region(reprojected_region) + self.bottom_output_widget.layout.display = "block" + self.update_output(new) + + +class InteractiveDrawController: + def __init__(self, map_obj, ipyleaflet): + import ipywidgets as widgets # pylint: disable=import-outside-toplevel + + self.map = map_obj + self._ipyleaflet = ipyleaflet + + self.draw_control = self._ipyleaflet.DrawControl(edit=False, remove=False) + self.drawn_geometries = [] + self.geo_json_layers = {} + self.save_button_control = None + + self.name_input = widgets.Text( + description="New vector map name:", + style={"description_width": "initial"}, + layout=widgets.Layout(width="80%", margin="1px 1px 1px 1px"), + ) + + self.save_button = widgets.Button( + description="Save", + layout=widgets.Layout(width="20%", margin="1px 1px 1px 1px"), + ) + + self.save_button.on_click(self.save_geometries) + + def activate(self): + self.map.add_control(self.draw_control) + self.draw_control.on_draw(self.handle_draw) + self.show_interface() + + def deactivate(self): + self.map.remove_control(self.draw_control) + self.draw_control.clear() + self.drawn_geometries.clear() + self.hide_interface() + + def handle_draw(self, target, action, geo_json): + if action == "created": + self.drawn_geometries.append(geo_json) + print(f"Geometry created: {geo_json}") + + def show_interface(self): + import ipywidgets as widgets # pylint: disable=import-outside-toplevel + + hbox_layout = widgets.Layout( + display="flex", + flex_flow="row", + align_items="stretch", + width="300px", + justify_content="space-between", + ) + + self.save_button_control = self._ipyleaflet.WidgetControl( + widget=widgets.HBox( + [self.name_input, self.save_button], layout=hbox_layout + ), + position="topright", + ) + + self.map.add_control(self.save_button_control) + + def hide_interface(self): + if self.save_button_control: + self.map.remove_control(self.save_button_control) + self.save_button_control = None + + def save_geometries(self, _b): + name = self.name_input.value + if name and self.drawn_geometries: + for geometry in self.drawn_geometries: + geometry["properties"]["name"] = name + geo_json = { + "type": "FeatureCollection", + "features": self.drawn_geometries, + } + save_vector(name, geo_json) + geo_json_layer = self._ipyleaflet.GeoJSON(data=geo_json, name=name) + self.geo_json_layers[name] = geo_json_layer + self.map.add_layer(geo_json_layer) + self.draw_control.clear() + self.drawn_geometries.clear() From 0a909a20b1a5620e8981873972b4c72fed9dbc04 Mon Sep 17 00:00:00 2001 From: 29riyasaxena <29riyasaxena@gmail.com> Date: Wed, 7 Aug 2024 18:29:05 +0530 Subject: [PATCH 12/15] Setup Toggle Changes --- python/grass/jupyter/interactivemap.py | 181 +++++++++++++++++++------ 1 file changed, 137 insertions(+), 44 deletions(-) diff --git a/python/grass/jupyter/interactivemap.py b/python/grass/jupyter/interactivemap.py index 9f93c6aca58..50b09c61378 100644 --- a/python/grass/jupyter/interactivemap.py +++ b/python/grass/jupyter/interactivemap.py @@ -288,6 +288,9 @@ def _import_ipyleaflet(error): # Store height and width self.width = width self.height = height + self.controllers = {} + self.draw_button = None + self.region_mode_button = None if self._ipyleaflet: basemap = xyzservices.providers.query_name(tiles) @@ -356,60 +359,59 @@ def add_layer_control(self, **kwargs): def setup_drawing_interface(self): """ - Allow Users to draw Geometry and Save/Delete them. - """ - import ipywidgets as widgets # pylint: disable=import-outside-toplevel + Sets up the drawing interface for users + to interactively draw and manage geometries on the map. - draw_button = widgets.ToggleButton( + This includes creating a toggle button to activate the drawing mode, and + instantiating an InteractiveDrawController to handle the drawing functionality. + """ + self.draw_button = self._create_toggle_button( icon="pencil", - value=False, tooltip="Click to draw geometries", - layout=widgets.Layout(width="33px", margin="0px 0px 0px 0px"), - ) - - draw_controller = InteractiveDrawController(self.map, self._ipyleaflet) - - def toggle_region_mode(change): - if change["new"]: - draw_controller.activate() - else: - draw_controller.deactivate() - - draw_button.observe(toggle_region_mode, names="value") - - draw_button_control = self._ipyleaflet.WidgetControl( - widget=draw_button, position="bottomright" + controller=InteractiveDrawController(self.map, self._ipyleaflet), ) - self.map.add(draw_button_control) def draw_computational_region(self): """ - Allow users to draw the computational region and modify it. - """ - import ipywidgets as widgets # pylint: disable=import-outside-toplevel + Sets up the interface for users to draw and + modify the computational region on the map. - region_mode_button = widgets.ToggleButton( + This includes creating a toggle button to activate the + region editing mode, and instantiating an InteractiveRegionController to + handle the region selection and modification functionality. + """ + self.region_mode_button = self._create_toggle_button( icon="square-o", - description="", - value=False, tooltip="Click to show and edit computational region", - layout=widgets.Layout(width="40px", margin="0px 0px 0px 0px"), + controller=InteractiveRegionController(self.map, self._ipyleaflet), ) - region_controller = InteractiveRegionController(self.map, self._ipyleaflet) - - def toggle_region_mode(change): - if change["new"]: - region_controller.activate() - else: - region_controller.deactivate() + def _create_toggle_button(self, icon, tooltip, controller): + import ipywidgets as widgets # pylint: disable=import-outside-toplevel - region_mode_button.observe(toggle_region_mode, names="value") + button = widgets.ToggleButton( + icon=icon, + value=False, + tooltip=tooltip, + layout=widgets.Layout(width="33px", margin="0px 0px 0px 0px"), + ) - region_mode_control = self._ipyleaflet.WidgetControl( - widget=region_mode_button, position="topright" + self.controllers[button] = controller + button.observe(self._toggle_mode, names="value") + self.map.add( + self._ipyleaflet.WidgetControl(widget=button, position="bottomright") ) - self.map.add(region_mode_control) + return button + + def _toggle_mode(self, change): + if change["new"]: + for button, controller in self.controllers.items(): + if button is not change["owner"]: + button.value = False + controller.deactivate() + self.controllers[change["owner"]].activate() + else: + self.controllers[change["owner"]].deactivate() def show(self): """This function returns a folium figure or ipyleaflet map object @@ -447,7 +449,26 @@ def save(self, filename): class InteractiveRegionController: + """ + A controller for interactive region selection on a map. + + Attributes: + map : The ipyleaflet.Map object. + region_rectangle : The rectangle representing the selected region. + _ipyleaflet : The ipyleaflet module. + save_button : The button to save the selected region. + bottom_output_widget : The output widget to display the selected region. + changed_region (dict): The dictionary to store the changed region. + """ + def __init__(self, map_obj, ipyleaflet): + """ + Initializes the InteractiveRegionController. + + Args: + map_obj (ipyleaflet.Map): The map object. + ipyleaflet (ipyleaflet): The ipyleaflet module. + """ self.map = map_obj self.region_rectangle = None self._ipyleaflet = ipyleaflet @@ -477,6 +498,12 @@ def __init__(self, map_obj, ipyleaflet): self.map.add(output_control) def update_output(self, region): + """ + Updates the output widget with the selected region. + + Args: + region (dict): The selected region. + """ with self.bottom_output_widget: self.bottom_output_widget.clear_output() print( @@ -487,6 +514,12 @@ def update_output(self, region): ) def on_rectangle_change(self, value): + """ + Handles the change event of the rectangle. + + Args: + value (dict): The changed value. + """ self.save_button.disabled = False self.bottom_output_widget.layout.display = "none" latlon_bounds = value["new"][0] @@ -496,6 +529,9 @@ def on_rectangle_change(self, value): self.changed_region["west"] = latlon_bounds[0]["lng"] def activate(self): + """ + Activates the interactive region selection. + """ region_bounds = get_region_bounds_latlon() self.region_rectangle = self._ipyleaflet.Rectangle( bounds=region_bounds, @@ -517,18 +553,30 @@ def activate(self): self.map.add(self.save_button_control) def deactivate(self): + """ + Deactivates the interactive region selection. + """ if self.region_rectangle: self.region_rectangle.transform = False self.map.remove(self.region_rectangle) self.region_rectangle = None - self.save_button.disabled = True - - if self.save_button_control: + if ( + hasattr(self, "save_button_control") + and self.save_button_control in self.map.controls + ): self.map.remove(self.save_button_control) + + self.save_button.disabled = True self.bottom_output_widget.layout.display = "none" def save_region(self, _change): + """ + Saves the selected region. + + Args: + _change (None): Not used. + """ from_proj = "+proj=longlat +datum=WGS84 +no_defs" to_proj = get_location_proj_string() reprojected_region = reproject_region(self.changed_region, from_proj, to_proj) @@ -538,7 +586,26 @@ def save_region(self, _change): class InteractiveDrawController: + """ + A controller for interactive drawing on a map. + + Attributes: + map: The ipyleaflet.Map object. + _ipyleaflet : The ipyleaflet module. + draw_control : The draw control. + drawn_geometries : The list of drawn geometries. + geo_json_layers : The dictionary of GeoJSON layers. + save_button_control : The save button control. + """ + def __init__(self, map_obj, ipyleaflet): + """ + Initializes the InteractiveDrawController. + + Args: + map_obj (ipyleaflet.Map): The map object. + ipyleaflet (ipyleaflet): The ipyleaflet module. + """ import ipywidgets as widgets # pylint: disable=import-outside-toplevel self.map = map_obj @@ -563,22 +630,39 @@ def __init__(self, map_obj, ipyleaflet): self.save_button.on_click(self.save_geometries) def activate(self): + """ + Activates the interactive drawing. + """ self.map.add_control(self.draw_control) self.draw_control.on_draw(self.handle_draw) self.show_interface() def deactivate(self): - self.map.remove_control(self.draw_control) + """ + Deactivates the interactive drawing. + """ + if self.draw_control in self.map.controls: + self.map.remove(self.draw_control) self.draw_control.clear() self.drawn_geometries.clear() self.hide_interface() - def handle_draw(self, target, action, geo_json): + def handle_draw(self, _, action, geo_json): + """ + Handles the draw event. + + Args: + action (str): The action type. + geo_json (dict): The GeoJSON data. + """ if action == "created": self.drawn_geometries.append(geo_json) print(f"Geometry created: {geo_json}") def show_interface(self): + """ + Shows the interface for saving the drawn geometries. + """ import ipywidgets as widgets # pylint: disable=import-outside-toplevel hbox_layout = widgets.Layout( @@ -599,11 +683,20 @@ def show_interface(self): self.map.add_control(self.save_button_control) def hide_interface(self): + """ + Hides the interface for saving the drawn geometries. + """ if self.save_button_control: self.map.remove_control(self.save_button_control) self.save_button_control = None def save_geometries(self, _b): + """ + Saves the drawn geometries. + + Args: + _b (None): Not used. + """ name = self.name_input.value if name and self.drawn_geometries: for geometry in self.drawn_geometries: From 5777116ab7088f2addb0bec851193912dd06636a Mon Sep 17 00:00:00 2001 From: Anna Petrasova Date: Wed, 7 Aug 2024 14:40:18 -0400 Subject: [PATCH 13/15] small changes --- python/grass/jupyter/interactivemap.py | 68 +++++++++++++------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/python/grass/jupyter/interactivemap.py b/python/grass/jupyter/interactivemap.py index 50b09c61378..8b607bbc995 100644 --- a/python/grass/jupyter/interactivemap.py +++ b/python/grass/jupyter/interactivemap.py @@ -288,9 +288,7 @@ def _import_ipyleaflet(error): # Store height and width self.width = width self.height = height - self.controllers = {} - self.draw_button = None - self.region_mode_button = None + self._controllers = {} if self._ipyleaflet: basemap = xyzservices.providers.query_name(tiles) @@ -365,13 +363,13 @@ def setup_drawing_interface(self): This includes creating a toggle button to activate the drawing mode, and instantiating an InteractiveDrawController to handle the drawing functionality. """ - self.draw_button = self._create_toggle_button( + return self._create_toggle_button( icon="pencil", - tooltip="Click to draw geometries", + tooltip=_("Click to draw geometries"), controller=InteractiveDrawController(self.map, self._ipyleaflet), ) - def draw_computational_region(self): + def setup_computational_region_interface(self): """ Sets up the interface for users to draw and modify the computational region on the map. @@ -380,9 +378,9 @@ def draw_computational_region(self): region editing mode, and instantiating an InteractiveRegionController to handle the region selection and modification functionality. """ - self.region_mode_button = self._create_toggle_button( + return self._create_toggle_button( icon="square-o", - tooltip="Click to show and edit computational region", + tooltip=_("Click to show and edit computational region"), controller=InteractiveRegionController(self.map, self._ipyleaflet), ) @@ -393,25 +391,22 @@ def _create_toggle_button(self, icon, tooltip, controller): icon=icon, value=False, tooltip=tooltip, - layout=widgets.Layout(width="33px", margin="0px 0px 0px 0px"), + layout=widgets.Layout(width="40px", margin="0px", border="2px solid lightgrey"), ) - self.controllers[button] = controller + self._controllers[button] = controller button.observe(self._toggle_mode, names="value") - self.map.add( - self._ipyleaflet.WidgetControl(widget=button, position="bottomright") - ) return button def _toggle_mode(self, change): if change["new"]: - for button, controller in self.controllers.items(): + for button, controller in self._controllers.items(): if button is not change["owner"]: button.value = False controller.deactivate() - self.controllers[change["owner"]].activate() + self._controllers[change["owner"]].activate() else: - self.controllers[change["owner"]].deactivate() + self._controllers[change["owner"]].deactivate() def show(self): """This function returns a folium figure or ipyleaflet map object @@ -419,9 +414,13 @@ def show(self): If map has layer control enabled, additional layers cannot be added after calling show().""" + import ipywidgets as widgets # pylint: disable=import-outside-toplevel if self._ipyleaflet: - self.setup_drawing_interface() - self.draw_computational_region() + toggle_buttons = [] + toggle_buttons.append(self.setup_drawing_interface()) + toggle_buttons.append(self.setup_computational_region_interface()) + button_box = widgets.HBox(toggle_buttons, layout=widgets.Layout(align_items='flex-start')) + self.map.add(self._ipyleaflet.WidgetControl(widget=button_box, position="topright")) self.map.fit_bounds(self._renderer.get_bbox()) @@ -490,14 +489,14 @@ def __init__(self, map_obj, ipyleaflet): ) self.changed_region = {} self.save_button_control = None - self.save_button.on_click(self.save_region) + self.save_button.on_click(self._save_region) output_control = self._ipyleaflet.WidgetControl( widget=self.bottom_output_widget, position="bottomleft" ) self.map.add(output_control) - def update_output(self, region): + def _update_output(self, region): """ Updates the output widget with the selected region. @@ -513,7 +512,7 @@ def update_output(self, region): ).format(**region) ) - def on_rectangle_change(self, value): + def _on_rectangle_change(self, value): """ Handles the change event of the rectangle. @@ -543,7 +542,7 @@ def activate(self): rotation=False, name="Computational region", ) - self.region_rectangle.observe(self.on_rectangle_change, names="locations") + self.region_rectangle.observe(self._on_rectangle_change, names="locations") self.map.fit_bounds(region_bounds) self.map.add(self.region_rectangle) @@ -570,7 +569,7 @@ def deactivate(self): self.save_button.disabled = True self.bottom_output_widget.layout.display = "none" - def save_region(self, _change): + def _save_region(self, _change): """ Saves the selected region. @@ -582,7 +581,7 @@ def save_region(self, _change): reprojected_region = reproject_region(self.changed_region, from_proj, to_proj) new = update_region(reprojected_region) self.bottom_output_widget.layout.display = "block" - self.update_output(new) + self._update_output(new) class InteractiveDrawController: @@ -617,25 +616,25 @@ def __init__(self, map_obj, ipyleaflet): self.save_button_control = None self.name_input = widgets.Text( - description="New vector map name:", + description=_("New vector map name:"), style={"description_width": "initial"}, layout=widgets.Layout(width="80%", margin="1px 1px 1px 1px"), ) self.save_button = widgets.Button( - description="Save", + description=_("Save"), layout=widgets.Layout(width="20%", margin="1px 1px 1px 1px"), ) - self.save_button.on_click(self.save_geometries) + self.save_button.on_click(self._save_geometries) def activate(self): """ Activates the interactive drawing. """ self.map.add_control(self.draw_control) - self.draw_control.on_draw(self.handle_draw) - self.show_interface() + self.draw_control.on_draw(self._handle_draw) + self._show_interface() def deactivate(self): """ @@ -645,9 +644,9 @@ def deactivate(self): self.map.remove(self.draw_control) self.draw_control.clear() self.drawn_geometries.clear() - self.hide_interface() + self._hide_interface() - def handle_draw(self, _, action, geo_json): + def _handle_draw(self, _, action, geo_json): """ Handles the draw event. @@ -659,7 +658,7 @@ def handle_draw(self, _, action, geo_json): self.drawn_geometries.append(geo_json) print(f"Geometry created: {geo_json}") - def show_interface(self): + def _show_interface(self): """ Shows the interface for saving the drawn geometries. """ @@ -682,7 +681,7 @@ def show_interface(self): self.map.add_control(self.save_button_control) - def hide_interface(self): + def _hide_interface(self): """ Hides the interface for saving the drawn geometries. """ @@ -690,7 +689,7 @@ def hide_interface(self): self.map.remove_control(self.save_button_control) self.save_button_control = None - def save_geometries(self, _b): + def _save_geometries(self, _b): """ Saves the drawn geometries. @@ -709,5 +708,4 @@ def save_geometries(self, _b): geo_json_layer = self._ipyleaflet.GeoJSON(data=geo_json, name=name) self.geo_json_layers[name] = geo_json_layer self.map.add_layer(geo_json_layer) - self.draw_control.clear() self.drawn_geometries.clear() From 2a3413b6617525bd938223a1c5b7206b25c25a93 Mon Sep 17 00:00:00 2001 From: Anna Petrasova Date: Wed, 7 Aug 2024 16:41:01 -0400 Subject: [PATCH 14/15] more fixes --- python/grass/jupyter/interactivemap.py | 188 +++++++++++-------------- 1 file changed, 85 insertions(+), 103 deletions(-) diff --git a/python/grass/jupyter/interactivemap.py b/python/grass/jupyter/interactivemap.py index 8b607bbc995..ff5583424a7 100644 --- a/python/grass/jupyter/interactivemap.py +++ b/python/grass/jupyter/interactivemap.py @@ -246,6 +246,7 @@ def __init__( """ self._ipyleaflet = None self._folium = None + self._ipywidgets = None def _import_folium(error): try: @@ -283,6 +284,8 @@ def _import_ipyleaflet(error): if self._ipyleaflet: import ipywidgets as widgets # pylint: disable=import-outside-toplevel + + self._ipywidgets = widgets import xyzservices # pylint: disable=import-outside-toplevel # Store height and width @@ -294,7 +297,7 @@ def _import_ipyleaflet(error): basemap = xyzservices.providers.query_name(tiles) if API_key and basemap.get("accessToken"): basemap["accessToken"] = API_key - layout = widgets.Layout(width=f"{width}px", height=f"{height}px") + layout = self._ipywidgets.Layout(width=f"{width}px", height=f"{height}px") self.map = self._ipyleaflet.Map( basemap=basemap, layout=layout, scroll_wheel_zoom=True ) @@ -356,8 +359,7 @@ def add_layer_control(self, **kwargs): self.layer_control_object = self._ipyleaflet.LayersControl(**kwargs) def setup_drawing_interface(self): - """ - Sets up the drawing interface for users + """Sets up the drawing interface for users to interactively draw and manage geometries on the map. This includes creating a toggle button to activate the drawing mode, and @@ -366,12 +368,11 @@ def setup_drawing_interface(self): return self._create_toggle_button( icon="pencil", tooltip=_("Click to draw geometries"), - controller=InteractiveDrawController(self.map, self._ipyleaflet), + controller_class=InteractiveDrawController, ) def setup_computational_region_interface(self): - """ - Sets up the interface for users to draw and + """Sets up the interface for users to draw and modify the computational region on the map. This includes creating a toggle button to activate the @@ -381,19 +382,22 @@ def setup_computational_region_interface(self): return self._create_toggle_button( icon="square-o", tooltip=_("Click to show and edit computational region"), - controller=InteractiveRegionController(self.map, self._ipyleaflet), + controller_class=InteractiveRegionController, ) - def _create_toggle_button(self, icon, tooltip, controller): - import ipywidgets as widgets # pylint: disable=import-outside-toplevel - - button = widgets.ToggleButton( + def _create_toggle_button(self, icon, tooltip, controller_class): + button = self._ipywidgets.ToggleButton( icon=icon, value=False, tooltip=tooltip, - layout=widgets.Layout(width="40px", margin="0px", border="2px solid lightgrey"), + description="", + layout=self._ipywidgets.Layout( + width="43px", margin="0px", border="2px solid darkgrey" + ), + ) + controller = controller_class( + self.map, self._ipyleaflet, self._ipywidgets, button ) - self._controllers[button] = controller button.observe(self._toggle_mode, names="value") return button @@ -414,13 +418,17 @@ def show(self): If map has layer control enabled, additional layers cannot be added after calling show().""" - import ipywidgets as widgets # pylint: disable=import-outside-toplevel if self._ipyleaflet: - toggle_buttons = [] - toggle_buttons.append(self.setup_drawing_interface()) - toggle_buttons.append(self.setup_computational_region_interface()) - button_box = widgets.HBox(toggle_buttons, layout=widgets.Layout(align_items='flex-start')) - self.map.add(self._ipyleaflet.WidgetControl(widget=button_box, position="topright")) + toggle_buttons = [ + self.setup_computational_region_interface(), + self.setup_drawing_interface(), + ] + button_box = self._ipywidgets.HBox( + toggle_buttons, layout=self._ipywidgets.Layout(align_items="flex-start") + ) + self.map.add( + self._ipyleaflet.WidgetControl(widget=button_box, position="topright") + ) self.map.fit_bounds(self._renderer.get_bbox()) @@ -448,38 +456,36 @@ def save(self, filename): class InteractiveRegionController: - """ - A controller for interactive region selection on a map. + """A controller for interactive region selection on a map. Attributes: - map : The ipyleaflet.Map object. - region_rectangle : The rectangle representing the selected region. - _ipyleaflet : The ipyleaflet module. - save_button : The button to save the selected region. - bottom_output_widget : The output widget to display the selected region. + map: The ipyleaflet.Map object. + region_rectangle: The rectangle representing the selected region. + _ipyleaflet: The ipyleaflet module. + _ipywidgets: The ipywidgets module. + save_button: The button to save the selected region. + bottom_output_widget: The output widget to display the selected region. changed_region (dict): The dictionary to store the changed region. """ - def __init__(self, map_obj, ipyleaflet): - """ - Initializes the InteractiveRegionController. + def __init__(self, map_object, ipyleaflet, ipywidgets, *args): + """Initializes the InteractiveRegionController. - Args: - map_obj (ipyleaflet.Map): The map object. - ipyleaflet (ipyleaflet): The ipyleaflet module. + :param ipyleaflet.Map map_object: The map object. + :param ipyleaflet: The ipyleaflet module. + :param ipywidgets: The ipywidgets module. """ - self.map = map_obj + self.map = map_object self.region_rectangle = None self._ipyleaflet = ipyleaflet + self._ipywidgets = ipywidgets - import ipywidgets as widgets # pylint: disable=import-outside-toplevel - - self.save_button = widgets.Button( + self.save_button = self._ipywidgets.Button( description="Update region", tooltip="Click to update region", disabled=True, ) - self.bottom_output_widget = widgets.Output( + self.bottom_output_widget = self._ipywidgets.Output( layout={ "width": "100%", "max_height": "300px", @@ -497,11 +503,9 @@ def __init__(self, map_obj, ipyleaflet): self.map.add(output_control) def _update_output(self, region): - """ - Updates the output widget with the selected region. + """Updates the output widget with the selected region. - Args: - region (dict): The selected region. + :param dict region: The selected region. """ with self.bottom_output_widget: self.bottom_output_widget.clear_output() @@ -513,11 +517,9 @@ def _update_output(self, region): ) def _on_rectangle_change(self, value): - """ - Handles the change event of the rectangle. + """Handles the change event of the rectangle. - Args: - value (dict): The changed value. + :param dict value: The changed value. """ self.save_button.disabled = False self.bottom_output_widget.layout.display = "none" @@ -528,9 +530,7 @@ def _on_rectangle_change(self, value): self.changed_region["west"] = latlon_bounds[0]["lng"] def activate(self): - """ - Activates the interactive region selection. - """ + """Activates the interactive region selection.""" region_bounds = get_region_bounds_latlon() self.region_rectangle = self._ipyleaflet.Rectangle( bounds=region_bounds, @@ -552,9 +552,7 @@ def activate(self): self.map.add(self.save_button_control) def deactivate(self): - """ - Deactivates the interactive region selection. - """ + """Deactivates the interactive region selection.""" if self.region_rectangle: self.region_rectangle.transform = False self.map.remove(self.region_rectangle) @@ -570,11 +568,9 @@ def deactivate(self): self.bottom_output_widget.layout.display = "none" def _save_region(self, _change): - """ - Saves the selected region. + """Saves the selected region. - Args: - _change (None): Not used. + :param _change:Not used. """ from_proj = "+proj=longlat +datum=WGS84 +no_defs" to_proj = get_location_proj_string() @@ -585,61 +581,56 @@ def _save_region(self, _change): class InteractiveDrawController: - """ - A controller for interactive drawing on a map. + """A controller for interactive drawing on a map. Attributes: map: The ipyleaflet.Map object. - _ipyleaflet : The ipyleaflet module. - draw_control : The draw control. - drawn_geometries : The list of drawn geometries. - geo_json_layers : The dictionary of GeoJSON layers. - save_button_control : The save button control. + _ipyleaflet: The ipyleaflet module. + draw_control: The draw control. + drawn_geometries: The list of drawn geometries. + geo_json_layers: The dictionary of GeoJSON layers. + save_button_control: The save button control. + toggle_button: The toggle button activating/deactivating drawing. """ - def __init__(self, map_obj, ipyleaflet): - """ - Initializes the InteractiveDrawController. + def __init__(self, map_object, ipyleaflet, ipywidgets, toggle_button): + """Initializes the InteractiveDrawController. - Args: - map_obj (ipyleaflet.Map): The map object. - ipyleaflet (ipyleaflet): The ipyleaflet module. + :param ipyleaflet.Map map_object: The map object. + :param ipyleaflet: The ipyleaflet module. + :param ipywidgets: The ipywidgets module. + :param toggle_button: The toggle button activating/deactivating drawing. """ - import ipywidgets as widgets # pylint: disable=import-outside-toplevel - - self.map = map_obj + self.map = map_object self._ipyleaflet = ipyleaflet - + self._ipywidgets = ipywidgets + self.toggle_button = toggle_button self.draw_control = self._ipyleaflet.DrawControl(edit=False, remove=False) self.drawn_geometries = [] self.geo_json_layers = {} self.save_button_control = None - self.name_input = widgets.Text( + self.name_input = self._ipywidgets.Text( description=_("New vector map name:"), style={"description_width": "initial"}, - layout=widgets.Layout(width="80%", margin="1px 1px 1px 1px"), + layout=self._ipywidgets.Layout(width="80%", margin="1px 1px 1px 1px"), ) - self.save_button = widgets.Button( + self.save_button = self._ipywidgets.Button( description=_("Save"), - layout=widgets.Layout(width="20%", margin="1px 1px 1px 1px"), + layout=self._ipywidgets.Layout(width="20%", margin="1px 1px 1px 1px"), ) self.save_button.on_click(self._save_geometries) def activate(self): - """ - Activates the interactive drawing. - """ + """Activates the interactive drawing.""" self.map.add_control(self.draw_control) self.draw_control.on_draw(self._handle_draw) self._show_interface() def deactivate(self): - """ - Deactivates the interactive drawing. - """ + """Deactivates the interactive drawing.""" if self.draw_control in self.map.controls: self.map.remove(self.draw_control) self.draw_control.clear() @@ -647,33 +638,27 @@ def deactivate(self): self._hide_interface() def _handle_draw(self, _, action, geo_json): - """ - Handles the draw event. + """Handles the draw event. - Args: - action (str): The action type. - geo_json (dict): The GeoJSON data. + :param str action: The action type. + :param dict geo_json: The GeoJSON data. """ if action == "created": self.drawn_geometries.append(geo_json) print(f"Geometry created: {geo_json}") def _show_interface(self): - """ - Shows the interface for saving the drawn geometries. - """ - import ipywidgets as widgets # pylint: disable=import-outside-toplevel - - hbox_layout = widgets.Layout( + """Shows the interface for saving the drawn geometries.""" + hbox_layout = self._ipywidgets.Layout( display="flex", flex_flow="row", align_items="stretch", width="300px", justify_content="space-between", ) - + self.name_input.value = "" self.save_button_control = self._ipyleaflet.WidgetControl( - widget=widgets.HBox( + widget=self._ipywidgets.HBox( [self.name_input, self.save_button], layout=hbox_layout ), position="topright", @@ -682,19 +667,15 @@ def _show_interface(self): self.map.add_control(self.save_button_control) def _hide_interface(self): - """ - Hides the interface for saving the drawn geometries. - """ + """Hides the interface for saving the drawn geometries.""" if self.save_button_control: self.map.remove_control(self.save_button_control) self.save_button_control = None def _save_geometries(self, _b): - """ - Saves the drawn geometries. + """Saves the drawn geometries. - Args: - _b (None): Not used. + :param _b: Not used. """ name = self.name_input.value if name and self.drawn_geometries: @@ -708,4 +689,5 @@ def _save_geometries(self, _b): geo_json_layer = self._ipyleaflet.GeoJSON(data=geo_json, name=name) self.geo_json_layers[name] = geo_json_layer self.map.add_layer(geo_json_layer) - self.drawn_geometries.clear() + self.deactivate() + self.toggle_button.value = False From bf7eef32b1731290fa1d91f5bf05202b9fe83158 Mon Sep 17 00:00:00 2001 From: Anna Petrasova Date: Wed, 7 Aug 2024 16:55:18 -0400 Subject: [PATCH 15/15] fix unused argument --- python/grass/jupyter/interactivemap.py | 9 +++++++-- python/grass/jupyter/utils.py | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/python/grass/jupyter/interactivemap.py b/python/grass/jupyter/interactivemap.py index ff5583424a7..8ae79023fe7 100644 --- a/python/grass/jupyter/interactivemap.py +++ b/python/grass/jupyter/interactivemap.py @@ -396,7 +396,10 @@ def _create_toggle_button(self, icon, tooltip, controller_class): ), ) controller = controller_class( - self.map, self._ipyleaflet, self._ipywidgets, button + map_object=self.map, + ipyleaflet=self._ipyleaflet, + ipywidgets=self._ipywidgets, + toggle_button=button, ) self._controllers[button] = controller button.observe(self._toggle_mode, names="value") @@ -468,7 +471,9 @@ class InteractiveRegionController: changed_region (dict): The dictionary to store the changed region. """ - def __init__(self, map_object, ipyleaflet, ipywidgets, *args): + def __init__( + self, map_object, ipyleaflet, ipywidgets, **kwargs + ): # pylint: disable=unused-argument """Initializes the InteractiveRegionController. :param ipyleaflet.Map map_object: The map object. diff --git a/python/grass/jupyter/utils.py b/python/grass/jupyter/utils.py index adb3ffd801f..366fedf7c06 100644 --- a/python/grass/jupyter/utils.py +++ b/python/grass/jupyter/utils.py @@ -210,8 +210,8 @@ def save_vector(name, geo_json): """ Saves the user drawn vector. - param geojson_filename: name of the geojson file to be saved - param name: name with which vector should be saved + :param geo_json: name of the geojson file to be saved + :param name: name with which vector should be saved """ with tempfile.NamedTemporaryFile( suffix=".geojson", delete=False, mode="w"