diff --git a/geemap/geemap.py b/geemap/geemap.py index 005b3a8cb9..896887189a 100644 --- a/geemap/geemap.py +++ b/geemap/geemap.py @@ -278,13 +278,7 @@ def __init__(self, **kwargs): # Map attributes for layers self.geojson_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 = {} + self.ee_layers = {} # ipyleaflet built-in layer control self.layer_control = None @@ -294,6 +288,53 @@ 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. @@ -406,30 +447,21 @@ def add_ee_layer( layer = self.find_layer(name=name) if layer is not None: - existing_object = self.ee_layer_dict[name]["ee_object"] + existing_object = self.ee_layers[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_layer_names + self.ee_raster_layers.keys() ) - 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.remove(existing_object) - self.ee_layer_names.remove(name) + self.ee_layers.pop(name, None) self.remove_layer(layer) - 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] = { + self.ee_layers[name] = { "ee_object": ee_object, "ee_layer": tile_layer, "vis_params": vis_params, @@ -438,16 +470,11 @@ 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_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) + self._plot_dropdown_widget.options = list(self.ee_raster_layers.keys()) arc_add_layer(tile_layer.url_format, name, shown, opacity) @@ -459,17 +486,9 @@ def remove_ee_layer(self, name): Args: name (str): The name of the Earth Engine layer to remove. """ - 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 name in self.ee_layers: + ee_layer = self.ee_layers[name]["ee_layer"] + self.ee_layers.pop(name, None) if ee_layer in self.layers: self.remove_layer(ee_layer) @@ -1098,8 +1117,8 @@ def add_legend( else: self.legends.append(legend_control) - if layer_name in self.ee_layer_names: - self.ee_layer_dict[layer_name]["legend"] = legend_control + if layer_name in self.ee_layers: + self.ee_layers[layer_name]["legend"] = legend_control except Exception as e: raise Exception(e) @@ -1160,10 +1179,10 @@ def add_colorbar( ) self._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 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 not hasattr(self, "colorbars"): self.colorbars = [colormap_ctrl] else: @@ -1200,7 +1219,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_layer_dict. + layer_dict (dict): A dict containning information about the layer. It is an element from Map.ee_layers. Returns: object: An ipywidget. @@ -2427,16 +2446,16 @@ def import_btn_clicked(b): def apply_btn_clicked(b): compute_label.value = "Computing ..." - if new_layer_name.value in self.ee_layer_names: + if new_layer_name.value in self.ee_layers: old_layer = new_layer_name.value - if "legend" in self.ee_layer_dict[old_layer].keys(): - legend = self.ee_layer_dict[old_layer]["legend"] + if "legend" in self.ee_layers[old_layer].keys(): + legend = self.ee_layers[old_layer]["legend"] if legend in self.controls: self.remove_control(legend) legend.close() - if "colorbar" in self.ee_layer_dict[old_layer].keys(): - colorbar = self.ee_layer_dict[old_layer]["colorbar"] + if "colorbar" in self.ee_layers[old_layer].keys(): + colorbar = self.ee_layers[old_layer]["colorbar"] if colorbar in self.controls: self.remove_control(colorbar) colorbar.close() @@ -2619,7 +2638,7 @@ def _on_close(): self.layer_manager_control = None def _on_open_vis(layer_name): - self.create_vis_widget(self.ee_layer_dict.get(layer_name, None)) + self.create_vis_widget(self.ee_layers.get(layer_name, None)) self.layer_manager_widget = map_widgets.LayerManager(self) self.layer_manager_widget.collapsed = not opened @@ -3137,7 +3156,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[-1] + ee_object = self.ee_raster_layers.values()[-1]["ee_object"] if isinstance(ee_object, ee.ImageCollection): ee_object = ee_object.mosaic() elif isinstance(ee_object, ee.ImageCollection): @@ -3709,8 +3728,8 @@ def add_colorbar_branca( else: self.colorbars.append(colormap_ctrl) - if layer_name in self.ee_layer_names: - self.ee_layer_dict[layer_name]["colorbar"] = colormap_ctrl + if layer_name in self.ee_layers: + self.ee_layers[layer_name]["colorbar"] = colormap_ctrl def image_overlay(self, url, bounds, name): """Overlays an image from the Internet or locally on the map. @@ -5134,7 +5153,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_raster_layer_names: + if layer_name not in self.ee_layers: self.addLayer(ee_object, {}, layer_name, False, opacity) band_names = ee_object.bandNames() ee_object = ee.ImageCollection( @@ -5178,7 +5197,7 @@ def add_time_slider( first = ee.Image(ee_object.first()) - if layer_name not in self.ee_raster_layer_names: + if layer_name not in self.ee_layers: self.addLayer(ee_object.toBands(), {}, layer_name, False, opacity) self.addLayer(first, vis_params, "Image X", True, opacity) @@ -5245,7 +5264,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_raster_layer_names: + if layer_name not in self.ee_layers: 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 3c143f4409..ee0e87cdff 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_layer_names: - layers[name] = self._host_map.ee_layer_dict[name] + if name in self._host_map.ee_layers: + layers[name] = self._host_map.ee_layers[name] else: - layers = self._host_map.ee_layer_dict + layers = self._host_map.ee_layers 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_layer_names: + if layer_name not in self._host_map.ee_layers: return - layer_dict = self._host_map.ee_layer_dict[layer_name] + layer_dict = self._host_map.ee_layers[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 e32fc24bbf..b714895526 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_layer_dict.get(layer_name, None) + self.host_map.ee_layers.get(layer_name, None) ) self.host_map.layer_manager_widget = map_widgets.LayerManager(self.host_map) @@ -666,9 +666,7 @@ def ee_plot_gui(m, position="topright", **kwargs): ) m._plot_checked = True - dropdown = widgets.Dropdown( - options=list(m.ee_raster_layer_names), - ) + dropdown = widgets.Dropdown(options=list(m.ee_raster_layers.keys())) dropdown.layout.width = "18ex" m._plot_dropdown_widget = dropdown @@ -697,10 +695,7 @@ def handle_interaction(**kwargs): and len(m.ee_raster_layers) > 0 ): plot_layer_name = m._plot_dropdown_widget.value - layer_names = m.ee_raster_layer_names - layers = m.ee_raster_layers - index = layer_names.index(plot_layer_name) - ee_object = layers[index] + ee_object = m.ee_layers.get(plot_layer_name)["ee_object"] if isinstance(ee_object, ee.ImageCollection): ee_object = ee_object.mosaic() @@ -2791,7 +2786,7 @@ def time_slider(m=None): col_options = list(col_options_dict.keys()) if m is not None: - col_options += m.ee_raster_layer_names + col_options += m.ee_raster_layers collection = widgets.Dropdown( options=col_options, @@ -2802,7 +2797,7 @@ def time_slider(m=None): ) region = widgets.Dropdown( - options=["User-drawn ROI"] + m.ee_vector_layer_names, + options=["User-drawn ROI"] + m.ee_vector_layers.keys(), value="User-drawn ROI", description="Region:", layout=widgets.Layout(width=widget_width, padding=padding), @@ -3205,8 +3200,8 @@ def submit_clicked(b): with output: print("Use the Drawing tool to create an ROI.") return - elif region.value in m.ee_layer_dict: - roi = m.ee_layer_dict[region.value]["ee_object"] + elif region.value in m.ee_layers: + roi = m.ee_layers[region.value]["ee_object"] with output: print("Computing... Please wait...") @@ -3254,8 +3249,8 @@ def submit_clicked(b): except Exception as e: raise ValueError(e) - if collection.value in m.ee_raster_layer_names: - layer = m.ee_layer_dict[collection.value] + if collection.value in m.ee_raster_layers: + layer = m.ee_layers[collection.value] ee_object = layer["ee_object"] elif collection.value in col_options_dict: start_date = str(start_month.value).zfill(2) + "-01" @@ -3362,13 +3357,13 @@ def close_click(change): def collection_changed(change): if change["new"]: selected = change["owner"].value - if selected in m.ee_layer_dict: + if selected in m.ee_layers: prebuilt_options.children = [] labels.value = "" region.value = None - ee_object = m.ee_layer_dict[selected]["ee_object"] - vis_params = m.ee_layer_dict[selected]["vis_params"] + ee_object = m.ee_layers[selected]["ee_object"] + vis_params = m.ee_layers[selected]["vis_params"] if isinstance(ee_object, ee.Image): palette_vbox.children = [ widgets.HBox([classes, colormap]), @@ -3703,10 +3698,10 @@ def plot_transect(m=None): ) if m is not None: - layer.options = m.ee_raster_layer_names + layer.options = m.ee_raster_layers.keys() layer.value = layer.options[0] if len(layer.options) > 0: - image = m.ee_layer_dict[layer.value]["ee_object"] + image = m.ee_layers[layer.value]["ee_object"] if isinstance(image, ee.ImageCollection): image = image.toBands() band.options = image.bandNames().getInfo() @@ -3720,7 +3715,7 @@ def plot_transect(m=None): def layer_changed(change): if change["new"]: if m is not None: - image = m.ee_layer_dict[layer.value]["ee_object"] + image = m.ee_layers[layer.value]["ee_object"] if isinstance(image, ee.ImageCollection): image = image.toBands() band.options = image.bandNames().getInfo() @@ -3775,7 +3770,7 @@ def button_clicked(change): if geom_type != "LineString": print("Use drawing tool to draw a line") else: - image = m.ee_layer_dict[layer.value]["ee_object"] + image = m.ee_layers[layer.value]["ee_object"] if isinstance(image, ee.ImageCollection): image = image.toBands() image = image.select([band.value]) @@ -3980,10 +3975,10 @@ def dataset_changed(change): ) if m is not None: - if "Las Vegas" not in m.ee_vector_layer_names: - region.options = ["User-drawn ROI", "Las Vegas"] + m.ee_vector_layer_names + if "Las Vegas" not in m.ee_vector_layers.keys(): + region.options = ["User-drawn ROI", "Las Vegas"] + m.ee_vector_layers.keys() else: - region.options = ["User-drawn ROI"] + m.ee_vector_layer_names + region.options = ["User-drawn ROI"] + m.ee_vector_layers.keys() plot_close_btn = widgets.Button( tooltip="Close the plot", @@ -4201,7 +4196,7 @@ def button_clicked(change): image1 = image1.clip(geom) image2 = image2.clip(geom) else: - roi_object = m.ee_layer_dict[region.value]["ee_object"] + roi_object = m.ee_layers[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 b3c1d3562b..0f255ef002 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_layer_dict = {} + self.ee_layers = {} self.layers = [] self.geojson_layers = [] diff --git a/tests/test_map_widgets.py b/tests/test_map_widgets.py index b22363dcfb..599e926f6e 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_layer_dict = { + self.map_fake.ee_layers = { "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_layer_dict = { + self.map_fake.ee_layers = { "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_layer_dict = { + self.fake_map.ee_layers = { "test-layer": { "ee_object": None, "ee_layer": self.fake_map.layers[2], @@ -633,42 +633,39 @@ 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() @@ -729,16 +726,14 @@ 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) @@ -748,17 +743,11 @@ 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): @@ -786,8 +775,7 @@ 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) @@ -797,8 +785,7 @@ 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) @@ -807,8 +794,7 @@ 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) @@ -822,13 +808,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 @@ -839,8 +825,7 @@ 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 = []