Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
giswqs
GitHub Repository: giswqs/geemap
Path: blob/master/tests/test_core.py
2313 views
1
#!/usr/bin/env python
2
"""Tests for `map_widgets` module."""
3
4
import unittest
5
from unittest import mock
6
7
import ee
8
import ipyleaflet
9
10
from geemap import core
11
from geemap import map_widgets
12
from geemap import toolbar
13
from tests import fake_ee
14
from tests import fake_map
15
16
17
@mock.patch.object(ee, "FeatureCollection", fake_ee.FeatureCollection)
18
@mock.patch.object(ee, "Geometry", fake_ee.Geometry)
19
class TestMap(unittest.TestCase):
20
"""Tests for Map."""
21
22
def _clear_default_widgets(self):
23
widgets = [
24
"search_control",
25
"zoom_control",
26
"fullscreen_control",
27
"scale_control",
28
"attribution_control",
29
"toolbar",
30
"inspector",
31
"layer_manager",
32
"draw_control",
33
]
34
for widget in widgets:
35
self.core_map.remove(widget)
36
37
def setUp(self):
38
super().setUp()
39
self.core_map = core.Map(ee_initialize=False, width="100%")
40
41
def test_defaults(self):
42
"""Tests that map defaults are set properly."""
43
self.assertEqual(self.core_map.width, "100%")
44
self.assertEqual(self.core_map.height, "600px")
45
self.assertEqual(self.core_map.get_center(), [0, 0])
46
self.assertEqual(self.core_map.get_zoom(), 2)
47
48
controls = self.core_map.controls
49
self.assertEqual(len(controls), 7)
50
self.assertIsInstance(controls[0], ipyleaflet.WidgetControl)
51
self.assertIsInstance(controls[0].widget.children[0], map_widgets.LayerManager)
52
self.assertIsInstance(controls[0].widget.children[1], toolbar.Toolbar)
53
self.assertIsInstance(controls[1].widget, map_widgets.SearchBar)
54
self.assertIsInstance(controls[2], ipyleaflet.ZoomControl)
55
self.assertIsInstance(controls[3], ipyleaflet.FullScreenControl)
56
self.assertIsInstance(controls[4], core.MapDrawControl)
57
self.assertIsInstance(controls[5], ipyleaflet.ScaleControl)
58
self.assertIsInstance(controls[6], ipyleaflet.AttributionControl)
59
60
def test_set_center(self):
61
"""Tests that `set_center` sets the center and zoom."""
62
self.core_map.set_center(1, 2, 3)
63
self.assertEqual(self.core_map.get_center(), [2, 1])
64
self.assertEqual(self.core_map.get_zoom(), 3)
65
self.core_map.set_center(5, 6)
66
self.assertEqual(self.core_map.get_center(), [6, 5])
67
self.assertEqual(self.core_map.get_zoom(), 3)
68
69
def test_scale(self):
70
"""Tests that `scale` is calculated correctly."""
71
self.core_map.set_center(0, 0, 2)
72
self.assertAlmostEqual(self.core_map.get_scale(), 39135.76, places=2)
73
self.core_map.set_center(-10, 4, 8)
74
self.assertAlmostEqual(self.core_map.get_scale(), 610.01, places=2)
75
76
def test_center_object(self):
77
"""Tests that `center_object` fits the object to the bounds."""
78
fit_bounds_mock = mock.Mock()
79
self.core_map.fit_bounds = fit_bounds_mock
80
self.core_map.center_object(ee.Geometry.Point())
81
fit_bounds_mock.assert_called_with([[-76, -178], [80, 179]])
82
83
fit_bounds_mock = mock.Mock()
84
self.core_map.fit_bounds = fit_bounds_mock
85
self.core_map.center_object(ee.FeatureCollection([]))
86
fit_bounds_mock.assert_called_with([[-76, -178], [80, 179]])
87
88
set_center_mock = mock.Mock()
89
self.core_map.set_center = set_center_mock
90
self.core_map.center_object(ee.Geometry.Point(), 2)
91
set_center_mock.assert_called_with(120, -70, 2)
92
93
with self.assertRaisesRegex(Exception, "must be one of"):
94
self.core_map.center_object("invalid object")
95
96
with self.assertRaisesRegex(ValueError, "Zoom must be an integer"):
97
self.core_map.center_object(ee.Geometry.Point(), "2")
98
99
@mock.patch.object(core.Map, "bounds")
100
def test_get_bounds(self, mock_bounds):
101
"""Tests that `get_bounds` returns the bounds of the map."""
102
mock_bounds.__get__ = mock.Mock(return_value=[[1, 2], [3, 4]])
103
self.assertEqual(self.core_map.get_bounds(), [2, 1, 4, 3])
104
self.assertEqual(self.core_map.getBounds(), [2, 1, 4, 3])
105
expected_geo_json = {
106
"geodesic": False,
107
"type": "Polygon",
108
"coordinates": [[0, 1], [1, 2], [0, 1]],
109
}
110
self.assertEqual(self.core_map.get_bounds(as_geojson=True), expected_geo_json)
111
112
mock_bounds.__get__ = mock.Mock(return_value=())
113
with self.assertRaisesRegex(RuntimeError, "Map bounds are undefined"):
114
self.core_map.get_bounds(as_geojson=True)
115
116
def test_add_basic_widget_by_name(self):
117
"""Tests that `add` adds widgets by name."""
118
self._clear_default_widgets()
119
120
self.core_map.add("scale_control", position="topleft", metric=False)
121
122
# Scale control and top right layout box.
123
self.assertEqual(len(self.core_map.controls), 2)
124
control = self.core_map.controls[1]
125
self.assertIsInstance(control, ipyleaflet.ScaleControl)
126
self.assertEqual(control.position, "topleft")
127
self.assertEqual(control.metric, False)
128
129
def test_add_basic_widget(self):
130
"""Tests that `add` adds widget instances to the map."""
131
self._clear_default_widgets()
132
133
self.core_map.add(ipyleaflet.ScaleControl(position="topleft", metric=False))
134
135
# Scale control and top right layout box.
136
self.assertEqual(len(self.core_map.controls), 2)
137
control = self.core_map.controls[1]
138
self.assertIsInstance(control, ipyleaflet.ScaleControl)
139
self.assertEqual(control.position, "topleft")
140
self.assertEqual(control.metric, False)
141
142
def test_add_duplicate_basic_widget(self):
143
"""Tests adding a duplicate widget to the map."""
144
self.assertEqual(len(self.core_map.controls), 7)
145
self.assertIsInstance(self.core_map.controls[1], ipyleaflet.WidgetControl)
146
self.assertEqual(self.core_map.controls[1].position, "topleft")
147
148
self.core_map.add("zoom_control", position="bottomright")
149
150
self.assertEqual(len(self.core_map.controls), 7)
151
self.assertIsInstance(self.core_map.controls[1], ipyleaflet.WidgetControl)
152
self.assertEqual(self.core_map.controls[1].position, "topleft")
153
154
def test_add_toolbar(self):
155
"""Tests adding the toolbar widget."""
156
self._clear_default_widgets()
157
158
self.core_map.add("toolbar", position="bottomright")
159
160
# Toolbar and top right layout box.
161
self.assertEqual(len(self.core_map.controls), 2)
162
toolbar_control = self.core_map.controls[1].widget
163
164
self.assertEqual(len(toolbar_control.main_tools), 3)
165
self.assertEqual(toolbar_control.main_tools[0].tooltip_text, "Basemap selector")
166
self.assertEqual(toolbar_control.main_tools[1].tooltip_text, "Inspector")
167
self.assertEqual(toolbar_control.main_tools[2].tooltip_text, "Get help")
168
169
def test_add_draw_control(self):
170
"""Tests adding and getting the draw widget."""
171
172
self._clear_default_widgets()
173
self.core_map.add("draw_control", position="topleft")
174
175
# Draw control and top right layout box.
176
self.assertEqual(len(self.core_map.controls), 2)
177
self.assertIsInstance(self.core_map.get_draw_control(), core.MapDrawControl)
178
179
def test_add_basemap_selector(self):
180
"""Tests adding the basemap selector widget."""
181
self._clear_default_widgets()
182
183
self.core_map.add("basemap_selector")
184
185
# Basemap selector and top right layout box.
186
self.assertEqual(len(self.core_map.controls), 2)
187
188
189
@mock.patch.object(ee, "FeatureCollection", fake_ee.FeatureCollection)
190
@mock.patch.object(ee, "Feature", fake_ee.Feature)
191
@mock.patch.object(ee, "Geometry", fake_ee.Geometry)
192
@mock.patch.object(ee, "Image", fake_ee.Image)
193
class TestAbstractDrawControl(unittest.TestCase):
194
"""Tests for the draw control interface in the `draw_control` module."""
195
196
geo_json = {
197
"type": "Feature",
198
"geometry": {
199
"type": "Polygon",
200
"coordinates": [
201
[
202
[0, 1],
203
[0, -1],
204
[1, -1],
205
[1, 1],
206
[0, 1],
207
]
208
],
209
},
210
"properties": {"name": "Null Island"},
211
}
212
geo_json2 = {
213
"type": "Feature",
214
"geometry": {
215
"type": "Polygon",
216
"coordinates": [
217
[
218
[0, 2],
219
[0, -2],
220
[2, -2],
221
[2, 2],
222
[0, 2],
223
]
224
],
225
},
226
"properties": {"name": "Null Island 2x"},
227
}
228
229
def setUp(self):
230
super().setUp()
231
self.map = fake_map.FakeMap()
232
self._draw_control = TestAbstractDrawControl.TestDrawControl(self.map)
233
234
def test_initialization(self):
235
# Initialized is set by the `_bind_draw_controls` method.
236
self.assertTrue(self._draw_control.initialized)
237
self.assertIsNone(self._draw_control.layer)
238
self.assertEqual(self._draw_control.geometries, [])
239
self.assertEqual(self._draw_control.properties, [])
240
self.assertIsNone(self._draw_control.last_geometry)
241
self.assertIsNone(self._draw_control.last_draw_action)
242
self.assertEqual(self._draw_control.features, [])
243
self.assertEqual(self._draw_control.collection, fake_ee.FeatureCollection([]))
244
self.assertIsNone(self._draw_control.last_feature)
245
self.assertEqual(self._draw_control.count, 0)
246
self.assertFalse("Drawn Features" in self.map.ee_layers)
247
248
def test_handles_creation(self):
249
self._draw_control.create(self.geo_json)
250
self.assertEqual(
251
self._draw_control.geometries,
252
[fake_ee.Geometry(self.geo_json["geometry"])],
253
)
254
self.assertTrue("Drawn Features" in self.map.ee_layers)
255
256
def test_handles_deletion(self):
257
self._draw_control.create(self.geo_json)
258
self.assertTrue("Drawn Features" in self.map.ee_layers)
259
self.assertEqual(len(self._draw_control.geometries), 1)
260
self._draw_control.delete(0)
261
self.assertEqual(len(self._draw_control.geometries), 0)
262
self.assertFalse("Drawn Features" in self.map.ee_layers)
263
264
def test_handles_edit(self):
265
self._draw_control.create(self.geo_json)
266
self.assertEqual(len(self._draw_control.geometries), 1)
267
268
self._draw_control.edit(0, self.geo_json2)
269
self.assertEqual(len(self._draw_control.geometries), 1)
270
self.assertEqual(
271
self._draw_control.geometries[0],
272
fake_ee.Geometry(self.geo_json2["geometry"]),
273
)
274
275
def test_property_accessors(self):
276
self._draw_control.create(self.geo_json)
277
278
# Test layer accessor.
279
self.assertIsNotNone(self._draw_control.layer)
280
# Test geometries accessor.
281
geometry = fake_ee.Geometry(self.geo_json["geometry"])
282
self.assertEqual(len(self._draw_control.geometries), 1)
283
self.assertEqual(self._draw_control.geometries, [geometry])
284
# Test properties accessor.
285
self.assertEqual(self._draw_control.properties, [None])
286
# Test last_geometry accessor.
287
self.assertEqual(self._draw_control.last_geometry, geometry)
288
# Test last_draw_action accessor.
289
self.assertEqual(self._draw_control.last_draw_action, core.DrawActions.CREATED)
290
# Test features accessor.
291
feature = fake_ee.Feature(geometry, None)
292
self.assertEqual(self._draw_control.features, [feature])
293
# Test collection accessor.
294
self.assertEqual(
295
self._draw_control.collection, fake_ee.FeatureCollection([feature])
296
)
297
# Test last_feature accessor.
298
self.assertEqual(self._draw_control.last_feature, feature)
299
# Test count accessor.
300
self.assertEqual(self._draw_control.count, 1)
301
302
def test_feature_property_access(self):
303
self._draw_control.create(self.geo_json)
304
geometry = self._draw_control.geometries[0]
305
self.assertIsNone(self._draw_control.get_geometry_properties(geometry))
306
self.assertEqual(self._draw_control.features, [fake_ee.Feature(geometry, None)])
307
self._draw_control.set_geometry_properties(geometry, {"test": 1})
308
self.assertEqual(
309
self._draw_control.features, [fake_ee.Feature(geometry, {"test": 1})]
310
)
311
312
def test_reset(self):
313
self._draw_control.create(self.geo_json)
314
self.assertEqual(len(self._draw_control.geometries), 1)
315
316
# When clear_draw_control is True, deletes the underlying geometries.
317
self._draw_control.reset(clear_draw_control=True)
318
self.assertEqual(len(self._draw_control.geometries), 0)
319
self.assertEqual(len(self._draw_control.geo_jsons), 0)
320
self.assertFalse("Drawn Features" in self.map.ee_layers)
321
322
self._draw_control.create(self.geo_json)
323
self.assertEqual(len(self._draw_control.geometries), 1)
324
# When clear_draw_control is False, does not delete the underlying geometries.
325
self._draw_control.reset(clear_draw_control=False)
326
self.assertEqual(len(self._draw_control.geometries), 0)
327
self.assertEqual(len(self._draw_control.geo_jsons), 1)
328
self.assertFalse("Drawn Features" in self.map.ee_layers)
329
330
def test_remove_geometry(self):
331
self._draw_control.create(self.geo_json)
332
self._draw_control.create(self.geo_json2)
333
geometry1 = self._draw_control.geometries[0]
334
geometry2 = self._draw_control.geometries[1]
335
self.assertEqual(len(self._draw_control.geometries), 2)
336
self.assertEqual(len(self._draw_control.properties), 2)
337
self.assertEqual(self._draw_control.last_draw_action, core.DrawActions.CREATED)
338
self.assertEqual(self._draw_control.last_geometry, geometry2)
339
340
# When there are two geometries and the removed geometry is the last one, then
341
# we treat it like an undo.
342
self._draw_control.remove_geometry(geometry2)
343
self.assertEqual(len(self._draw_control.geometries), 1)
344
self.assertEqual(len(self._draw_control.properties), 1)
345
self.assertEqual(
346
self._draw_control.last_draw_action, core.DrawActions.REMOVED_LAST
347
)
348
self.assertEqual(self._draw_control.last_geometry, geometry1)
349
350
# When there's only one geometry, last_geometry is the removed geometry.
351
self._draw_control.remove_geometry(geometry1)
352
self.assertEqual(len(self._draw_control.geometries), 0)
353
self.assertEqual(len(self._draw_control.properties), 0)
354
self.assertEqual(
355
self._draw_control.last_draw_action, core.DrawActions.REMOVED_LAST
356
)
357
self.assertEqual(self._draw_control.last_geometry, geometry1)
358
359
# When there are two geometries and the removed geometry is the first
360
# one, then treat it like a normal delete.
361
self._draw_control.create(self.geo_json)
362
self._draw_control.create(self.geo_json2)
363
geometry1 = self._draw_control.geometries[0]
364
geometry2 = self._draw_control.geometries[1]
365
self._draw_control.remove_geometry(geometry1)
366
self.assertEqual(len(self._draw_control.geometries), 1)
367
self.assertEqual(len(self._draw_control.properties), 1)
368
self.assertEqual(self._draw_control.last_draw_action, core.DrawActions.DELETED)
369
self.assertEqual(self._draw_control.last_geometry, geometry1)
370
371
class TestDrawControl(core.AbstractDrawControl):
372
"""Implements an AbstractDrawControl for tests."""
373
374
geo_jsons = []
375
initialized = False
376
377
def __init__(self, host_map, **kwargs):
378
"""Initialize the test draw control.
379
380
Args:
381
host_map (geemap.Map): The geemap.Map object
382
"""
383
super().__init__(host_map=host_map, **kwargs)
384
self.geo_jsons = []
385
386
def _get_synced_geojson_from_draw_control(self):
387
return [data.copy() for data in self.geo_jsons]
388
389
def _bind_to_draw_control(self):
390
# In a non-test environment, `_on_draw` would be used here.
391
self.initialized = True
392
393
def _remove_geometry_at_index_on_draw_control(self, index):
394
geo_json = self.geo_jsons[index]
395
del self.geo_jsons[index]
396
self._on_draw("deleted", geo_json)
397
398
def _clear_draw_control(self):
399
self.geo_jsons = []
400
401
def _on_draw(self, action, geo_json):
402
"""Mimics the ipyleaflet DrawControl handler."""
403
if action == "created":
404
self._handle_geometry_created(geo_json)
405
elif action == "edited":
406
self._handle_geometry_edited(geo_json)
407
elif action == "deleted":
408
self._handle_geometry_deleted(geo_json)
409
410
def create(self, geo_json):
411
self.geo_jsons.append(geo_json)
412
self._on_draw("created", geo_json)
413
414
def edit(self, i, geo_json):
415
self.geo_jsons[i] = geo_json
416
self._on_draw("edited", geo_json)
417
418
def delete(self, i):
419
geo_json = self.geo_jsons[i]
420
del self.geo_jsons[i]
421
self._on_draw("deleted", geo_json)
422
423
424
if __name__ == "__main__":
425
unittest.main()
426
427