diff --git a/geemap/geemap.py b/geemap/geemap.py index 896887189a..005b3a8cb9 100644 --- a/geemap/geemap.py +++ b/geemap/geemap.py @@ -278,7 +278,13 @@ def __init__(self, **kwargs): # Map attributes for layers self.geojson_layers = [] - self.ee_layers = {} + self.ee_layers = [] + self.ee_layer_names = [] + self.ee_raster_layers = [] + self.ee_raster_layer_names = [] + self.ee_vector_layers = [] + self.ee_vector_layer_names = [] + self.ee_layer_dict = {} # ipyleaflet built-in layer control self.layer_control = None @@ -288,53 +294,6 @@ def __init__(self, **kwargs): self.roi_reducer = ee.Reducer.mean() self.roi_reducer_scale = None - @property - def ee_layer_names(self): - warnings.warn( - "ee_layer_names is deprecated. Use ee_layers.keys() instead.", - DeprecationWarning, - ) - return self.ee_layers.keys() - - @property - def ee_layer_dict(self): - warnings.warn( - "ee_layer_dict is deprecated. Use ee_layers instead.", DeprecationWarning - ) - return self.ee_layers - - @property - def ee_raster_layer_names(self): - warnings.warn( - "ee_raster_layer_names is deprecated. Use self.ee_raster_layers.keys() instead.", - DeprecationWarning, - ) - return self.ee_raster_layers.keys() - - @property - def ee_vector_layer_names(self): - warnings.warn( - "ee_vector_layer_names is deprecated. Use self.ee_vector_layers.keys() instead.", - DeprecationWarning, - ) - return self.ee_vector_layers.keys() - - @property - def ee_raster_layers(self): - return dict(filter(self._raster_filter, self.ee_layers.items())) - - @property - def ee_vector_layers(self): - return dict(filter(self._vector_filter, self.ee_layers.items())) - - def _raster_filter(self, pair): - return isinstance(pair[1]["ee_object"], (ee.Image, ee.ImageCollection)) - - def _vector_filter(self, pair): - return isinstance( - pair[1]["ee_object"], (ee.Geometry, ee.Feature, ee.FeatureCollection) - ) - def add(self, object): """Adds a layer or control to the map. @@ -447,21 +406,30 @@ def add_ee_layer( layer = self.find_layer(name=name) if layer is not None: - existing_object = self.ee_layers[name]["ee_object"] + existing_object = self.ee_layer_dict[name]["ee_object"] if isinstance(existing_object, (ee.Image, ee.ImageCollection)): + self.ee_raster_layers.remove(existing_object) + self.ee_raster_layer_names.remove(name) if ( hasattr(self, "_plot_dropdown_widget") and self._plot_dropdown_widget is not None ): self._plot_dropdown_widget.options = list( - self.ee_raster_layers.keys() + self.ee_raster_layer_names ) + elif isinstance(ee_object, (ee.Geometry, ee.Feature, ee.FeatureCollection)): + self.ee_vector_layers.remove(existing_object) + self.ee_vector_layer_names.remove(name) - self.ee_layers.pop(name, None) + self.ee_layers.remove(existing_object) + self.ee_layer_names.remove(name) self.remove_layer(layer) - self.ee_layers[name] = { + self.ee_layers.append(ee_object) + if name not in self.ee_layer_names: + self.ee_layer_names.append(name) + self.ee_layer_dict[name] = { "ee_object": ee_object, "ee_layer": tile_layer, "vis_params": vis_params, @@ -470,11 +438,16 @@ def add_ee_layer( self.add(tile_layer) if isinstance(ee_object, (ee.Image, ee.ImageCollection)): + self.ee_raster_layers.append(ee_object) + self.ee_raster_layer_names.append(name) if ( hasattr(self, "_plot_dropdown_widget") and self._plot_dropdown_widget is not None ): - self._plot_dropdown_widget.options = list(self.ee_raster_layers.keys()) + self._plot_dropdown_widget.options = list(self.ee_raster_layer_names) + elif isinstance(ee_object, (ee.Geometry, ee.Feature, ee.FeatureCollection)): + self.ee_vector_layers.append(ee_object) + self.ee_vector_layer_names.append(name) arc_add_layer(tile_layer.url_format, name, shown, opacity) @@ -486,9 +459,17 @@ def remove_ee_layer(self, name): Args: name (str): The name of the Earth Engine layer to remove. """ - if name in self.ee_layers: - ee_layer = self.ee_layers[name]["ee_layer"] - self.ee_layers.pop(name, None) + if name in self.ee_layer_dict: + ee_object = self.ee_layer_dict[name]["ee_object"] + ee_layer = self.ee_layer_dict[name]["ee_layer"] + if name in self.ee_raster_layer_names: + self.ee_raster_layer_names.remove(name) + self.ee_raster_layers.remove(ee_object) + elif name in self.ee_vector_layer_names: + self.ee_vector_layer_names.remove(name) + self.ee_vector_layers.remove(ee_object) + self.ee_layers.remove(ee_object) + self.ee_layer_names.remove(name) if ee_layer in self.layers: self.remove_layer(ee_layer) @@ -1117,8 +1098,8 @@ def add_legend( else: self.legends.append(legend_control) - if layer_name in self.ee_layers: - self.ee_layers[layer_name]["legend"] = legend_control + if layer_name in self.ee_layer_names: + self.ee_layer_dict[layer_name]["legend"] = legend_control except Exception as e: raise Exception(e) @@ -1179,10 +1160,10 @@ def add_colorbar( ) self._colorbar = colormap_ctrl - if layer_name in self.ee_layers: - if "colorbar" in self.ee_layers[layer_name]: - self.remove_control(self.ee_layers[layer_name]["colorbar"]) - self.ee_layers[layer_name]["colorbar"] = colormap_ctrl + if layer_name in self.ee_layer_names: + if "colorbar" in self.ee_layer_dict[layer_name]: + self.remove_control(self.ee_layer_dict[layer_name]["colorbar"]) + self.ee_layer_dict[layer_name]["colorbar"] = colormap_ctrl if not hasattr(self, "colorbars"): self.colorbars = [colormap_ctrl] else: @@ -1219,7 +1200,7 @@ def create_vis_widget(self, layer_dict): """Create a GUI for changing layer visualization parameters interactively. Args: - layer_dict (dict): A dict containning information about the layer. It is an element from Map.ee_layers. + layer_dict (dict): A dict containning information about the layer. It is an element from Map.ee_layer_dict. Returns: object: An ipywidget. @@ -2446,16 +2427,16 @@ def import_btn_clicked(b): def apply_btn_clicked(b): compute_label.value = "Computing ..." - if new_layer_name.value in self.ee_layers: + if new_layer_name.value in self.ee_layer_names: old_layer = new_layer_name.value - if "legend" in self.ee_layers[old_layer].keys(): - legend = self.ee_layers[old_layer]["legend"] + if "legend" in self.ee_layer_dict[old_layer].keys(): + legend = self.ee_layer_dict[old_layer]["legend"] if legend in self.controls: self.remove_control(legend) legend.close() - if "colorbar" in self.ee_layers[old_layer].keys(): - colorbar = self.ee_layers[old_layer]["colorbar"] + if "colorbar" in self.ee_layer_dict[old_layer].keys(): + colorbar = self.ee_layer_dict[old_layer]["colorbar"] if colorbar in self.controls: self.remove_control(colorbar) colorbar.close() @@ -2638,7 +2619,7 @@ def _on_close(): self.layer_manager_control = None def _on_open_vis(layer_name): - self.create_vis_widget(self.ee_layers.get(layer_name, None)) + self.create_vis_widget(self.ee_layer_dict.get(layer_name, None)) self.layer_manager_widget = map_widgets.LayerManager(self) self.layer_manager_widget.collapsed = not opened @@ -3156,7 +3137,7 @@ def plot_raster( self.default_style = {"cursor": "crosshair"} msg = "The plot function can only be used on ee.Image or ee.ImageCollection with more than one band." if (ee_object is None) and len(self.ee_raster_layers) > 0: - ee_object = self.ee_raster_layers.values()[-1]["ee_object"] + ee_object = self.ee_raster_layers[-1] if isinstance(ee_object, ee.ImageCollection): ee_object = ee_object.mosaic() elif isinstance(ee_object, ee.ImageCollection): @@ -3728,8 +3709,8 @@ def add_colorbar_branca( else: self.colorbars.append(colormap_ctrl) - if layer_name in self.ee_layers: - self.ee_layers[layer_name]["colorbar"] = colormap_ctrl + if layer_name in self.ee_layer_names: + self.ee_layer_dict[layer_name]["colorbar"] = colormap_ctrl def image_overlay(self, url, bounds, name): """Overlays an image from the Internet or locally on the map. @@ -5153,7 +5134,7 @@ def add_time_slider( ee_object = ee_object.clip(region) elif isinstance(region, ee.FeatureCollection): ee_object = ee_object.clipToCollection(region) - if layer_name not in self.ee_layers: + if layer_name not in self.ee_raster_layer_names: self.addLayer(ee_object, {}, layer_name, False, opacity) band_names = ee_object.bandNames() ee_object = ee.ImageCollection( @@ -5197,7 +5178,7 @@ def add_time_slider( first = ee.Image(ee_object.first()) - if layer_name not in self.ee_layers: + if layer_name not in self.ee_raster_layer_names: self.addLayer(ee_object.toBands(), {}, layer_name, False, opacity) self.addLayer(first, vis_params, "Image X", True, opacity) @@ -5264,7 +5245,7 @@ def slider_changed(change): index = slider.value - 1 label.value = labels[index] image = ee.Image(ee_object.toList(ee_object.size()).get(index)) - if layer_name not in self.ee_layers: + if layer_name not in self.ee_raster_layer_names: self.addLayer(ee_object.toBands(), {}, layer_name, False, opacity) self.addLayer(image, vis_params, "Image X", True, opacity) self.default_style = {"cursor": "default"} diff --git a/geemap/map_widgets.py b/geemap/map_widgets.py index ee0e87cdff..3c143f4409 100644 --- a/geemap/map_widgets.py +++ b/geemap/map_widgets.py @@ -304,10 +304,10 @@ def _get_visible_map_layers(self): if self._names is not None: names = [names] if isinstance(names, str) else self._names for name in names: - if name in self._host_map.ee_layers: - layers[name] = self._host_map.ee_layers[name] + if name in self._host_map.ee_layer_names: + layers[name] = self._host_map.ee_layer_dict[name] else: - layers = self._host_map.ee_layers + layers = self._host_map.ee_layer_dict return {k: v for k, v in layers.items() if v["ee_layer"].visible} def _root_node(self, title, nodes, **kwargs): @@ -774,10 +774,10 @@ def _on_layer_visibility_changed(self, change, layer): layer.visible = change["new"] layer_name = change["owner"].description - if layer_name not in self._host_map.ee_layers: + if layer_name not in self._host_map.ee_layer_names: return - layer_dict = self._host_map.ee_layers[layer_name] + layer_dict = self._host_map.ee_layer_dict[layer_name] for attachment_name in ["legend", "colorbar"]: attachment = layer_dict.get(attachment_name, None) attachment_on_map = attachment in self._host_map.controls diff --git a/geemap/toolbar.py b/geemap/toolbar.py index b714895526..e32fc24bbf 100644 --- a/geemap/toolbar.py +++ b/geemap/toolbar.py @@ -204,7 +204,7 @@ def _layers_btn_click(self, change): def _on_open_vis(layer_name): self.host_map.create_vis_widget( - self.host_map.ee_layers.get(layer_name, None) + self.host_map.ee_layer_dict.get(layer_name, None) ) self.host_map.layer_manager_widget = map_widgets.LayerManager(self.host_map) @@ -666,7 +666,9 @@ def ee_plot_gui(m, position="topright", **kwargs): ) m._plot_checked = True - dropdown = widgets.Dropdown(options=list(m.ee_raster_layers.keys())) + dropdown = widgets.Dropdown( + options=list(m.ee_raster_layer_names), + ) dropdown.layout.width = "18ex" m._plot_dropdown_widget = dropdown @@ -695,7 +697,10 @@ def handle_interaction(**kwargs): and len(m.ee_raster_layers) > 0 ): plot_layer_name = m._plot_dropdown_widget.value - ee_object = m.ee_layers.get(plot_layer_name)["ee_object"] + layer_names = m.ee_raster_layer_names + layers = m.ee_raster_layers + index = layer_names.index(plot_layer_name) + ee_object = layers[index] if isinstance(ee_object, ee.ImageCollection): ee_object = ee_object.mosaic() @@ -2786,7 +2791,7 @@ def time_slider(m=None): col_options = list(col_options_dict.keys()) if m is not None: - col_options += m.ee_raster_layers + col_options += m.ee_raster_layer_names collection = widgets.Dropdown( options=col_options, @@ -2797,7 +2802,7 @@ def time_slider(m=None): ) region = widgets.Dropdown( - options=["User-drawn ROI"] + m.ee_vector_layers.keys(), + options=["User-drawn ROI"] + m.ee_vector_layer_names, value="User-drawn ROI", description="Region:", layout=widgets.Layout(width=widget_width, padding=padding), @@ -3200,8 +3205,8 @@ def submit_clicked(b): with output: print("Use the Drawing tool to create an ROI.") return - elif region.value in m.ee_layers: - roi = m.ee_layers[region.value]["ee_object"] + elif region.value in m.ee_layer_dict: + roi = m.ee_layer_dict[region.value]["ee_object"] with output: print("Computing... Please wait...") @@ -3249,8 +3254,8 @@ def submit_clicked(b): except Exception as e: raise ValueError(e) - if collection.value in m.ee_raster_layers: - layer = m.ee_layers[collection.value] + if collection.value in m.ee_raster_layer_names: + layer = m.ee_layer_dict[collection.value] ee_object = layer["ee_object"] elif collection.value in col_options_dict: start_date = str(start_month.value).zfill(2) + "-01" @@ -3357,13 +3362,13 @@ def close_click(change): def collection_changed(change): if change["new"]: selected = change["owner"].value - if selected in m.ee_layers: + if selected in m.ee_layer_dict: prebuilt_options.children = [] labels.value = "" region.value = None - ee_object = m.ee_layers[selected]["ee_object"] - vis_params = m.ee_layers[selected]["vis_params"] + ee_object = m.ee_layer_dict[selected]["ee_object"] + vis_params = m.ee_layer_dict[selected]["vis_params"] if isinstance(ee_object, ee.Image): palette_vbox.children = [ widgets.HBox([classes, colormap]), @@ -3698,10 +3703,10 @@ def plot_transect(m=None): ) if m is not None: - layer.options = m.ee_raster_layers.keys() + layer.options = m.ee_raster_layer_names layer.value = layer.options[0] if len(layer.options) > 0: - image = m.ee_layers[layer.value]["ee_object"] + image = m.ee_layer_dict[layer.value]["ee_object"] if isinstance(image, ee.ImageCollection): image = image.toBands() band.options = image.bandNames().getInfo() @@ -3715,7 +3720,7 @@ def plot_transect(m=None): def layer_changed(change): if change["new"]: if m is not None: - image = m.ee_layers[layer.value]["ee_object"] + image = m.ee_layer_dict[layer.value]["ee_object"] if isinstance(image, ee.ImageCollection): image = image.toBands() band.options = image.bandNames().getInfo() @@ -3770,7 +3775,7 @@ def button_clicked(change): if geom_type != "LineString": print("Use drawing tool to draw a line") else: - image = m.ee_layers[layer.value]["ee_object"] + image = m.ee_layer_dict[layer.value]["ee_object"] if isinstance(image, ee.ImageCollection): image = image.toBands() image = image.select([band.value]) @@ -3975,10 +3980,10 @@ def dataset_changed(change): ) if m is not None: - if "Las Vegas" not in m.ee_vector_layers.keys(): - region.options = ["User-drawn ROI", "Las Vegas"] + m.ee_vector_layers.keys() + if "Las Vegas" not in m.ee_vector_layer_names: + region.options = ["User-drawn ROI", "Las Vegas"] + m.ee_vector_layer_names else: - region.options = ["User-drawn ROI"] + m.ee_vector_layers.keys() + region.options = ["User-drawn ROI"] + m.ee_vector_layer_names plot_close_btn = widgets.Button( tooltip="Close the plot", @@ -4196,7 +4201,7 @@ def button_clicked(change): image1 = image1.clip(geom) image2 = image2.clip(geom) else: - roi_object = m.ee_layers[region.value]["ee_object"] + roi_object = m.ee_layer_dict[region.value]["ee_object"] if region.value == "Las Vegas": m.centerObject(roi_object, 10) if isinstance(roi_object, ee.Geometry): diff --git a/tests/fake_map.py b/tests/fake_map.py index 0f255ef002..b3c1d3562b 100644 --- a/tests/fake_map.py +++ b/tests/fake_map.py @@ -7,7 +7,7 @@ def __init__(self): self.scale = 1024 self.zoom = 7 self.layers = [] - self.ee_layers = {} + self.ee_layer_dict = {} self.layers = [] self.geojson_layers = [] diff --git a/tests/test_map_widgets.py b/tests/test_map_widgets.py index 599e926f6e..b22363dcfb 100644 --- a/tests/test_map_widgets.py +++ b/tests/test_map_widgets.py @@ -365,7 +365,7 @@ def test_map_empty_click(self): def test_map_click(self): """Tests that clicking the map triggers inspection.""" - self.map_fake.ee_layers = { + self.map_fake.ee_layer_dict = { "test-map-1": { "ee_object": ee.Image(1), "ee_layer": fake_map.FakeEeTileLayer(visible=True), @@ -410,7 +410,7 @@ def test_map_click(self): def test_map_click_twice(self): """Tests that clicking the map a second time removes the original output.""" - self.map_fake.ee_layers = { + self.map_fake.ee_layer_dict = { "test-map-1": { "ee_object": ee.Image(1), "ee_layer": fake_map.FakeEeTileLayer(visible=True), @@ -494,7 +494,7 @@ def setUp(self): style={"some-style": "red", "opacity": 0.3, "fillOpacity": 0.2}, ), ] - self.fake_map.ee_layers = { + self.fake_map.ee_layer_dict = { "test-layer": { "ee_object": None, "ee_layer": self.fake_map.layers[2], @@ -633,39 +633,42 @@ def test_layer_manager_close_button_hidden(self): @patch.object(ee, "Image", fake_ee.Image) class TestAbstractDrawControl(unittest.TestCase): """Tests for the draw control interface in the `map_widgets` module.""" - geo_json = { - "type": "Feature", - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [0, 1], - [0, -1], - [1, -1], - [1, 1], - [0, 1], - ] - ], - }, - "properties": {"name": "Null Island"}, - } + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [0, 1], + [0, -1], + [1, -1], + [1, 1], + [0, 1], + ] + ], + }, + "properties": { + "name": "Null Island" + } + } geo_json2 = { - "type": "Feature", - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [0, 2], - [0, -2], - [2, -2], - [2, 2], - [0, 2], - ] - ], - }, - "properties": {"name": "Null Island 2x"}, - } + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [0, 2], + [0, -2], + [2, -2], + [2, 2], + [0, 2], + ] + ], + }, + "properties": { + "name": "Null Island 2x" + } + } def setUp(self): map = fake_map.FakeMap() @@ -726,14 +729,16 @@ def test_property_accessors(self): self.assertEquals(self.draw_control.last_geometry, geometry) # Test last_draw_action accessor. self.assertEquals( - self.draw_control.last_draw_action, map_widgets.DrawActions.CREATED + self.draw_control.last_draw_action, + map_widgets.DrawActions.CREATED ) # Test features accessor. feature = fake_ee.Feature(geometry, None) self.assertEquals(self.draw_control.features, [feature]) # Test collection accessor. self.assertEquals( - self.draw_control.collection, fake_ee.FeatureCollection([feature]) + self.draw_control.collection, + fake_ee.FeatureCollection([feature]) ) # Test last_feature accessor. self.assertEquals(self.draw_control.last_feature, feature) @@ -743,11 +748,17 @@ def test_property_accessors(self): def test_feature_property_access(self): self.draw_control.create(self.geo_json) geometry = self.draw_control.geometries[0] - self.assertIsNone(self.draw_control.get_geometry_properties(geometry)) - self.assertEquals(self.draw_control.features, [fake_ee.Feature(geometry, None)]) + self.assertIsNone( + self.draw_control.get_geometry_properties(geometry) + ) + self.assertEquals( + self.draw_control.features, + [fake_ee.Feature(geometry, None)] + ) self.draw_control.set_geometry_properties(geometry, {"test": 1}) self.assertEquals( - self.draw_control.features, [fake_ee.Feature(geometry, {"test": 1})] + self.draw_control.features, + [fake_ee.Feature(geometry, {"test": 1})] ) def test_reset(self): @@ -775,7 +786,8 @@ def test_remove_geometry(self): self.assertEquals(len(self.draw_control.geometries), 2) self.assertEquals(len(self.draw_control.properties), 2) self.assertEquals( - self.draw_control.last_draw_action, map_widgets.DrawActions.CREATED + self.draw_control.last_draw_action, + map_widgets.DrawActions.CREATED ) self.assertEquals(self.draw_control.last_geometry, geometry2) @@ -785,7 +797,8 @@ def test_remove_geometry(self): self.assertEquals(len(self.draw_control.geometries), 1) self.assertEquals(len(self.draw_control.properties), 1) self.assertEquals( - self.draw_control.last_draw_action, map_widgets.DrawActions.REMOVED_LAST + self.draw_control.last_draw_action, + map_widgets.DrawActions.REMOVED_LAST ) self.assertEquals(self.draw_control.last_geometry, geometry1) @@ -794,7 +807,8 @@ def test_remove_geometry(self): self.assertEquals(len(self.draw_control.geometries), 0) self.assertEquals(len(self.draw_control.properties), 0) self.assertEquals( - self.draw_control.last_draw_action, map_widgets.DrawActions.REMOVED_LAST + self.draw_control.last_draw_action, + map_widgets.DrawActions.REMOVED_LAST ) self.assertEquals(self.draw_control.last_geometry, geometry1) @@ -808,13 +822,13 @@ def test_remove_geometry(self): self.assertEquals(len(self.draw_control.geometries), 1) self.assertEquals(len(self.draw_control.properties), 1) self.assertEquals( - self.draw_control.last_draw_action, map_widgets.DrawActions.DELETED + self.draw_control.last_draw_action, + map_widgets.DrawActions.DELETED ) self.assertEquals(self.draw_control.last_geometry, geometry1) class TestDrawControl(map_widgets.AbstractDrawControl): """Implements an AbstractDrawControl for tests.""" - geo_jsons = [] initialized = False @@ -825,7 +839,8 @@ def __init__(self, host_map, **kwargs): host_map (geemap.Map): The geemap.Map object """ super(TestAbstractDrawControl.TestDrawControl, self).__init__( - host_map=host_map, **kwargs + host_map=host_map, + **kwargs ) self.geo_jsons = []