Skip to content

Commit

Permalink
First pass implementation of AbstractDrawControl
Browse files Browse the repository at this point in the history
  • Loading branch information
sufyanAbbasi committed Aug 19, 2023
1 parent cfc9ed8 commit b2857c8
Show file tree
Hide file tree
Showing 3 changed files with 298 additions and 147 deletions.
196 changes: 109 additions & 87 deletions geemap/geemap.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,72 @@

basemaps = Box(xyz_to_leaflet(), frozen_box=True)

class MapDrawControl(ipyleaflet.DrawControl, map_widgets.AbstractDrawControl):
""""Implements the AbstractDrawControl for the map."""
_roi_start = False
_roi_end = False

def __init__(self, host_map, **kwargs):
super(MapDrawControl,self).__init__(host_map=host_map, **kwargs)

@property
def user_roi(self):
return self.last_geometry

@property
def user_rois(self):
return self.collection

# NOTE: Overridden for backwards compatibility, where edited geometries are
# added to the layer instead of modified in place. Remove when
# https://github.com/jupyter-widgets/ipyleaflet/issues/1119 is fixed to
# allow geometry edits to be reflected on the tile layer.
def _handle_geometry_edited(self, geo_json):
return self._handle_geometry_created(geo_json)

def _get_synced_geojson_from_draw_control(self):
return [data.copy() for data in self.data]

def _bind_to_draw_control(self):
# Handles draw events
def handle_draw(_, action, geo_json):
try:
self._roi_start = True
if action == "created":
self._handle_geometry_created(geo_json)
elif action == "edited":
self._handle_geometry_edited(geo_json)
elif action == "deleted":
self._handle_geometry_deleted(geo_json)
self._roi_end = True
self._roi_start = False
except Exception as e:
self.geometries = []
self.properties = []
self.last_geometry = None
self._roi_start = False
self._roi_end = False
print("There was an error creating Earth Engine Feature.")
raise Exception(e)
self.on_draw(handle_draw)
# NOTE: Uncomment the following code once
# https://github.com/jupyter-widgets/ipyleaflet/issues/1119 is fixed
# to allow edited geometries to be reflected instead of added.
# def handle_data_update(_):
# self._sync_geometries()
# self.observe(handle_data_update, 'data')

def _remove_geometry_at_index_on_draw_control(self, index):
# NOTE: Uncomment the following code once
# https://github.com/jupyter-widgets/ipyleaflet/issues/1119 is fixed to
# remove drawn geometries with `remove_last_drawn()`.
# del self.data[index]
# self.send_state(key='data')
pass

def _clear_draw_control(self):
return self.clear()


class Map(ipyleaflet.Map):
"""The Map class inherits the ipyleaflet Map class. The arguments you can pass to the Map initialization
Expand All @@ -46,6 +112,23 @@ class Map(ipyleaflet.Map):
object: ipyleaflet map object.
"""

# Map attributes for drawing features
@property
def draw_features(self):
return self.draw_control.features if self.draw_control else []
@property
def draw_last_feature(self):
return self.draw_control.last_feature if self.draw_control else None
@property
def draw_layer(self):
return self.draw_control.layer if self.draw_control else None
@property
def user_roi(self):
return self.draw_control.user_roi if self.draw_control else None
@property
def user_rois(self):
return self.draw_control.user_rois if self.draw_control else None

def __init__(self, **kwargs):
"""Initialize a map object. The following additional parameters can be passed in addition to the ipyleaflet.Map parameters:
Expand Down Expand Up @@ -170,13 +253,6 @@ def __init__(self, **kwargs):
if kwargs.get(control, True):
self.add_controls(control, position="bottomright")

# Map attributes for drawing features
self.draw_features = []
self.draw_last_feature = None
self.draw_layer = None
self.user_roi = None
self.user_rois = None

# Map attributes for layers
self.geojson_layers = []
self.ee_layers = []
Expand Down Expand Up @@ -2500,8 +2576,8 @@ def add_draw_control(self, position="topleft"):
Args:
position (str, optional): The position of the draw control. Defaults to "topleft".
"""

draw_control = ipyleaflet.DrawControl(
draw_control = MapDrawControl(
host_map=self,
marker={"shapeOptions": {"color": "#3388ff"}},
rectangle={"shapeOptions": {"color": "#3388ff"}},
# circle={"shapeOptions": {"color": "#3388ff"}},
Expand All @@ -2510,50 +2586,6 @@ def add_draw_control(self, position="topleft"):
remove=True,
position=position,
)

# Handles draw events
def handle_draw(target, action, geo_json):
try:
self._roi_start = True
geom = geojson_to_ee(geo_json, False)
self.user_roi = geom
feature = ee.Feature(geom)
self.draw_last_feature = feature
if not hasattr(self, "_draw_count"):
self._draw_count = 0
if action == "deleted" and len(self.draw_features) > 0:
self.draw_features.remove(feature)
self._draw_count -= 1
else:
self.draw_features.append(feature)
self._draw_count += 1
collection = ee.FeatureCollection(self.draw_features)
self.user_rois = collection
ee_draw_layer = EELeafletTileLayer(
collection, {"color": "blue"}, "Drawn Features", False, 0.5
)
draw_layer_index = self.find_layer_index("Drawn Features")

if draw_layer_index == -1:
self.add(ee_draw_layer)
self.draw_layer = ee_draw_layer
else:
self.substitute_layer(self.draw_layer, ee_draw_layer)
self.draw_layer = ee_draw_layer
self._roi_end = True
self._roi_start = False
except Exception as e:
self._draw_count = 0
self.draw_features = []
self.draw_last_feature = None
self.draw_layer = None
self.user_roi = None
self._roi_start = False
self._roi_end = False
print("There was an error creating Earth Engine Feature.")
raise Exception(e)

draw_control.on_draw(handle_draw)
self.add(draw_control)
self.draw_control = draw_control

Expand Down Expand Up @@ -4060,46 +4092,36 @@ def add_remote_tile(
else:
raise Exception("The source must be a URL.")

def remove_draw_control(self):
controls = []
old_draw_control = None
for control in self.controls:
if isinstance(control, MapDrawControl):
old_draw_control = control

else:
controls.append(control)

self.controls = tuple(controls)
if old_draw_control:
old_draw_control.close()

def remove_drawn_features(self):
"""Removes user-drawn geometries from the map"""
if self.draw_layer is not None:
self.remove_layer(self.draw_layer)
self._draw_count = 0
self.draw_features = []
self.draw_last_feature = None
self.draw_layer = None
self.user_roi = None
self.user_rois = None
self._chart_values = []
self._chart_points = []
self._chart_labels = None
if self.draw_control is not None:
self.draw_control.clear()
self.draw_control.reset()

def remove_last_drawn(self):
"""Removes user-drawn geometries from the map"""
if self.draw_layer is not None:
collection = ee.FeatureCollection(self.draw_features[:-1])
ee_draw_layer = EELeafletTileLayer(
collection, {"color": "blue"}, "Drawn Features", True, 0.5
)
if self._draw_count == 1:
"""Removes last user-drawn geometry from the map"""
if self.draw_control is not None:
if self.draw_control.count == 1:
self.remove_drawn_features()
else:
self.substitute_layer(self.draw_layer, ee_draw_layer)
self.draw_layer = ee_draw_layer
self._draw_count -= 1
self.draw_features = self.draw_features[:-1]
self.draw_last_feature = self.draw_features[-1]
self.draw_layer = ee_draw_layer
self.user_roi = ee.Feature(
collection.toList(collection.size()).get(
collection.size().subtract(1)
)
).geometry()
self.user_rois = collection
self._chart_values = self._chart_values[:-1]
self._chart_points = self._chart_points[:-1]
elif self.draw_control.count:
self.draw_control.remove_geometry(self.draw_control.geometries[-1])
if hasattr(self, '_chart_values'):
self._chart_values = self._chart_values[:-1]
if hasattr(self, '_chart_points'):
self._chart_points = self._chart_points[:-1]
# self._chart_labels = None

def extract_values_to_points(self, filename):
Expand Down
Loading

0 comments on commit b2857c8

Please sign in to comment.