Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
giswqs
GitHub Repository: giswqs/geemap
Path: blob/master/tests/test_common.py
2313 views
1
"""Tests for the common module."""
2
3
import base64
4
import builtins
5
import io
6
import math
7
import os
8
import pathlib
9
import shutil
10
import sys
11
import tempfile
12
import unittest
13
from unittest import mock
14
import zipfile
15
16
import ee
17
import ipywidgets
18
from PIL import Image
19
import psutil
20
import requests
21
22
from geemap import colormaps
23
from geemap import common
24
from tests import fake_ee
25
26
27
class CommonTest(unittest.TestCase):
28
29
def _create_zip_with_tif(self, tif_name: str, content: bytes) -> bytes:
30
zip_buffer = io.BytesIO()
31
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
32
zip_file.writestr(tif_name, content)
33
return zip_buffer.getvalue()
34
35
@mock.patch.object(requests, "get")
36
def test_ee_export_image_unzip(self, mock_get):
37
"""Tests ee_export_image with unzip=True."""
38
mock_response = mock.Mock()
39
mock_response.status_code = 200
40
zip_content = self._create_zip_with_tif("test.tif", b"tif content")
41
mock_response.iter_content.return_value = [zip_content]
42
mock_get.return_value = mock_response
43
44
image_mock = mock.MagicMock(spec=ee.Image)
45
image_mock.getDownloadURL.return_value = "http://example.com/image.zip"
46
image_mock.geometry.return_value = fake_ee.Geometry()
47
48
with tempfile.TemporaryDirectory() as tmpdir:
49
filename = str(pathlib.Path(tmpdir) / "test.tif")
50
common.ee_export_image(image_mock, filename, unzip=True, verbose=False)
51
52
image_mock.getDownloadURL.assert_called_once()
53
mock_get.assert_called_once_with(
54
"http://example.com/image.zip", stream=True, timeout=300, proxies=None
55
)
56
filename_path = pathlib.Path(filename)
57
self.assertTrue(filename_path.exists())
58
with open(filename_path, "rb") as f:
59
self.assertEqual(f.read(), b"tif content")
60
filename_zip_path = pathlib.Path(tmpdir) / "test.zip"
61
self.assertFalse(filename_zip_path.exists())
62
63
@mock.patch.object(requests, "get")
64
def test_ee_export_image_no_unzip(self, mock_get):
65
"""Tests ee_export_image with unzip=False."""
66
mock_response = mock.Mock()
67
mock_response.status_code = 200
68
zip_content = self._create_zip_with_tif("test.tif", b"tif content")
69
mock_response.iter_content.return_value = [zip_content]
70
mock_get.return_value = mock_response
71
72
image_mock = mock.MagicMock(spec=ee.Image)
73
image_mock.getDownloadURL.return_value = "http://example.com/image.zip"
74
image_mock.geometry.return_value = fake_ee.Geometry()
75
76
with tempfile.TemporaryDirectory() as tmpdir:
77
filename = str(pathlib.Path(tmpdir) / "test.tif")
78
common.ee_export_image(image_mock, filename, unzip=False, verbose=False)
79
80
image_mock.getDownloadURL.assert_called_once()
81
mock_get.assert_called_once_with(
82
"http://example.com/image.zip", stream=True, timeout=300, proxies=None
83
)
84
filename_path = pathlib.Path(filename)
85
filename_zip_path = pathlib.Path(tmpdir) / "test.zip"
86
self.assertTrue(filename_zip_path.exists())
87
with open(filename_zip_path, "rb") as f:
88
self.assertEqual(f.read(), zip_content)
89
self.assertFalse(filename_path.exists())
90
91
# TODO: test_ee_export_image_collection
92
# TODO: test_ee_export_image_to_drive
93
# TODO: test_ee_export_image_to_asset
94
# TODO: test_ee_export_image_to_cloud_storage
95
# TODO: test_ee_export_image_collection_to_drive
96
# TODO: test_ee_export_image_collection_to_asset
97
# TODO: test_ee_export_image_collection_to_cloud_storage
98
@mock.patch.object(requests, "get")
99
def test_ee_export_geojson(self, mock_get):
100
"""Tests ee_export_geojson."""
101
# Setup mock response
102
mock_response = mock.Mock()
103
mock_response.status_code = 200
104
mock_response.iter_content.return_value = [b"geojson content"]
105
mock_get.return_value = mock_response
106
107
# Mock feature collection
108
collection_mock = mock.MagicMock(spec=ee.FeatureCollection)
109
collection_mock.first().propertyNames().getInfo.return_value = [
110
"prop1",
111
"prop2",
112
]
113
collection_mock.getDownloadURL.return_value = "http://example.com/data.geojson"
114
115
with tempfile.TemporaryDirectory() as tmpdir:
116
filename = str(pathlib.Path(tmpdir) / "test.geojson")
117
118
# Valid export
119
result = common.ee_export_geojson(collection_mock, filename)
120
self.assertEqual(result, "geojson content")
121
mock_get.assert_called_with(
122
"http://example.com/data.geojson",
123
stream=True,
124
timeout=300,
125
proxies=None,
126
)
127
128
# Valid export with explicit selectors
129
result = common.ee_export_geojson(
130
collection_mock, filename, selectors=["prop1"]
131
)
132
self.assertEqual(result, "geojson content")
133
collection_mock.getDownloadURL.assert_called_with(
134
filetype="geojson", selectors=["prop1"], filename="test"
135
)
136
137
# Invalid object
138
self.assertIsNone(common.ee_export_geojson("not a collection", filename))
139
140
# Invalid extension
141
invalid_filename = str(pathlib.Path(tmpdir) / "test.shp")
142
self.assertIsNone(
143
common.ee_export_geojson(collection_mock, invalid_filename)
144
)
145
146
# Invalid selectors type
147
self.assertIsNone(
148
common.ee_export_geojson(collection_mock, filename, selectors="invalid")
149
)
150
151
# Invalid selector attributes
152
self.assertIsNone(
153
common.ee_export_geojson(
154
collection_mock, filename, selectors=["invalid_prop"]
155
)
156
)
157
158
# Handle status code != 200
159
mock_response_fail = mock.Mock()
160
mock_response_fail.status_code = 404
161
mock_response_fail.json.return_value = {"error": {"message": "Not Found"}}
162
mock_get.return_value = mock_response_fail
163
164
# This should retry with filter_polygons and fail gracefully
165
collection_mock.map.return_value.getDownloadURL.return_value = (
166
"http://example.com/data2.geojson"
167
)
168
self.assertIsNone(common.ee_export_geojson(collection_mock, filename))
169
170
@mock.patch.object(zipfile, "ZipFile")
171
@mock.patch.object(requests, "get")
172
def test_ee_export_vector(self, mock_get, mock_zip):
173
"""Tests ee_export_vector."""
174
# Setup mock response
175
mock_response = mock.Mock()
176
mock_response.status_code = 200
177
mock_response.iter_content.return_value = [b"vector content"]
178
mock_get.return_value = mock_response
179
180
# Mock feature collection
181
collection_mock = mock.MagicMock(spec=ee.FeatureCollection)
182
collection_mock.first().propertyNames().getInfo.return_value = [
183
"prop1",
184
"prop2",
185
]
186
collection_mock.getDownloadURL.return_value = "http://example.com/data.shp"
187
188
# Test valid extensions
189
with tempfile.TemporaryDirectory() as tmpdir:
190
# SHP format
191
filename = str(pathlib.Path(tmpdir) / "test.shp")
192
common.ee_export_vector(collection_mock, filename)
193
mock_get.assert_called_with(
194
"http://example.com/data.shp", stream=True, timeout=300, proxies=None
195
)
196
self.assertTrue(mock_zip.called)
197
198
# CSV format
199
collection_mock.select.return_value = collection_mock
200
filename_csv = str(pathlib.Path(tmpdir) / "test.csv")
201
common.ee_export_vector(collection_mock, filename_csv)
202
collection_mock.select.assert_called_with([".*"], None, False)
203
204
# GeoJSON format
205
filename_geojson = str(pathlib.Path(tmpdir) / "test.geojson")
206
common.ee_export_vector(collection_mock, filename_geojson)
207
208
# Invalid object type
209
with self.assertRaisesRegex(
210
ValueError, "ee_object must be an ee.FeatureCollection"
211
):
212
common.ee_export_vector("not a collection", filename)
213
214
# Invalid format
215
filename_invalid = str(pathlib.Path(tmpdir) / "test.invalid")
216
with self.assertRaisesRegex(
217
ValueError, "The file type must be one of the following:"
218
):
219
common.ee_export_vector(collection_mock, filename_invalid)
220
221
# Invalid selectors type
222
with self.assertRaisesRegex(ValueError, "selectors must be a list"):
223
common.ee_export_vector(collection_mock, filename, selectors="invalid")
224
225
# Invalid selector attributes
226
with self.assertRaisesRegex(
227
ValueError, "Attributes must be one chosen from:"
228
):
229
common.ee_export_vector(
230
collection_mock, filename, selectors=["invalid_prop"]
231
)
232
233
# Request fails
234
mock_response.status_code = 404
235
mock_response.json.return_value = {"error": {"message": "Not Found"}}
236
collection_mock.map.return_value.getDownloadURL.return_value = (
237
"http://example.com/data2.shp"
238
)
239
with mock.patch("sys.stdout", new_callable=io.StringIO):
240
common.ee_export_vector(collection_mock, filename)
241
242
@mock.patch.object(ee.batch.Export.table, "toDrive")
243
def test_ee_export_vector_to_drive(self, mock_to_drive):
244
"""Tests ee_export_vector_to_drive."""
245
mock_task = mock.MagicMock()
246
mock_to_drive.return_value = mock_task
247
collection_mock = mock.MagicMock(spec=ee.FeatureCollection)
248
249
common.ee_export_vector_to_drive(
250
collection_mock,
251
description="test_task",
252
folder="test_folder",
253
fileFormat="CSV",
254
)
255
256
mock_to_drive.assert_called_once_with(
257
collection_mock,
258
"test_task",
259
"test_folder",
260
None,
261
"CSV",
262
None,
263
None,
264
)
265
mock_task.start.assert_called_once()
266
267
with self.assertRaisesRegex(
268
ValueError, "The collection must be an ee.FeatureCollection"
269
):
270
common.ee_export_vector_to_drive("not a collection", fileFormat="CSV")
271
272
with self.assertRaisesRegex(ValueError, "The file type must be one"):
273
common.ee_export_vector_to_drive(collection_mock, fileFormat="INVALID")
274
275
@mock.patch.object(ee.batch.Export.table, "toAsset")
276
@mock.patch.object(common, "ee_user_id")
277
def test_ee_export_vector_to_asset(self, mock_ee_user_id, mock_to_asset):
278
"""Tests ee_export_vector_to_asset."""
279
mock_ee_user_id.return_value = "projects/test-project"
280
mock_task = mock.MagicMock()
281
mock_to_asset.return_value = mock_task
282
collection_mock = mock.MagicMock(spec=ee.FeatureCollection)
283
284
common.ee_export_vector_to_asset(
285
collection_mock,
286
description="test_task",
287
assetId="my_asset",
288
)
289
290
mock_to_asset.assert_called_once_with(
291
collection_mock,
292
"test_task",
293
"projects/test-project/my_asset",
294
None,
295
)
296
mock_task.start.assert_called_once()
297
298
with self.assertRaisesRegex(
299
ValueError, "The collection must be an ee.FeatureCollection"
300
):
301
common.ee_export_vector_to_asset("not a collection")
302
303
@mock.patch.object(ee.batch.Export.table, "toCloudStorage")
304
def test_ee_export_vector_to_cloud_storage(self, mock_to_cloud_storage):
305
"""Tests ee_export_vector_to_cloud_storage."""
306
mock_task = mock.MagicMock()
307
mock_to_cloud_storage.return_value = mock_task
308
collection_mock = mock.MagicMock(spec=ee.FeatureCollection)
309
310
common.ee_export_vector_to_cloud_storage(
311
collection_mock,
312
description="test_task",
313
bucket="test_bucket",
314
fileFormat="CSV",
315
)
316
317
mock_to_cloud_storage.assert_called_once_with(
318
collection_mock,
319
"test_task",
320
"test_bucket",
321
None,
322
"CSV",
323
None,
324
None,
325
)
326
mock_task.start.assert_called_once()
327
328
with self.assertRaisesRegex(
329
ValueError, "The collection must be an ee.FeatureCollection"
330
):
331
common.ee_export_vector_to_cloud_storage(
332
"not a collection", fileFormat="CSV"
333
)
334
335
with self.assertRaisesRegex(ValueError, "The file type must be one"):
336
common.ee_export_vector_to_cloud_storage(
337
collection_mock, fileFormat="INVALID"
338
)
339
340
@mock.patch.object(ee.batch.Export.table, "toFeatureView")
341
def test_ee_export_vector_to_feature_view(self, mock_to_feature_view):
342
"""Tests ee_export_vector_to_feature_view."""
343
mock_task = mock.MagicMock()
344
mock_to_feature_view.return_value = mock_task
345
collection_mock = mock.MagicMock(spec=ee.FeatureCollection)
346
347
common.ee_export_vector_to_feature_view(
348
collection_mock,
349
description="test_task",
350
assetId="test_asset",
351
)
352
353
mock_to_feature_view.assert_called_once_with(
354
collection_mock,
355
"test_task",
356
"test_asset",
357
None,
358
)
359
mock_task.start.assert_called_once()
360
361
with self.assertRaisesRegex(
362
ValueError, "The collection must be an ee.FeatureCollection"
363
):
364
common.ee_export_vector_to_feature_view("not a collection")
365
366
@mock.patch.object(ee.batch.Export.video, "toDrive")
367
def test_ee_export_video_to_drive(self, mock_to_drive):
368
"""Tests ee_export_video_to_drive."""
369
mock_task = mock.MagicMock()
370
mock_to_drive.return_value = mock_task
371
collection_mock = mock.MagicMock(spec=ee.ImageCollection)
372
373
common.ee_export_video_to_drive(
374
collection_mock,
375
description="test_task",
376
folder="test_folder",
377
framesPerSecond=30,
378
)
379
380
mock_to_drive.assert_called_once_with(
381
collection_mock,
382
"test_task",
383
"test_folder",
384
None,
385
30,
386
None,
387
None,
388
None,
389
None,
390
None,
391
None,
392
None,
393
)
394
mock_task.start.assert_called_once()
395
396
with self.assertRaisesRegex(
397
TypeError, "collection must be an ee.ImageCollection"
398
):
399
common.ee_export_video_to_drive("not a collection")
400
401
@mock.patch.object(ee.batch.Export.video, "toCloudStorage")
402
def test_ee_export_video_to_cloud_storage(self, mock_to_cloud_storage):
403
"""Tests ee_export_video_to_cloud_storage."""
404
mock_task = mock.MagicMock()
405
mock_to_cloud_storage.return_value = mock_task
406
collection_mock = mock.MagicMock(spec=ee.ImageCollection)
407
408
common.ee_export_video_to_cloud_storage(
409
collection_mock,
410
description="test_task",
411
bucket="test_bucket",
412
framesPerSecond=30,
413
)
414
415
mock_to_cloud_storage.assert_called_once_with(
416
collection_mock,
417
"test_task",
418
"test_bucket",
419
None,
420
30,
421
None,
422
None,
423
None,
424
None,
425
None,
426
None,
427
None,
428
)
429
mock_task.start.assert_called_once()
430
431
with self.assertRaisesRegex(
432
TypeError, "collection must be an ee.ImageCollection"
433
):
434
common.ee_export_video_to_cloud_storage("not a collection")
435
436
@mock.patch.object(ee.batch.Export.map, "toCloudStorage")
437
def test_ee_export_map_to_cloud_storage(self, mock_to_cloud_storage):
438
"""Tests ee_export_map_to_cloud_storage."""
439
mock_task = mock.MagicMock()
440
mock_to_cloud_storage.return_value = mock_task
441
image_mock = mock.MagicMock(spec=ee.Image)
442
443
common.ee_export_map_to_cloud_storage(
444
image_mock,
445
description="test_task",
446
bucket="test_bucket",
447
fileFormat="png",
448
)
449
450
mock_to_cloud_storage.assert_called_once_with(
451
image_mock,
452
"test_task",
453
"test_bucket",
454
"png",
455
None,
456
None,
457
None,
458
None,
459
None,
460
None,
461
None,
462
None,
463
)
464
mock_task.start.assert_called_once()
465
466
with self.assertRaisesRegex(TypeError, "image must be an ee.Image"):
467
common.ee_export_map_to_cloud_storage("not an image")
468
469
# TODO: test_TitilerEndpoint
470
# TODO: test_PlanetaryComputerEndpoint
471
472
def test_check_titiler_endpoint(self):
473
"""Tests check_titiler_endpoint."""
474
self.assertEqual(
475
common.check_titiler_endpoint(None),
476
"https://giswqs-titiler-endpoint.hf.space",
477
)
478
self.assertEqual(common.check_titiler_endpoint("some_url"), "some_url")
479
self.assertIsInstance(
480
common.check_titiler_endpoint("pc"),
481
common.PlanetaryComputerEndpoint,
482
)
483
self.assertIsInstance(
484
common.check_titiler_endpoint("planetary-computer"),
485
common.PlanetaryComputerEndpoint,
486
)
487
with mock.patch.dict(os.environ, {"TITILER_ENDPOINT": "planetary-computer"}):
488
self.assertIsInstance(
489
common.check_titiler_endpoint(None),
490
common.PlanetaryComputerEndpoint,
491
)
492
with mock.patch.dict(os.environ, {"TITILER_ENDPOINT": "some_other_url"}):
493
self.assertEqual(common.check_titiler_endpoint(None), "some_other_url")
494
495
@mock.patch.object(requests, "get")
496
def test_set_proxy(self, mock_get):
497
"""Tests set_proxy."""
498
mock_response = mock.Mock()
499
mock_response.status_code = 200
500
mock_get.return_value = mock_response
501
502
common.set_proxy(port=8080, ip="192.168.1.1")
503
self.assertEqual(os.environ["HTTP_PROXY"], "http://192.168.1.1:8080")
504
self.assertEqual(os.environ["HTTPS_PROXY"], "http://192.168.1.1:8080")
505
mock_get.assert_called_with("https://earthengine.google.com/", timeout=300)
506
507
# Test ip without http prefix.
508
common.set_proxy(port=8080, ip="192.168.1.2")
509
self.assertEqual(os.environ["HTTP_PROXY"], "http://192.168.1.2:8080")
510
self.assertEqual(os.environ["HTTPS_PROXY"], "http://192.168.1.2:8080")
511
512
# Test default values.
513
common.set_proxy()
514
self.assertEqual(os.environ["HTTP_PROXY"], "http://127.0.0.1:1080")
515
self.assertEqual(os.environ["HTTPS_PROXY"], "http://127.0.0.1:1080")
516
517
# Test connection failure.
518
mock_response.status_code = 404
519
mock_get.return_value = mock_response
520
with mock.patch("sys.stdout", new_callable=io.StringIO) as mock_stdout:
521
common.set_proxy()
522
self.assertIn("Failed to connect", mock_stdout.getvalue())
523
524
del os.environ["HTTP_PROXY"]
525
del os.environ["HTTPS_PROXY"]
526
527
@mock.patch.object(os.path, "exists")
528
def test_is_drive_mounted(self, mock_exists):
529
"""Tests is_drive_mounted."""
530
mock_exists.return_value = True
531
self.assertTrue(common.is_drive_mounted())
532
mock_exists.assert_called_with("/content/drive/My Drive")
533
534
mock_exists.return_value = False
535
self.assertFalse(common.is_drive_mounted())
536
mock_exists.assert_called_with("/content/drive/My Drive")
537
538
@mock.patch.object(os.path, "exists")
539
def test_credentials_in_drive(self, mock_exists):
540
"""Tests credentials_in_drive."""
541
mock_exists.return_value = True
542
self.assertTrue(common.credentials_in_drive())
543
mock_exists.assert_called_with(
544
"/content/drive/My Drive/.config/earthengine/credentials"
545
)
546
547
mock_exists.return_value = False
548
self.assertFalse(common.credentials_in_drive())
549
mock_exists.assert_called_with(
550
"/content/drive/My Drive/.config/earthengine/credentials"
551
)
552
553
@mock.patch.object(os.path, "exists")
554
def test_credentials_in_colab(self, mock_exists):
555
"""Tests credentials_in_colab."""
556
mock_exists.return_value = True
557
self.assertTrue(common.credentials_in_colab())
558
mock_exists.assert_called_with("/root/.config/earthengine/credentials")
559
560
mock_exists.return_value = False
561
self.assertFalse(common.credentials_in_colab())
562
mock_exists.assert_called_with("/root/.config/earthengine/credentials")
563
564
@mock.patch.object(shutil, "copyfile")
565
@mock.patch.object(os, "makedirs")
566
@mock.patch.object(os.path, "exists")
567
def test_copy_credentials_to_drive_exists(
568
self, mock_exists, mock_makedirs, mock_copyfile
569
):
570
"""Tests copy_credentials_to_drive."""
571
src = "/root/.config/earthengine/credentials"
572
dst = "/content/drive/My Drive/.config/earthengine/credentials"
573
dst_dir = "/content/drive/My Drive/.config/earthengine"
574
575
# Case 1: Destination directory exists.
576
mock_exists.return_value = True
577
common.copy_credentials_to_drive()
578
mock_exists.assert_called_with(dst_dir)
579
mock_makedirs.assert_not_called()
580
mock_copyfile.assert_called_with(src, dst)
581
582
@mock.patch.object(shutil, "copyfile")
583
@mock.patch.object(os, "makedirs")
584
@mock.patch.object(os.path, "exists")
585
def test_copy_credentials_to_drive_does_not_exist(
586
self, mock_exists, mock_makedirs, mock_copyfile
587
):
588
"""Tests copy_credentials_to_drive."""
589
src = "/root/.config/earthengine/credentials"
590
dst = "/content/drive/My Drive/.config/earthengine/credentials"
591
dst_dir = "/content/drive/My Drive/.config/earthengine"
592
593
# Case 2: Destination directory does not exist.
594
mock_exists.return_value = False
595
common.copy_credentials_to_drive()
596
mock_exists.assert_called_with(dst_dir)
597
mock_makedirs.assert_called_once_with(dst_dir)
598
mock_copyfile.assert_called_with(src, dst)
599
600
@mock.patch.object(shutil, "copyfile")
601
@mock.patch.object(os, "makedirs")
602
@mock.patch.object(os.path, "exists")
603
def test_copy_credentials_to_colab_exists(
604
self, mock_exists, mock_makedirs, mock_copyfile
605
):
606
"""Tests copy_credentials_to_colab."""
607
src = "/content/drive/My Drive/.config/earthengine/credentials"
608
dst = "/root/.config/earthengine/credentials"
609
dst_dir = "/root/.config/earthengine"
610
611
# Case 1: Destination directory exists.
612
mock_exists.return_value = True
613
common.copy_credentials_to_colab()
614
mock_exists.assert_called_with(dst_dir)
615
mock_makedirs.assert_not_called()
616
mock_copyfile.assert_called_with(src, dst)
617
618
@mock.patch.object(shutil, "copyfile")
619
@mock.patch.object(os, "makedirs")
620
@mock.patch.object(os.path, "exists")
621
def test_copy_credentials_to_colab_does_not_exist(
622
self, mock_exists, mock_makedirs, mock_copyfile
623
):
624
"""Tests copy_credentials_to_colab."""
625
src = "/content/drive/My Drive/.config/earthengine/credentials"
626
dst = "/root/.config/earthengine/credentials"
627
dst_dir = "/root/.config/earthengine"
628
629
# Case 2: Destination directory does not exist.
630
mock_exists.return_value = False
631
common.copy_credentials_to_colab()
632
mock_exists.assert_called_with(dst_dir)
633
mock_makedirs.assert_called_once_with(dst_dir)
634
mock_copyfile.assert_called_with(src, dst)
635
636
# TODO: test_check_install
637
# TODO: test_update_package
638
# TODO: test_check_package
639
# TODO: test_install_package
640
# TODO: test_clone_repo
641
# TODO: test_install_from_github
642
# TODO: test_check_git_install
643
# TODO: test_clone_github_repo
644
# TODO: test_clone_google_repo
645
# TODO: test_open_github
646
# TODO: test_open_youtube
647
# TODO: test_is_tool
648
# TODO: test_open_image_from_url
649
# TODO: test_show_image
650
# TODO: test_show_html
651
# TODO: test_has_transparency
652
# TODO: test_upload_to_imgur
653
# TODO: test_system_fonts
654
# TODO: test_download_from_url
655
# TODO: test_download_from_gdrive
656
# TODO: test_create_download_link
657
# TODO: test_edit_download_html
658
# TODO: test_xy_to_points
659
# TODO: test_csv_points_to_shp
660
# TODO: test_csv_to_shp
661
# TODO: test_csv_to_geojson
662
# TODO: test_df_to_geojson
663
# TODO: test_csv_to_ee
664
# TODO: test_csv_to_gdf
665
# TODO: test_csv_to_vector
666
# TODO: test_ee_to_geojson
667
# TODO: test_ee_to_bbox
668
# TODO: test_shp_to_geojson
669
# TODO: test_shp_to_ee
670
# TODO: test_filter_polygons
671
# TODO: test_ee_to_shp
672
# TODO: test_ee_to_csv
673
# TODO: test_dict_to_csv
674
# TODO: test_get_image_thumbnail
675
# TODO: test_get_image_collection_thumbnails
676
# TODO: test_netcdf_to_ee
677
# TODO: test_numpy_to_ee
678
# TODO: test_ee_to_numpy
679
# TODO: test_ee_to_xarray
680
# TODO: test_download_ee_video
681
# TODO: test_screen_capture
682
# TODO: test_api_docs
683
# TODO: test_show_youtube
684
# TODO: test_create_colorbar
685
# TODO: test_save_colorbar
686
# TODO: test_minimum_bounding_box
687
# TODO: test_geocode
688
# TODO: test_is_latlon_valid
689
# TODO: test_latlon_from_text
690
# TODO: test_search_ee_data
691
# TODO: test_ee_data_thumbnail
692
# TODO: test_ee_data_html
693
# TODO: test_ee_api_to_csv
694
# TODO: test_read_api_csv
695
# TODO: test_ee_function_tree
696
# TODO: test_build_api_tree
697
# TODO: test_search_api_tree
698
# TODO: test_ee_search
699
# TODO: test_ee_user_id
700
# TODO: test_build_asset_tree
701
# TODO: test_build_repo_tree
702
# TODO: test_file_browser
703
# TODO: test_date_sequence
704
# TODO: test_legend_from_ee
705
# TODO: test_vis_to_qml
706
# TODO: test_create_nlcd_qml
707
# TODO: test_load_GeoTIFF
708
# TODO: test_load_GeoTIFFs
709
# TODO: test_cog_tile
710
# TODO: test_cog_mosaic
711
# TODO: test_cog_mosaic_from_file
712
# TODO: test_cog_bounds
713
# TODO: test_cog_center
714
# TODO: test_cog_bands
715
# TODO: test_cog_stats
716
# TODO: test_cog_info
717
# TODO: test_cog_pixel_value
718
# TODO: test_stac_tile
719
# TODO: test_stac_bounds
720
# TODO: test_stac_center
721
# TODO: test_stac_bands
722
# TODO: test_stac_stats
723
# TODO: test_stac_info
724
# TODO: test_stac_info_geojson
725
# TODO: test_stac_assets
726
# TODO: test_stac_pixel_value
727
# TODO: test_local_tile_pixel_value
728
# TODO: test_local_tile_vmin_vmax
729
# TODO: test_local_tile_bands
730
def test_bbox_to_geojson(self):
731
"""Tests bbox_to_geojson."""
732
bounds = [-10, -20, 10, 20]
733
expected_geojson = {
734
"geometry": {
735
"type": "Polygon",
736
"coordinates": [
737
[
738
[-10, 20],
739
[-10, -20],
740
[10, -20],
741
[10, 20],
742
[-10, 20],
743
]
744
],
745
},
746
"type": "Feature",
747
}
748
self.assertEqual(common.bbox_to_geojson(bounds), expected_geojson)
749
self.assertEqual(common.bbox_to_geojson(tuple(bounds)), expected_geojson)
750
751
def test_coords_to_geojson(self):
752
"""Tests coords_to_geojson."""
753
coords = [[-10, -20, 10, 20], [-100, -80, 100, 80]]
754
expected_geojson = {
755
"type": "FeatureCollection",
756
"features": [
757
{
758
"geometry": {
759
"type": "Polygon",
760
"coordinates": [
761
[
762
[-10, 20],
763
[-10, -20],
764
[10, -20],
765
[10, 20],
766
[-10, 20],
767
]
768
],
769
},
770
"type": "Feature",
771
},
772
{
773
"geometry": {
774
"type": "Polygon",
775
"coordinates": [
776
[
777
[-100, 80],
778
[-100, -80],
779
[100, -80],
780
[100, 80],
781
[-100, 80],
782
]
783
],
784
},
785
"type": "Feature",
786
},
787
],
788
}
789
self.assertEqual(common.coords_to_geojson(coords), expected_geojson)
790
791
def test_explode(self):
792
"""Tests explode."""
793
self.assertEqual(list(common.explode([1.0, 2.0])), [[1.0, 2.0]])
794
self.assertEqual(
795
list(common.explode([[1.0, 2.0], [3.0, 4.0]])), [[1.0, 2.0], [3.0, 4.0]]
796
)
797
self.assertEqual(
798
list(common.explode([[[1.0, 2.0], [3.0, 4.0]]])), [[1.0, 2.0], [3.0, 4.0]]
799
)
800
801
# TODO: test_get_bounds
802
# TODO: test_get_center
803
# TODO: test_image_props
804
# TODO: test_image_stats
805
# TODO: test_adjust_longitude
806
# TODO: test_zonal_stats
807
# TODO: test_zonal_stats_by_group
808
# TODO: test_vec_area
809
# TODO: test_vec_area_km2
810
# TODO: test_vec_area_mi2
811
# TODO: test_vec_area_ha
812
# TODO: test_remove_geometry
813
# TODO: test_image_cell_size
814
# TODO: test_image_scale
815
# TODO: test_image_band_names
816
# TODO: test_image_date
817
# TODO: test_image_dates
818
# TODO: test_image_area
819
# TODO: test_image_area_by_group
820
# TODO: test_image_max_value
821
# TODO: test_image_min_value
822
# TODO: test_image_mean_value
823
# TODO: test_image_std_value
824
# TODO: test_image_sum_value
825
# TODO: test_image_value_list
826
# TODO: test_image_histogram
827
# TODO: test_image_stats_by_zone
828
# TODO: test_latitude_grid
829
# TODO: test_longitude_grid
830
# TODO: test_latlon_grid
831
# TODO: test_fishnet
832
# TODO: test_extract_values_to_points
833
# TODO: test_extract_timeseries_to_point
834
# TODO: test_image_reclassify
835
# TODO: test_image_smoothing
836
# TODO: test_rename_bands
837
# TODO: test_bands_to_image_collection
838
# TODO: test_find_landsat_by_path_row
839
# TODO: test_str_to_num
840
# TODO: test_array_sum
841
# TODO: test_array_mean
842
# TODO: test_get_annual_NAIP
843
# TODO: test_get_all_NAIP
844
# TODO: test_annual_NAIP
845
# TODO: test_find_NAIP
846
# TODO: test_filter_NWI
847
# TODO: test_filter_HUC08
848
# TODO: test_filter_HUC10
849
# TODO: test_find_HUC08
850
# TODO: test_find_HUC10
851
# TODO: test_find_NWI
852
# TODO: test_nwi_add_color
853
# TODO: test_nwi_rename
854
# TODO: test_summarize_by_group
855
# TODO: test_summary_stats
856
# TODO: test_column_stats
857
# TODO: test_ee_num_round
858
859
def test_num_round(self):
860
self.assertEqual(common.num_round(1.2345), 1.23)
861
self.assertEqual(common.num_round(1.2345, 3), 1.234)
862
self.assertEqual(common.num_round(-1.2, 3), -1.2)
863
864
# TODO: test_png_to_gif
865
# TODO: test_jpg_to_gif
866
# TODO: test_vector_styling
867
# TODO: test_is_GCS
868
# TODO: test_kml_to_shp
869
# TODO: test_kml_to_geojson
870
# TODO: test_kml_to_ee
871
# TODO: test_kmz_to_ee
872
# TODO: test_csv_to_df
873
# TODO: test_ee_to_df
874
# TODO: test_shp_to_gdf
875
# TODO: test_ee_to_gdf
876
877
def test_delete_shp(self):
878
with tempfile.TemporaryDirectory() as tmpdir:
879
shp_path = os.path.join(tmpdir, "test.shp")
880
extensions = [".shp", ".shx", ".dbf", ".prj"]
881
for ext in extensions:
882
with open(os.path.join(tmpdir, "test" + ext), "w") as f:
883
f.write("test")
884
885
common.delete_shp(shp_path)
886
887
for ext in extensions:
888
self.assertFalse(os.path.exists(os.path.join(tmpdir, "test" + ext)))
889
890
# TODO: test_df_to_ee
891
# TODO: test_gdf_to_ee
892
# TODO: test_vector_to_geojson
893
# TODO: test_vector_to_ee
894
# TODO: test_extract_pixel_values
895
896
def test_list_vars(self):
897
"""Tests list_vars function."""
898
common.list_vars_test_int = 1
899
common.list_vars_test_str = "test"
900
vars_all = common.list_vars()
901
vars_int = common.list_vars(var_type=int)
902
vars_str = common.list_vars(var_type=str)
903
del common.list_vars_test_int
904
del common.list_vars_test_str
905
906
self.assertIn("list_vars_test_int", vars_all)
907
self.assertIn("list_vars_test_str", vars_all)
908
self.assertIn("list_vars_test_int", vars_int)
909
self.assertNotIn("list_vars_test_str", vars_int)
910
self.assertIn("list_vars_test_str", vars_str)
911
self.assertNotIn("list_vars_test_int", vars_str)
912
913
# TODO: test_extract_transect
914
# TODO: test_random_sampling
915
# TODO: test_osm_to_gdf
916
# TODO: test_osm_to_ee
917
# TODO: test_osm_to_geojson
918
# TODO: test_planet_monthly_tropical
919
# TODO: test_planet_biannual_tropical
920
# TODO: test_planet_catalog_tropical
921
# TODO: test_planet_monthly_tiles_tropical
922
# TODO: test_planet_biannual_tiles_tropical
923
# TODO: test_planet_tiles_tropical
924
# TODO: test_planet_monthly
925
# TODO: test_planet_quarterly
926
# TODO: test_planet_catalog
927
# TODO: test_planet_monthly_tiles
928
# TODO: test_planet_quarterly_tiles
929
# TODO: test_planet_tiles
930
# TODO: test_planet_by_quarter
931
# TODO: test_planet_by_month
932
# TODO: test_planet_tile_by_quarter
933
# TODO: test_planet_tile_by_month
934
# TODO: test_get_current_latlon
935
# TODO: test_get_census_dict
936
# TODO: test_search_xyz_services
937
# TODO: test_search_qms
938
# TODO: test_get_wms_layers
939
# TODO: test_read_file_from_url
940
# TODO: test_create_download_button
941
# TODO: test_gdf_to_geojson
942
# TODO: test_get_temp_dir
943
944
def test_create_contours(self):
945
with (
946
mock.patch.object(ee.Kernel, "gaussian") as mock_gaussian,
947
mock.patch.object(ee.List, "sequence") as mock_sequence,
948
mock.patch.object(ee, "ImageCollection") as mock_ic,
949
mock.patch.object(ee.Image, "constant") as mock_constant,
950
):
951
image = mock.MagicMock(spec=ee.Image)
952
region_geom = mock.MagicMock(spec=ee.Geometry)
953
region_fc = mock.MagicMock(spec=ee.FeatureCollection)
954
kernel = mock.MagicMock()
955
constant_img_mock = mock.MagicMock()
956
mock_constant.return_value = constant_img_mock
957
constant_img_mock.toFloat.return_value = mock.MagicMock()
958
mock_gaussian.return_value = kernel
959
960
list_mock = mock.MagicMock()
961
list_mock.map.return_value = "contours"
962
mock_sequence.return_value = list_mock
963
964
mosaic_mock = mock.MagicMock()
965
mosaic_mock.clip.return_value = "clip_geom_result"
966
mosaic_mock.clipToCollection.return_value = "clip_fc_result"
967
mock_ic.return_value.mosaic.return_value = mosaic_mock
968
969
self.assertEqual(common.create_contours(image, 0.0, 1.0, 0.5), mosaic_mock)
970
self.assertEqual(
971
common.create_contours(image, 0.0, 1.0, 0.5, region=region_geom),
972
"clip_geom_result",
973
)
974
self.assertEqual(
975
common.create_contours(image, 0.0, 1.0, 0.5, region=region_fc),
976
"clip_fc_result",
977
)
978
979
with self.assertRaisesRegex(TypeError, r"image must be an ee\.Image"):
980
common.create_contours("not an image", 0, 1, 0.5)
981
982
message = r"region must be an ee\.Geometry or ee\.FeatureCollection"
983
with self.assertRaisesRegex(TypeError, message):
984
common.create_contours(image, 0, 1, 0.5, region="not a geometry")
985
986
# TODO: test_get_local_tile_layer
987
# TODO: test_get_palettable
988
# TODO: test_connect_postgis
989
# TODO: test_read_postgis
990
# TODO: test_postgis_to_ee
991
# TODO: test_points_from_xy
992
# TODO: test_vector_centroids
993
# TODO: test_bbox_to_gdf
994
995
def test_check_dir(self):
996
with tempfile.TemporaryDirectory() as tmpdir:
997
# Test with make_dirs=True.
998
dir_path_1 = os.path.join(tmpdir, "subdir1")
999
abs_path_1 = common.check_dir(dir_path_1, make_dirs=True)
1000
self.assertTrue(os.path.exists(abs_path_1))
1001
self.assertEqual(abs_path_1, os.path.abspath(dir_path_1))
1002
1003
# Test with make_dirs=False and dir does not exist.
1004
dir_path_2 = os.path.join(tmpdir, "subdir2")
1005
with self.assertRaises(FileNotFoundError):
1006
common.check_dir(dir_path_2, make_dirs=False)
1007
1008
# Test with make_dirs=False and dir exists.
1009
os.makedirs(dir_path_2)
1010
abs_path_2 = common.check_dir(dir_path_2, make_dirs=False)
1011
self.assertTrue(os.path.exists(abs_path_2))
1012
self.assertEqual(abs_path_2, os.path.abspath(dir_path_2))
1013
1014
# Test with invalid type.
1015
with self.assertRaises(TypeError):
1016
common.check_dir(123) # pytype: disable=wrong-arg-types
1017
1018
def test_check_file_path(self):
1019
with tempfile.TemporaryDirectory() as tmpdir:
1020
# Test with make_dirs=True.
1021
file_path_1 = os.path.join(tmpdir, "subdir1", "file1.txt")
1022
abs_path_1 = common.check_file_path(file_path_1, make_dirs=True)
1023
self.assertTrue(os.path.exists(os.path.dirname(abs_path_1)))
1024
self.assertEqual(abs_path_1, os.path.abspath(file_path_1))
1025
1026
# Test with make_dirs=False.
1027
file_path_2 = os.path.join(tmpdir, "subdir2", "file2.txt")
1028
abs_path_2 = common.check_file_path(file_path_2, make_dirs=False)
1029
self.assertFalse(os.path.exists(os.path.dirname(abs_path_2)))
1030
self.assertEqual(abs_path_2, os.path.abspath(file_path_2))
1031
1032
# Test with home directory character ~.
1033
file_path_3 = "~/some_dir/file3.txt"
1034
abs_path_3 = common.check_file_path(file_path_3, make_dirs=False)
1035
self.assertEqual(
1036
abs_path_3, os.path.abspath(os.path.expanduser(file_path_3))
1037
)
1038
1039
# Test with invalid type.
1040
with self.assertRaises(TypeError):
1041
common.check_file_path(123) # pytype: disable=wrong-arg-types
1042
1043
# TODO: test_image_to_cog
1044
# TODO: test_cog_validate
1045
# TODO: test_gdf_to_df
1046
# TODO: test_geojson_to_df
1047
# TODO: test_ee_join_table
1048
# TODO: test_gdf_bounds
1049
# TODO: test_gdf_centroid
1050
# TODO: test_gdf_geom_type
1051
# TODO: test_image_to_numpy
1052
# TODO: test_numpy_to_cog
1053
# TODO: test_view_lidar
1054
# TODO: test_read_lidar
1055
# TODO: test_convert_lidar
1056
# TODO: test_write_lidar
1057
# TODO: test_download_folder
1058
# TODO: test_blend
1059
# TODO: test_clip_image
1060
# TODO: test_netcdf_to_tif
1061
# TODO: test_read_netcdf
1062
# TODO: test_netcdf_tile_layer
1063
# TODO: test_classify
1064
# TODO: test_image_count
1065
# TODO: test_dynamic_world
1066
# TODO: test_dynamic_world_s2
1067
# TODO: test_download_ee_image
1068
# TODO: test_download_ee_image_tiles
1069
# TODO: test_download_ee_image_tiles_parallel
1070
# TODO: test_download_ee_image_collection
1071
1072
def test_get_palette_colors(self):
1073
# Test with n_class.
1074
colors = common.get_palette_colors("viridis", n_class=5)
1075
self.assertEqual(len(colors), 5)
1076
self.assertTrue(all(isinstance(c, str) and len(c) == 6 for c in colors))
1077
self.assertEqual(colors, ["440154", "3b528b", "21918c", "5ec962", "fde725"])
1078
1079
# Test with hashtag=True.
1080
colors_hashtag = common.get_palette_colors("viridis", 5, hashtag=True)
1081
self.assertTrue(all(c.startswith("#") for c in colors_hashtag))
1082
self.assertEqual(
1083
colors_hashtag, ["#440154", "#3b528b", "#21918c", "#5ec962", "#fde725"]
1084
)
1085
1086
# Test with no n_class (should default to a reasonable number, e.g., 256 for continuous).
1087
colors_default = common.get_palette_colors("viridis")
1088
self.assertGreater(len(colors_default), 1)
1089
self.assertTrue(all(isinstance(c, str) and len(c) == 6 for c in colors_default))
1090
1091
# Test invalid cmap name.
1092
with self.assertRaises(ValueError):
1093
common.get_palette_colors("invalid_cmap_name")
1094
1095
# TODO: test_plot_raster
1096
# TODO: test_plot_raster_3d
1097
1098
@mock.patch("geemap.common.IFrame")
1099
@mock.patch("geemap.common.display")
1100
def test_display_html(self, mock_display, mock_iframe):
1101
mock_iframe.return_value = "iframe_object"
1102
with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as fp:
1103
html_file = fp.name
1104
try:
1105
common.display_html(html_file, width=800, height=400)
1106
mock_iframe.assert_called_once_with(src=html_file, width=800, height=400)
1107
mock_display.assert_called_once_with(mock_iframe.return_value)
1108
finally:
1109
os.remove(html_file)
1110
1111
with self.assertRaisesRegex(ValueError, "is not a valid file path"):
1112
common.display_html("non_existent_file.html")
1113
1114
# TODO: test_bbox_coords
1115
# TODO: test_requireJS
1116
# TODO: test_setupJS
1117
# TODO: test_change_require
1118
# TODO: test_ee_vector_style
1119
1120
@mock.patch("geemap.common.requests.head")
1121
def test_get_direct_url(self, mock_head):
1122
mock_response = mock.Mock()
1123
mock_response.url = "https://example.com/direct_url"
1124
mock_head.return_value = mock_response
1125
1126
# Test with a URL that redirects.
1127
self.assertEqual(
1128
common.get_direct_url("https://example.com/redirect"),
1129
"https://example.com/direct_url",
1130
)
1131
mock_head.assert_called_with(
1132
"https://example.com/redirect", allow_redirects=True
1133
)
1134
1135
# Test with a direct URL.
1136
self.assertEqual(
1137
common.get_direct_url("https://example.com/direct_url"),
1138
"https://example.com/direct_url",
1139
)
1140
mock_head.assert_called_with(
1141
"https://example.com/direct_url", allow_redirects=True
1142
)
1143
1144
# Test with non-http URL.
1145
with self.assertRaisesRegex(ValueError, "url must start with http."):
1146
common.get_direct_url("ftp://example.com/file")
1147
1148
# Test with non-string URL.
1149
with self.assertRaisesRegex(ValueError, "url must be a string."):
1150
common.get_direct_url(123) # pytype: disable=wrong-arg-types
1151
1152
# TODO: test_add_crs
1153
# TODO: test_jrc_hist_monthly_history
1154
# TODO: test_html_to_streamlit
1155
# TODO: test_image_convolution
1156
# TODO: test_download_ned
1157
# TODO: test_mosaic
1158
# TODO: test_reproject
1159
# TODO: test_download_3dep_lidar
1160
# TODO: test_create_legend
1161
# TODO: test_is_arcpy
1162
# TODO: test_arc_active_map
1163
# TODO: test_arc_active_view
1164
# TODO: test_arc_add_layer
1165
# TODO: test_arc_zoom_to_extent
1166
# TODO: test_get_current_year
1167
1168
def test_html_to_gradio(self):
1169
html_list = [
1170
"<!DOCTYPE html>",
1171
"<html>",
1172
"<body>",
1173
" <script>",
1174
' L.tileLayer("url", {',
1175
' "attribution": "..."',
1176
" }).addTo(map);",
1177
' function(e) { console.log("foo"); }',
1178
' "should be kept";',
1179
" </script>",
1180
"</body>",
1181
"</html>",
1182
]
1183
gradio_html = common.html_to_gradio(html_list, width="800px", height="400px")
1184
self.assertIn('<iframe style="width: 800px; height: 400px"', gradio_html)
1185
self.assertNotIn('function(e) { console.log("foo"); }', gradio_html)
1186
self.assertIn('"should be kept";', gradio_html)
1187
1188
# TODO: test_image_check
1189
# TODO: test_image_client
1190
# TODO: test_image_center
1191
# TODO: test_image_bounds
1192
# TODO: test_image_metadata
1193
# TODO: test_image_bandcount
1194
# TODO: test_image_size
1195
# TODO: test_image_projection
1196
# TODO: test_image_set_crs
1197
# TODO: test_image_geotransform
1198
# TODO: test_image_resolution
1199
1200
def test_find_files(self):
1201
with tempfile.TemporaryDirectory() as tmpdir:
1202
dir_path = pathlib.Path(tmpdir)
1203
f1 = dir_path / "file1.txt"
1204
f2 = dir_path / "file2.csv"
1205
f1.touch()
1206
f2.touch()
1207
subdir = dir_path / "subdir"
1208
subdir.mkdir()
1209
f3 = subdir / "file3.txt"
1210
f4 = subdir / "file4.py"
1211
f3.touch()
1212
f4.touch()
1213
1214
# Test recursive search, full path, no extension.
1215
result = common.find_files(tmpdir, fullpath=True, recursive=True)
1216
expected = [str(f1), str(f2), str(f3), str(f4)]
1217
self.assertCountEqual(result, expected)
1218
1219
# Test recursive search, full path, with extension "txt".
1220
result = common.find_files(
1221
tmpdir, ext=".txt", fullpath=True, recursive=True
1222
)
1223
expected = [str(f1), str(f3)]
1224
self.assertCountEqual(result, expected)
1225
1226
result = common.find_files(tmpdir, ext="txt", fullpath=True, recursive=True)
1227
expected = [str(f1), str(f3)]
1228
self.assertCountEqual(result, expected)
1229
1230
# Test non-recursive search, full path, with extension "txt".
1231
result = common.find_files(
1232
tmpdir, ext="txt", fullpath=True, recursive=False
1233
)
1234
expected = [str(f1)]
1235
self.assertCountEqual(result, expected)
1236
1237
# Test recursive search, no full path, with extension "txt".
1238
result = common.find_files(
1239
tmpdir, ext="txt", fullpath=False, recursive=True
1240
)
1241
expected = ["file1.txt", "file3.txt"]
1242
self.assertCountEqual(result, expected)
1243
1244
# Test non-recursive search, no full path, no extension.
1245
result = common.find_files(tmpdir, fullpath=False, recursive=False)
1246
expected = ["file1.txt", "file2.csv"]
1247
self.assertCountEqual(result, expected)
1248
1249
def test_zoom_level_resolution(self):
1250
self.assertAlmostEqual(
1251
common.zoom_level_resolution(zoom=0, latitude=0), 156543.04, places=4
1252
)
1253
self.assertAlmostEqual(
1254
common.zoom_level_resolution(zoom=10, latitude=0), 152.8740625, places=4
1255
)
1256
self.assertAlmostEqual(
1257
common.zoom_level_resolution(zoom=10, latitude=math.pi / 3),
1258
76.43703125,
1259
places=4,
1260
)
1261
self.assertAlmostEqual(
1262
common.zoom_level_resolution(zoom=10, latitude=-math.pi / 3),
1263
76.43703125,
1264
places=4,
1265
)
1266
1267
def test_lnglat_to_meters(self):
1268
x, y = common.lnglat_to_meters(longitude=0, latitude=0)
1269
self.assertAlmostEqual(x, 0)
1270
self.assertAlmostEqual(y, 0)
1271
1272
x, y = common.lnglat_to_meters(longitude=1, latitude=2)
1273
self.assertAlmostEqual(x, 111319.4908, places=4)
1274
self.assertAlmostEqual(y, 222684.2085, places=4)
1275
1276
x, y = common.lnglat_to_meters(10, 20)
1277
lng, lat = common.meters_to_lnglat(x, y)
1278
self.assertAlmostEqual(lng, 10, places=4)
1279
self.assertAlmostEqual(lat, 20, places=4)
1280
1281
def test_meters_to_lnglat(self):
1282
lng, lat = common.meters_to_lnglat(x=0, y=0)
1283
self.assertAlmostEqual(lng, 0)
1284
self.assertAlmostEqual(lat, 0)
1285
1286
lng, lat = common.meters_to_lnglat(x=111319.4908, y=222684.2085)
1287
self.assertAlmostEqual(lng, 1.0, places=4)
1288
self.assertAlmostEqual(lat, 2.0, places=4)
1289
1290
x, y = common.lnglat_to_meters(10, 20)
1291
lng, lat = common.meters_to_lnglat(x, y)
1292
self.assertAlmostEqual(lng, 10, places=4)
1293
self.assertAlmostEqual(lat, 20, places=4)
1294
1295
# TODO: test_bounds_to_xy_range
1296
1297
def test_center_zoom_to_xy_range(self):
1298
x_range, y_range = common.center_zoom_to_xy_range(center=(0, 0), zoom=2)
1299
self.assertAlmostEqual(x_range[0], -19926188.8520, places=4)
1300
self.assertAlmostEqual(x_range[1], 19926188.8520, places=4)
1301
self.assertAlmostEqual(y_range[0], -11068715.6594, places=4)
1302
self.assertAlmostEqual(y_range[1], 11068715.6594, places=4)
1303
1304
x_range, y_range = common.center_zoom_to_xy_range(center=(0, 0), zoom=3)
1305
self.assertAlmostEqual(x_range[0], -9963094.4260, places=4)
1306
self.assertAlmostEqual(x_range[1], 9963094.4260, places=4)
1307
self.assertAlmostEqual(y_range[0], -4163881.1441, places=4)
1308
self.assertAlmostEqual(y_range[1], 4163881.1441, places=4)
1309
1310
# TODO: test_get_geometry_coords
1311
1312
def test_landsat_scaling(self):
1313
image = mock.MagicMock(spec=ee.Image)
1314
optical_bands_mock = mock.MagicMock(name="optical_bands_mock")
1315
thermal_bands_mock = mock.MagicMock(name="thermal_bands_mock")
1316
qa_pixel_mock = mock.MagicMock(name="qa_pixel_mock")
1317
1318
def select_side_effect(band_selector):
1319
if band_selector == "SR_B.":
1320
return optical_bands_mock
1321
if band_selector == "ST_B.*":
1322
return thermal_bands_mock
1323
if band_selector == "QA_PIXEL":
1324
return qa_pixel_mock
1325
1326
raise ValueError(f"Unexpected band selector: {band_selector}")
1327
1328
image.select.side_effect = select_side_effect
1329
1330
scaled_optical = mock.MagicMock(name="scaled_optical")
1331
optical_bands_mock.multiply.return_value.add.return_value = scaled_optical
1332
1333
scaled_thermal = mock.MagicMock(name="scaled_thermal")
1334
thermal_bands_mock.multiply.return_value.add.return_value = scaled_thermal
1335
1336
qa_mask = mock.MagicMock(name="qa_mask")
1337
qa_pixel_mock.bitwiseAnd.return_value.eq.return_value = qa_mask
1338
1339
# To allow chaining addBands().addBands().updateMask().
1340
image.addBands.return_value = image
1341
1342
# Test case 1: thermal_bands=True, apply_fmask=False.
1343
image.reset_mock()
1344
optical_bands_mock.reset_mock()
1345
thermal_bands_mock.reset_mock()
1346
qa_pixel_mock.reset_mock()
1347
common.landsat_scaling(image, thermal_bands=True, apply_fmask=False)
1348
image.select.assert_has_calls([mock.call("SR_B."), mock.call("ST_B.*")])
1349
self.assertEqual(image.select.call_count, 2)
1350
optical_bands_mock.multiply.assert_called_with(0.0000275)
1351
optical_bands_mock.multiply().add.assert_called_with(-0.2)
1352
thermal_bands_mock.multiply.assert_called_with(0.00341802)
1353
thermal_bands_mock.multiply().add.assert_called_with(149)
1354
image.addBands.assert_has_calls(
1355
[
1356
mock.call(scaled_thermal, None, True),
1357
mock.call(scaled_optical, None, True),
1358
]
1359
)
1360
image.updateMask.assert_not_called()
1361
1362
# Test case 2: thermal_bands=False, apply_fmask=False.
1363
image.reset_mock()
1364
optical_bands_mock.reset_mock()
1365
thermal_bands_mock.reset_mock()
1366
qa_pixel_mock.reset_mock()
1367
common.landsat_scaling(image, thermal_bands=False, apply_fmask=False)
1368
image.select.assert_called_once_with("SR_B.")
1369
optical_bands_mock.multiply.assert_called_with(0.0000275)
1370
optical_bands_mock.multiply().add.assert_called_with(-0.2)
1371
thermal_bands_mock.multiply.assert_not_called()
1372
image.addBands.assert_called_once_with(scaled_optical, None, True)
1373
image.updateMask.assert_not_called()
1374
1375
# Test case 3: thermal_bands=True, apply_fmask=True.
1376
image.reset_mock()
1377
optical_bands_mock.reset_mock()
1378
thermal_bands_mock.reset_mock()
1379
qa_pixel_mock.reset_mock()
1380
common.landsat_scaling(image, thermal_bands=True, apply_fmask=True)
1381
image.select.assert_has_calls(
1382
[mock.call("SR_B."), mock.call("ST_B.*"), mock.call("QA_PIXEL")],
1383
any_order=True,
1384
)
1385
self.assertEqual(image.select.call_count, 3)
1386
optical_bands_mock.multiply.assert_called_with(0.0000275)
1387
optical_bands_mock.multiply().add.assert_called_with(-0.2)
1388
thermal_bands_mock.multiply.assert_called_with(0.00341802)
1389
thermal_bands_mock.multiply().add.assert_called_with(149)
1390
qa_pixel_mock.bitwiseAnd.assert_called_once_with(31)
1391
qa_pixel_mock.bitwiseAnd().eq.assert_called_once_with(0)
1392
image.addBands.assert_has_calls(
1393
[
1394
mock.call(scaled_thermal, None, True),
1395
mock.call(scaled_optical, None, True),
1396
]
1397
)
1398
image.updateMask.assert_called_once_with(qa_mask)
1399
1400
# Test case 4: thermal_bands=False, apply_fmask=True.
1401
image.reset_mock()
1402
optical_bands_mock.reset_mock()
1403
thermal_bands_mock.reset_mock()
1404
qa_pixel_mock.reset_mock()
1405
common.landsat_scaling(image, thermal_bands=False, apply_fmask=True)
1406
image.select.assert_has_calls(
1407
[mock.call("SR_B."), mock.call("QA_PIXEL")], any_order=True
1408
)
1409
self.assertEqual(image.select.call_count, 2)
1410
optical_bands_mock.multiply.assert_called_with(0.0000275)
1411
optical_bands_mock.multiply().add.assert_called_with(-0.2)
1412
thermal_bands_mock.multiply.assert_not_called()
1413
qa_pixel_mock.bitwiseAnd.assert_called_once_with(31)
1414
qa_pixel_mock.bitwiseAnd().eq.assert_called_once_with(0)
1415
image.addBands.assert_called_once_with(scaled_optical, None, True)
1416
image.updateMask.assert_called_once_with(qa_mask)
1417
1418
# TODO: test_tms_to_geotiff
1419
# TODO: test_tif_to_jp2
1420
# TODO: test_ee_to_geotiff
1421
# TODO: test_create_grid
1422
1423
def test_jslink_slider_label(self):
1424
int_slider = ipywidgets.IntSlider(value=5)
1425
int_label = ipywidgets.Label(value="0")
1426
common.jslink_slider_label(int_slider, int_label)
1427
int_slider.value = 10
1428
self.assertEqual(int_label.value, "10")
1429
1430
float_slider = ipywidgets.FloatSlider(value=5.5)
1431
float_label = ipywidgets.Label(value="0.0")
1432
common.jslink_slider_label(float_slider, float_label)
1433
float_slider.value = 10.1
1434
self.assertEqual(float_label.value, "10.1")
1435
1436
def test_check_basemap(self):
1437
self.assertEqual(common.check_basemap("ROADMAP"), "Google Maps")
1438
self.assertEqual(common.check_basemap("SATELLITE"), "Google Satellite")
1439
self.assertEqual(common.check_basemap("TERRAIN"), "Google Terrain")
1440
self.assertEqual(common.check_basemap("HYBRID"), "Google Hybrid")
1441
self.assertEqual(common.check_basemap("roadmap"), "Google Maps")
1442
self.assertEqual(common.check_basemap("satellite"), "Google Satellite")
1443
self.assertEqual(common.check_basemap("terrain"), "Google Terrain")
1444
self.assertEqual(common.check_basemap("hybrid"), "Google Hybrid")
1445
self.assertEqual(common.check_basemap("OpenStreetMap"), "OpenStreetMap")
1446
1447
# pytype: disable=wrong-arg-types
1448
self.assertIsNone(common.check_basemap(None))
1449
self.assertEqual(common.check_basemap(123), 123)
1450
# pytype: enable=wrong-arg-types
1451
1452
@mock.patch.object(os.path, "exists")
1453
@mock.patch.object(
1454
builtins,
1455
"open",
1456
new_callable=mock.mock_open,
1457
read_data='{"access_token": "TOKEN"}',
1458
)
1459
def test_get_ee_token_exists(self, mock_file, mock_exists):
1460
del mock_file # Unused.
1461
mock_exists.return_value = True
1462
token = common.get_ee_token()
1463
self.assertEqual(token, {"access_token": "TOKEN"})
1464
1465
@mock.patch.object(os.path, "exists")
1466
@mock.patch.object(sys, "stdout", new_callable=io.StringIO)
1467
def test_get_ee_token_not_exists(self, mock_stdout, mock_exists):
1468
mock_exists.return_value = False
1469
token = common.get_ee_token()
1470
self.assertIsNone(token)
1471
self.assertIn("credentials not found", mock_stdout.getvalue())
1472
1473
# TODO: test_geotiff_to_image
1474
# TODO: test_xee_to_image
1475
# TODO: test_array_to_memory_file
1476
# TODO: test_array_to_image
1477
1478
@mock.patch.object(psutil, "Process")
1479
def test_is_studio_lab(self, mock_process):
1480
mock_process.return_value.parent.return_value.cmdline.return_value = [
1481
"python",
1482
"/path/to/studiolab/bin/jupyter",
1483
]
1484
self.assertTrue(common.is_studio_lab())
1485
1486
mock_process.return_value.parent.return_value.cmdline.return_value = [
1487
"python",
1488
"/home/user/run.py",
1489
]
1490
self.assertFalse(common.is_studio_lab())
1491
1492
@mock.patch.object(psutil, "Process")
1493
def test_is_on_aws(self, mock_process):
1494
mock_process.return_value.parent.return_value.cmdline.return_value = [
1495
"python",
1496
"/home/ec2-user/run.py",
1497
]
1498
self.assertTrue(common.is_on_aws())
1499
1500
mock_process.return_value.parent.return_value.cmdline.return_value = [
1501
"python",
1502
"script.aws",
1503
]
1504
self.assertTrue(common.is_on_aws())
1505
1506
mock_process.return_value.parent.return_value.cmdline.return_value = [
1507
"python",
1508
"/home/user/run.py",
1509
]
1510
self.assertFalse(common.is_on_aws())
1511
1512
# TODO: test_xarray_to_raster
1513
1514
def test_hex_to_rgba(self):
1515
self.assertEqual(common.hex_to_rgba("#000000", 1.0), "rgba(0,0,0,1.0)")
1516
self.assertEqual(common.hex_to_rgba("000000", 1.0), "rgba(0,0,0,1.0)")
1517
self.assertEqual(common.hex_to_rgba("#FF0000", 0.5), "rgba(255,0,0,0.5)")
1518
self.assertEqual(common.hex_to_rgba("00FF00", 0.0), "rgba(0,255,0,0.0)")
1519
self.assertEqual(common.hex_to_rgba("0000FF", 1.0), "rgba(0,0,255,1.0)")
1520
self.assertEqual(common.hex_to_rgba("#FFFFFF", 0.7), "rgba(255,255,255,0.7)")
1521
1522
def test_replace_top_level_hyphens(self):
1523
self.assertEqual(
1524
common.replace_top_level_hyphens({"a-b": 1, "c": 2}), {"a_b": 1, "c": 2}
1525
)
1526
self.assertEqual(
1527
common.replace_top_level_hyphens({"a-b": {"c-d": 1}}),
1528
{"a_b": {"c-d": 1}},
1529
)
1530
self.assertEqual(
1531
common.replace_top_level_hyphens([{"a-b": 1}, {"c-d": 2}]),
1532
[{"a-b": 1}, {"c-d": 2}],
1533
)
1534
self.assertEqual(common.replace_top_level_hyphens(1), 1)
1535
self.assertEqual(common.replace_top_level_hyphens("test"), "test")
1536
self.assertEqual(common.replace_top_level_hyphens({"a_b": 1}), {"a_b": 1})
1537
1538
def test_replace_hyphens_in_keys(self):
1539
self.assertEqual(
1540
common.replace_hyphens_in_keys({"a-b": 1, "c": 2}), {"a_b": 1, "c": 2}
1541
)
1542
self.assertEqual(
1543
common.replace_hyphens_in_keys({"a-b": {"c-d": 1}}), {"a_b": {"c_d": 1}}
1544
)
1545
self.assertEqual(
1546
common.replace_hyphens_in_keys([{"a-b": 1}, {"c-d": 2}]),
1547
[{"a_b": 1}, {"c_d": 2}],
1548
)
1549
self.assertEqual(common.replace_hyphens_in_keys(1), 1)
1550
self.assertEqual(common.replace_hyphens_in_keys("test"), "test")
1551
self.assertEqual(common.replace_hyphens_in_keys({"a_b": 1}), {"a_b": 1})
1552
1553
def test_remove_port_from_string(self):
1554
self.assertEqual(
1555
common.remove_port_from_string("http://127.0.0.1:8080"),
1556
"http://127.0.0.1",
1557
)
1558
self.assertEqual(
1559
common.remove_port_from_string("http://127.0.0.1"), "http://127.0.0.1"
1560
)
1561
self.assertEqual(
1562
common.remove_port_from_string("https://127.0.0.1:8080"),
1563
"https://127.0.0.1:8080",
1564
)
1565
self.assertEqual(
1566
common.remove_port_from_string("ABCD http://10.0.0.1:1234 EFGH"),
1567
"ABCD http://10.0.0.1 EFGH",
1568
)
1569
self.assertEqual(common.remove_port_from_string("no url"), "no url")
1570
self.assertEqual(
1571
common.remove_port_from_string('{"url": "http://127.0.0.1:8000/tile"}'),
1572
'{"url": "http://127.0.0.1/tile"}',
1573
)
1574
1575
# TODO: test_pmtiles_metadata
1576
1577
@mock.patch.object(common, "pmtiles_metadata")
1578
def test_pmtiles_style(self, mock_pmtiles_metadata):
1579
mock_pmtiles_metadata.return_value = {
1580
"layer_names": ["layer1", "layer2"],
1581
"center": [0, 0, 0],
1582
"bounds": [0, 0, 0, 0],
1583
}
1584
style = common.pmtiles_style("some_url")
1585
self.assertIn("layers", style)
1586
self.assertEqual(6, len(style["layers"]))
1587
1588
@mock.patch.object(common, "pmtiles_metadata")
1589
def test_pmtiles_style_cmap_list(self, mock_pmtiles_metadata):
1590
mock_pmtiles_metadata.return_value = {
1591
"layer_names": ["layer1", "layer2"],
1592
"center": [0, 0, 0],
1593
"bounds": [0, 0, 0, 0],
1594
}
1595
# pytype: disable=wrong-arg-types
1596
style = common.pmtiles_style("some_url", cmap=["#1a9850", "#91cf60"])
1597
# pytype: enable=wrong-arg-types
1598
self.assertEqual(style["layers"][0]["paint"]["circle-color"], "#1a9850")
1599
self.assertEqual(style["layers"][1]["paint"]["line-color"], "#1a9850")
1600
self.assertEqual(style["layers"][2]["paint"]["fill-color"], "#1a9850")
1601
self.assertEqual(style["layers"][3]["paint"]["circle-color"], "#91cf60")
1602
self.assertEqual(style["layers"][4]["paint"]["line-color"], "#91cf60")
1603
self.assertEqual(style["layers"][5]["paint"]["fill-color"], "#91cf60")
1604
1605
@mock.patch.object(common, "pmtiles_metadata")
1606
@mock.patch.object(colormaps, "get_palette")
1607
def test_pmtiles_style_cmap_palette(self, mock_get_palette, mock_pmtiles_metadata):
1608
mock_pmtiles_metadata.return_value = {
1609
"layer_names": ["layer1", "layer2"],
1610
"center": [0, 0, 0],
1611
"bounds": [0, 0, 0, 0],
1612
}
1613
mock_get_palette.return_value = ["440154", "414287"]
1614
style = common.pmtiles_style("some_url", cmap="viridis")
1615
self.assertEqual(style["layers"][0]["paint"]["circle-color"], "#440154")
1616
self.assertEqual(style["layers"][1]["paint"]["line-color"], "#440154")
1617
self.assertEqual(style["layers"][2]["paint"]["fill-color"], "#440154")
1618
self.assertEqual(style["layers"][3]["paint"]["circle-color"], "#414287")
1619
self.assertEqual(style["layers"][4]["paint"]["line-color"], "#414287")
1620
self.assertEqual(style["layers"][5]["paint"]["fill-color"], "#414287")
1621
1622
@mock.patch.object(common, "pmtiles_metadata")
1623
def test_pmtiles_style_layers_str(self, mock_pmtiles_metadata):
1624
mock_pmtiles_metadata.return_value = {
1625
"layer_names": ["layer1", "layer2"],
1626
"center": [0, 0, 0],
1627
"bounds": [0, 0, 0, 0],
1628
}
1629
style = common.pmtiles_style("some_url", layers="layer1")
1630
self.assertEqual(3, len(style["layers"]))
1631
self.assertEqual(style["layers"][0]["id"], "layer1_point")
1632
1633
@mock.patch.object(common, "pmtiles_metadata")
1634
def test_pmtiles_style_layers_list(self, mock_pmtiles_metadata):
1635
mock_pmtiles_metadata.return_value = {
1636
"layer_names": ["layer1", "layer2"],
1637
"center": [0, 0, 0],
1638
"bounds": [0, 0, 0, 0],
1639
}
1640
style = common.pmtiles_style("some_url", layers=["layer2"])
1641
self.assertEqual(3, len(style["layers"]))
1642
self.assertEqual(style["layers"][0]["id"], "layer2_point")
1643
1644
@mock.patch.object(common, "pmtiles_metadata")
1645
def test_pmtiles_style_layers_invalid_list(self, mock_pmtiles_metadata):
1646
mock_pmtiles_metadata.return_value = {
1647
"layer_names": ["layer1", "layer2"],
1648
"center": [0, 0, 0],
1649
"bounds": [0, 0, 0, 0],
1650
}
1651
with self.assertRaises(ValueError):
1652
common.pmtiles_style("some_url", layers=["layer1", "invalid_layer"])
1653
1654
@mock.patch.object(common, "pmtiles_metadata")
1655
def test_pmtiles_style_layers_invalid_type(self, mock_pmtiles_metadata):
1656
mock_pmtiles_metadata.return_value = {
1657
"layer_names": ["layer1", "layer2"],
1658
"center": [0, 0, 0],
1659
"bounds": [0, 0, 0, 0],
1660
}
1661
with self.assertRaises(ValueError):
1662
common.pmtiles_style(
1663
"some_url", layers=123
1664
) # pytype: disable=wrong-arg-types
1665
1666
def test_check_html_string(self):
1667
with tempfile.TemporaryDirectory() as root_dir:
1668
root = pathlib.Path(root_dir)
1669
1670
# Create a dummy image file.
1671
temp_file = root / "test_image.png"
1672
img = Image.new("RGB", (1, 1), color="red")
1673
img.save(temp_file)
1674
1675
img_data = temp_file.read_bytes()
1676
base64_data = base64.b64encode(img_data).decode("utf-8")
1677
expected_base64_src = f"data:image/png;base64,{base64_data}"
1678
1679
html_string = f'<html><body><img src="{temp_file}"></body></html>'
1680
1681
result_html = common.check_html_string(html_string)
1682
1683
self.assertIn(expected_base64_src, result_html)
1684
self.assertNotIn(str(temp_file), result_html)
1685
1686
1687
if __name__ == "__main__":
1688
unittest.main()
1689
1690