geobox 1.3.0__tar.gz → 1.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {geobox-1.3.0 → geobox-1.3.2}/PKG-INFO +1 -1
- {geobox-1.3.0 → geobox-1.3.2}/geobox/mosaic.py +11 -2
- {geobox-1.3.0 → geobox-1.3.2}/geobox/query.py +1 -1
- {geobox-1.3.0 → geobox-1.3.2}/geobox/raster.py +107 -45
- {geobox-1.3.0 → geobox-1.3.2}/geobox/user.py +4 -7
- {geobox-1.3.0 → geobox-1.3.2}/geobox/utils.py +7 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/vectorlayer.py +30 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox.egg-info/PKG-INFO +1 -1
- {geobox-1.3.0 → geobox-1.3.2}/pyproject.toml +1 -1
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_mosaic.py +2 -2
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_raster.py +25 -9
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_user.py +4 -3
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_vectorlayer.py +11 -0
- {geobox-1.3.0 → geobox-1.3.2}/LICENSE +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/README.md +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/__init__.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/api.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/apikey.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/attachment.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/base.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/basemap.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/dashboard.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/enums.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/exception.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/feature.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/field.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/file.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/log.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/map.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/model3d.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/plan.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/route.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/scene.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/settings.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/task.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/tile3d.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/tileset.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/usage.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/version.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/view.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox/workflow.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox.egg-info/SOURCES.txt +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox.egg-info/dependency_links.txt +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox.egg-info/requires.txt +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/geobox.egg-info/top_level.txt +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/setup.cfg +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_api.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_apikey.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_attachment.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_basemap.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_dashboard.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_feature.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_field.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_file.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_log.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_map.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_model3d.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_plan.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_query.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_route.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_scene.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_settings.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_task.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_tile3d.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_tileset.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_usage.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_version.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_view.py +0 -0
- {geobox-1.3.0 → geobox-1.3.2}/tests/test_workflow.py +0 -0
|
@@ -295,7 +295,7 @@ class Mosaic(Raster):
|
|
|
295
295
|
return super().get_point(lat, lng)
|
|
296
296
|
|
|
297
297
|
|
|
298
|
-
def
|
|
298
|
+
def get_render_png_url(self, x: int, y: int, z: int, **kwargs) -> str:
|
|
299
299
|
"""
|
|
300
300
|
Get the tile render URL of the mosaic.
|
|
301
301
|
|
|
@@ -304,6 +304,15 @@ class Mosaic(Raster):
|
|
|
304
304
|
y (int): The y coordinate of the tile.
|
|
305
305
|
z (int): The zoom level of the tile.
|
|
306
306
|
|
|
307
|
+
Keyword Args:
|
|
308
|
+
indexes (str, optional): list of comma separated band indexes to be rendered. e.g. 1, 2, 3
|
|
309
|
+
nodata (int, optional)
|
|
310
|
+
expression (str, optional): band math expression. e.g. b1*b2+b3
|
|
311
|
+
rescale (List, optional): comma (',') separated Min,Max range. Can set multiple time for multiple bands.
|
|
312
|
+
color_formula (str, optional): Color formula. e.g. gamma R 0.5
|
|
313
|
+
colormap_name (str, optional)
|
|
314
|
+
colormap (str, optional): JSON encoded custom Colormap. e.g. {"0": "#ff0000", "1": "#00ff00"} or [[[0, 100], "#ff0000"], [[100, 200], "#00ff00"]]
|
|
315
|
+
|
|
307
316
|
Returns:
|
|
308
317
|
str: The tile render URL of the mosaic.
|
|
309
318
|
|
|
@@ -314,7 +323,7 @@ class Mosaic(Raster):
|
|
|
314
323
|
>>> mosaic = Mosaic.get_mosaic(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
315
324
|
>>> mosaic.get_tile_render_url(x=1, y=1, z=1)
|
|
316
325
|
"""
|
|
317
|
-
return super().
|
|
326
|
+
return super().get_render_png_url(x, y, z, **kwargs)
|
|
318
327
|
|
|
319
328
|
|
|
320
329
|
def get_tile_png_url(self, x: int, y: int, z: int) -> str:
|
|
@@ -582,7 +582,7 @@ class Query(Base):
|
|
|
582
582
|
super()._unshare(self.endpoint, users)
|
|
583
583
|
|
|
584
584
|
|
|
585
|
-
def get_shared_users(self, search: str, skip: int = 0, limit: int = 10) -> List['User']:
|
|
585
|
+
def get_shared_users(self, search: str = None, skip: int = 0, limit: int = 10) -> List['User']:
|
|
586
586
|
"""
|
|
587
587
|
Retrieves the list of users the query is shared with.
|
|
588
588
|
|
|
@@ -3,6 +3,7 @@ from urllib.parse import urljoin, urlencode
|
|
|
3
3
|
from typing import Optional, Dict, List, Optional, Union, TYPE_CHECKING
|
|
4
4
|
import mimetypes
|
|
5
5
|
import requests
|
|
6
|
+
import sys
|
|
6
7
|
|
|
7
8
|
from .base import Base
|
|
8
9
|
from .utils import clean_data
|
|
@@ -179,7 +180,6 @@ class Raster(Base):
|
|
|
179
180
|
name (str): The name of the raster.
|
|
180
181
|
display_name (str): The display name of the raster.
|
|
181
182
|
description (str): The description of the raster.
|
|
182
|
-
max_zoom (int): The max zoom of the raster.
|
|
183
183
|
|
|
184
184
|
Returns:
|
|
185
185
|
None
|
|
@@ -194,10 +194,9 @@ class Raster(Base):
|
|
|
194
194
|
params = {
|
|
195
195
|
'name': kwargs.get('name'),
|
|
196
196
|
'display_name': kwargs.get('display_name'),
|
|
197
|
-
'description': kwargs.get('description')
|
|
198
|
-
'max_zoom': kwargs.get('max_zoom'),
|
|
197
|
+
'description': kwargs.get('description')
|
|
199
198
|
}
|
|
200
|
-
super()._update(self.endpoint, params)
|
|
199
|
+
return super()._update(self.endpoint, params)
|
|
201
200
|
|
|
202
201
|
|
|
203
202
|
def delete(self) -> None:
|
|
@@ -236,7 +235,8 @@ class Raster(Base):
|
|
|
236
235
|
return endpoint
|
|
237
236
|
|
|
238
237
|
|
|
239
|
-
|
|
238
|
+
@property
|
|
239
|
+
def info(self) -> Dict:
|
|
240
240
|
"""
|
|
241
241
|
Get the info of the raster.
|
|
242
242
|
|
|
@@ -248,7 +248,7 @@ class Raster(Base):
|
|
|
248
248
|
>>> from geobox.raster import Raster
|
|
249
249
|
>>> client = GeoboxClient()
|
|
250
250
|
>>> raster = Raster.get_raster(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
251
|
-
>>> raster.
|
|
251
|
+
>>> raster.info
|
|
252
252
|
"""
|
|
253
253
|
endpoint = urljoin(self.endpoint, 'info/')
|
|
254
254
|
return self.api.get(endpoint)
|
|
@@ -323,53 +323,74 @@ class Raster(Base):
|
|
|
323
323
|
|
|
324
324
|
Raises:
|
|
325
325
|
ValueError: If save_path does not end with a '/'.
|
|
326
|
-
"""
|
|
327
|
-
# Get the original filename from data or use uuid
|
|
328
|
-
if self.name:
|
|
329
|
-
filename = f"{self.name.split('.')[0]}" if len(self.name.split('.')) > 1 else f'{self.name}'
|
|
330
|
-
else:
|
|
331
|
-
filename = f'{self.uuid}'
|
|
332
326
|
|
|
327
|
+
Example:
|
|
328
|
+
>>> from geobox import GeoboxClient
|
|
329
|
+
>>> from geobox.file import File
|
|
330
|
+
>>> from geobox import GeoboxClient
|
|
331
|
+
>>> client = GeoboxClient()
|
|
332
|
+
>>> file_path = File.get_file(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
333
|
+
"""
|
|
333
334
|
# If save_path is provided, check if it ends with a '/'
|
|
334
335
|
if save_path and save_path.endswith('/'):
|
|
335
|
-
return f'{save_path}
|
|
336
|
+
return f'{save_path}'
|
|
336
337
|
|
|
337
338
|
if save_path and not save_path.endswith('/'):
|
|
338
339
|
raise ValueError("save_path must end with a '/'")
|
|
339
340
|
|
|
340
|
-
return os.
|
|
341
|
+
return os.getcwd()
|
|
341
342
|
|
|
342
343
|
|
|
343
|
-
def
|
|
344
|
+
def _get_file_name(self, response: requests.Response) -> str:
|
|
344
345
|
"""
|
|
345
|
-
Get the file
|
|
346
|
+
Get the file name from the response.
|
|
346
347
|
|
|
347
348
|
Args:
|
|
348
349
|
response (requests.Response): The response of the request.
|
|
349
350
|
|
|
350
351
|
Returns:
|
|
351
|
-
str: The file
|
|
352
|
+
str: The file name
|
|
352
353
|
"""
|
|
353
|
-
ext = ""
|
|
354
354
|
if 'Content-Disposition' in response.headers and 'filename=' in response.headers['Content-Disposition']:
|
|
355
|
-
|
|
356
|
-
filename = content_disposition.split('filename=')[-1].strip().strip('"')
|
|
357
|
-
ext = f".{filename.split('.')[-1]}"
|
|
355
|
+
file_name = response.headers['Content-Disposition'].split('filename=')[-1].strip().strip('"')
|
|
358
356
|
|
|
359
357
|
else:
|
|
360
358
|
content_type = response.headers.get("Content-Type", "")
|
|
361
|
-
|
|
359
|
+
file_name = f'{self.name}.{mimetypes.guess_extension(content_type.split(";")[0])}'
|
|
360
|
+
|
|
361
|
+
return file_name
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def _create_progress_bar(self) -> 'tqdm':
|
|
365
|
+
"""Creates a progress bar for the task."""
|
|
366
|
+
try:
|
|
367
|
+
from tqdm.auto import tqdm
|
|
368
|
+
except ImportError:
|
|
369
|
+
from .api import logger
|
|
370
|
+
logger.warning("[tqdm] extra is required to show the progress bar. install with: pip insatll geobox[tqdm]")
|
|
371
|
+
return None
|
|
362
372
|
|
|
363
|
-
return
|
|
373
|
+
return tqdm(unit="B",
|
|
374
|
+
total=int(self.size),
|
|
375
|
+
file=sys.stdout,
|
|
376
|
+
dynamic_ncols=True,
|
|
377
|
+
desc="Downloading",
|
|
378
|
+
unit_scale=True,
|
|
379
|
+
unit_divisor=1024,
|
|
380
|
+
ascii=True
|
|
381
|
+
)
|
|
364
382
|
|
|
365
383
|
|
|
366
|
-
def download(self, save_path: str = None) -> str:
|
|
384
|
+
def download(self, save_path: str = None, progress_bar: bool = True) -> str:
|
|
367
385
|
"""
|
|
368
386
|
Download the raster.
|
|
369
387
|
|
|
370
388
|
Args:
|
|
371
|
-
save_path (str):
|
|
372
|
-
|
|
389
|
+
save_path (str, optional): Path where the file should be saved.
|
|
390
|
+
If not provided, it saves to the current working directory
|
|
391
|
+
using the original filename and appropriate extension.
|
|
392
|
+
progress_bar (bool, optional): Whether to show a progress bar. default: True
|
|
393
|
+
|
|
373
394
|
Returns:
|
|
374
395
|
str: The path to save the raster.
|
|
375
396
|
|
|
@@ -390,23 +411,32 @@ class Raster(Base):
|
|
|
390
411
|
save_path = self._get_save_path(save_path)
|
|
391
412
|
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
|
392
413
|
|
|
393
|
-
with self.api.get(f"{self.endpoint}download/", stream=True) as response
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
414
|
+
with self.api.get(f"{self.endpoint}download/", stream=True) as response:
|
|
415
|
+
file_name = self._get_file_name(response)
|
|
416
|
+
full_path = f"{save_path}/{file_name}"
|
|
417
|
+
with open(full_path, 'wb') as f:
|
|
418
|
+
pbar = self._create_progress_bar() if progress_bar else None
|
|
419
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
420
|
+
f.write(chunk)
|
|
421
|
+
if pbar:
|
|
422
|
+
pbar.update(len(chunk))
|
|
423
|
+
pbar.refresh()
|
|
424
|
+
if pbar:
|
|
425
|
+
pbar.close()
|
|
397
426
|
|
|
398
|
-
|
|
399
|
-
os.rename(os.path.abspath(save_path), os.path.abspath(final_path))
|
|
400
|
-
return os.path.abspath(final_path)
|
|
427
|
+
return os.path.abspath(full_path)
|
|
401
428
|
|
|
402
429
|
|
|
403
|
-
def get_content_file(self, save_path: str = None) -> str:
|
|
430
|
+
def get_content_file(self, save_path: str = None, progress_bar: bool = True) -> str:
|
|
404
431
|
"""
|
|
405
432
|
Get Raster Content URL
|
|
406
433
|
|
|
407
434
|
Args:
|
|
408
|
-
save_path (str):
|
|
409
|
-
|
|
435
|
+
save_path (str, optional): Path where the file should be saved.
|
|
436
|
+
If not provided, it saves to the current working directory
|
|
437
|
+
using the original filename and appropriate extension.
|
|
438
|
+
progress_bar (bool, optional): Whether to show a progress bar. default: True
|
|
439
|
+
|
|
410
440
|
Returns:
|
|
411
441
|
str: The path to save the raster.
|
|
412
442
|
|
|
@@ -427,17 +457,23 @@ class Raster(Base):
|
|
|
427
457
|
save_path = self._get_save_path(save_path)
|
|
428
458
|
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
|
429
459
|
|
|
430
|
-
with self.api.get(f"{self.endpoint}content/", stream=True) as response
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
460
|
+
with self.api.get(f"{self.endpoint}content/", stream=True) as response:
|
|
461
|
+
file_name = self._get_file_name(response)
|
|
462
|
+
full_path = f"{save_path}/{file_name}"
|
|
463
|
+
with open(full_path, 'wb') as f:
|
|
464
|
+
pbar = self._create_progress_bar() if progress_bar else None
|
|
465
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
466
|
+
f.write(chunk)
|
|
467
|
+
if pbar:
|
|
468
|
+
pbar.update(len(chunk))
|
|
469
|
+
pbar.refresh()
|
|
470
|
+
if pbar:
|
|
471
|
+
pbar.close()
|
|
434
472
|
|
|
435
|
-
|
|
436
|
-
os.rename(os.path.abspath(save_path), os.path.abspath(final_path))
|
|
437
|
-
return os.path.abspath(final_path)
|
|
473
|
+
return os.path.abspath(full_path)
|
|
438
474
|
|
|
439
475
|
|
|
440
|
-
def
|
|
476
|
+
def get_render_png_url(self, x: int, y: int, z: int, **kwargs) -> str:
|
|
441
477
|
"""
|
|
442
478
|
Get the PNG URL of the raster.
|
|
443
479
|
|
|
@@ -446,6 +482,15 @@ class Raster(Base):
|
|
|
446
482
|
y (int): The y coordinate of the tile.
|
|
447
483
|
z (int): The zoom level of the tile.
|
|
448
484
|
|
|
485
|
+
Keyword Args:
|
|
486
|
+
indexes (str, optional): list of comma separated band indexes to be rendered. e.g. 1, 2, 3
|
|
487
|
+
nodata (int, optional)
|
|
488
|
+
expression (str, optional): band math expression. e.g. b1*b2+b3
|
|
489
|
+
rescale (List, optional): comma (',') separated Min,Max range. Can set multiple time for multiple bands.
|
|
490
|
+
color_formula (str, optional): Color formula. e.g. gamma R 0.5
|
|
491
|
+
colormap_name (str, optional)
|
|
492
|
+
colormap (str, optional): JSON encoded custom Colormap. e.g. {"0": "#ff0000", "1": "#00ff00"} or [[[0, 100], "#ff0000"], [[100, 200], "#00ff00"]]
|
|
493
|
+
|
|
449
494
|
Returns:
|
|
450
495
|
str: The PNG Render URL of the raster.
|
|
451
496
|
|
|
@@ -456,11 +501,22 @@ class Raster(Base):
|
|
|
456
501
|
>>> raster = Raster.get_raster(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
457
502
|
>>> raster.get_tile_render_url(x=10, y=20, z=1)
|
|
458
503
|
"""
|
|
504
|
+
params = clean_data({
|
|
505
|
+
'indexes': kwargs.get('indexes'),
|
|
506
|
+
'nodata': kwargs.get('nodata'),
|
|
507
|
+
'expression': kwargs.get('expression'),
|
|
508
|
+
'rescale': kwargs.get('rescale'),
|
|
509
|
+
'color_formula': kwargs.get('color_formula'),
|
|
510
|
+
'colormap_name': kwargs.get('colormap_name'),
|
|
511
|
+
'colormap': kwargs.get('colormap')
|
|
512
|
+
})
|
|
513
|
+
query_string = urlencode(params)
|
|
459
514
|
endpoint = urljoin(self.api.base_url, f'{self.endpoint}render/{z}/{x}/{y}.png')
|
|
515
|
+
endpoint = urljoin(endpoint, f'?{query_string}')
|
|
460
516
|
return endpoint
|
|
461
517
|
|
|
462
518
|
|
|
463
|
-
def get_tile_pbf_url(self, x: int, y: int, z: int) -> str:
|
|
519
|
+
def get_tile_pbf_url(self, x: int, y: int, z: int, indexes: str = None) -> str:
|
|
464
520
|
"""
|
|
465
521
|
Get the URL of the tile.
|
|
466
522
|
|
|
@@ -468,6 +524,7 @@ class Raster(Base):
|
|
|
468
524
|
x (int): The x coordinate of the tile.
|
|
469
525
|
y (int): The y coordinate of the tile.
|
|
470
526
|
z (int): The zoom level of the tile.
|
|
527
|
+
indexes (str, optional): list of comma separated band indexes to be rendered. e.g. 1, 2, 3
|
|
471
528
|
|
|
472
529
|
Returns:
|
|
473
530
|
str: The URL of the tile.
|
|
@@ -479,7 +536,12 @@ class Raster(Base):
|
|
|
479
536
|
>>> raster = Raster.get_raster(client, uuid="12345678-1234-5678-1234-567812345678")
|
|
480
537
|
>>> raster.get_tile_pbf_url(x=10, y=20, z=1)
|
|
481
538
|
"""
|
|
539
|
+
params = clean_data({
|
|
540
|
+
'indexes': indexes
|
|
541
|
+
})
|
|
542
|
+
query_string = urlencode(params)
|
|
482
543
|
endpoint = urljoin(self.api.base_url, f'{self.endpoint}tiles/{z}/{x}/{y}.pbf')
|
|
544
|
+
endpoint = urljoin(endpoint, f'?{query_string}')
|
|
483
545
|
return endpoint
|
|
484
546
|
|
|
485
547
|
|
|
@@ -2,7 +2,7 @@ from typing import List, Any, TYPE_CHECKING, Union, Dict
|
|
|
2
2
|
from urllib.parse import urlencode, urljoin
|
|
3
3
|
|
|
4
4
|
from .base import Base
|
|
5
|
-
from .utils import clean_data
|
|
5
|
+
from .utils import clean_data, xor_encode
|
|
6
6
|
from .enums import UserRole, UserStatus
|
|
7
7
|
from .plan import Plan
|
|
8
8
|
|
|
@@ -113,11 +113,9 @@ class User(Base):
|
|
|
113
113
|
'return_count': kwargs.get('return_count', False),
|
|
114
114
|
'skip': kwargs.get('skip', 0),
|
|
115
115
|
'limit': kwargs.get('limit', 10),
|
|
116
|
-
'user_id': kwargs.get('user_id')
|
|
117
|
-
'shared': kwargs.get('shared', False)
|
|
116
|
+
'user_id': kwargs.get('user_id')
|
|
118
117
|
}
|
|
119
118
|
return super()._get_list(api, cls.BASE_ENDPOINT, params, factory_func=lambda api, item: User(api, item['id'], item))
|
|
120
|
-
|
|
121
119
|
|
|
122
120
|
|
|
123
121
|
@classmethod
|
|
@@ -174,7 +172,7 @@ class User(Base):
|
|
|
174
172
|
data = {
|
|
175
173
|
"username": username,
|
|
176
174
|
"email": email,
|
|
177
|
-
"password": password,
|
|
175
|
+
"password": xor_encode(password),
|
|
178
176
|
"role": role.value,
|
|
179
177
|
"first_name": first_name,
|
|
180
178
|
"last_name": last_name,
|
|
@@ -184,7 +182,6 @@ class User(Base):
|
|
|
184
182
|
return super()._create(api, cls.BASE_ENDPOINT, data, factory_func=lambda api, item: User(api, item['id'], item))
|
|
185
183
|
|
|
186
184
|
|
|
187
|
-
|
|
188
185
|
@classmethod
|
|
189
186
|
def search_users(cls, api: 'GeoboxClient', search: str = None, skip: int = 0, limit: int = 10) -> List['User']:
|
|
190
187
|
"""
|
|
@@ -377,7 +374,7 @@ class User(Base):
|
|
|
377
374
|
>>> user.change_password(new_password='user_new_password')
|
|
378
375
|
"""
|
|
379
376
|
data = clean_data({
|
|
380
|
-
"new_password": new_password
|
|
377
|
+
"new_password": xor_encode(new_password)
|
|
381
378
|
})
|
|
382
379
|
endpoint = urljoin(self.endpoint, 'change-password')
|
|
383
380
|
self.api.post(endpoint, data, is_json=False)
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
|
|
2
|
+
import base64
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def xor_encode(s, key=42):
|
|
6
|
+
xor_str = ''.join(chr(ord(c) ^ key) for c in s)
|
|
7
|
+
encoded_bytes = base64.b64encode(xor_str.encode('utf-8'))
|
|
8
|
+
return encoded_bytes.decode('utf-8')
|
|
2
9
|
|
|
3
10
|
|
|
4
11
|
def clean_data(data: dict) -> dict:
|
|
@@ -439,6 +439,36 @@ class VectorLayer(Base):
|
|
|
439
439
|
return super()._create(self.api, endpoint, data, factory_func=lambda api, item: VectorLayerVersion(api, item['uuid'], item))
|
|
440
440
|
|
|
441
441
|
|
|
442
|
+
def get_versions(self, **kwargs) -> List['VectorLayerVersion']:
|
|
443
|
+
"""
|
|
444
|
+
Get list of versions of the current vector layer object with optional filtering and pagination.
|
|
445
|
+
|
|
446
|
+
Keyword Args:
|
|
447
|
+
q (str): query filter based on OGC CQL standard. e.g. "field1 LIKE '%GIS%' AND created_at > '2021-01-01'"
|
|
448
|
+
search (str): search term for keyword-based searching among search_fields or all textual fields if search_fields does not have value. NOTE: if q param is defined this param will be ignored.
|
|
449
|
+
search_fields (str): comma separated list of fields for searching.
|
|
450
|
+
order_by (str): comma separated list of fields for sorting results [field1 A|D, field2 A|D, …]. e.g. name A, type D. NOTE: "A" denotes ascending order and "D" denotes descending order.
|
|
451
|
+
return_count (bool): Whether to return total count. default is False.
|
|
452
|
+
skip (int): Number of items to skip. default is 0.
|
|
453
|
+
limit (int): Number of items to return. default is 10.
|
|
454
|
+
user_id (int): Specific user. privileges required.
|
|
455
|
+
shared (bool): Whether to return shared versions. default is False.
|
|
456
|
+
|
|
457
|
+
Returns:
|
|
458
|
+
List[VectorLayerVersion] | int: A list of vector layer version instances or the total number of versions.
|
|
459
|
+
|
|
460
|
+
Example:
|
|
461
|
+
>>> from geobox import GeoboxClient
|
|
462
|
+
>>> from geobox.version import VectorLayerVersion
|
|
463
|
+
>>> client = GeoboxClient()
|
|
464
|
+
>>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
|
|
465
|
+
>>> versions = layer.get_versions()
|
|
466
|
+
or
|
|
467
|
+
>>> versions = layer.get_versions()
|
|
468
|
+
"""
|
|
469
|
+
return VectorLayerVersion.get_versions(self.api, layer_id=self.id, **kwargs)
|
|
470
|
+
|
|
471
|
+
|
|
442
472
|
@property
|
|
443
473
|
def wfs(self) -> str:
|
|
444
474
|
"""
|
|
@@ -139,11 +139,11 @@ def test_get_point(api, mock_mosaic_data):
|
|
|
139
139
|
api.get.assert_called_once_with(f'{mosaic.endpoint}point?lat=1&lng=2')
|
|
140
140
|
|
|
141
141
|
|
|
142
|
-
def
|
|
142
|
+
def test_get_render_png_url(api, mock_mosaic_data):
|
|
143
143
|
"""Test the get_tile_render_url method."""
|
|
144
144
|
mosaic = Mosaic(api, mock_mosaic_data['uuid'], mock_mosaic_data)
|
|
145
145
|
|
|
146
|
-
tile_render_url = mosaic.
|
|
146
|
+
tile_render_url = mosaic.get_render_png_url(x=1, y=2, z=3)
|
|
147
147
|
|
|
148
148
|
assert tile_render_url == f'{api.base_url}{mosaic.endpoint}render/3/1/2.png'
|
|
149
149
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
from unittest.mock import MagicMock, patch
|
|
3
|
+
from unittest import mock
|
|
3
4
|
import os
|
|
4
5
|
import tempfile
|
|
5
6
|
from urllib.parse import urljoin
|
|
@@ -93,14 +94,14 @@ def test_update(api, mock_raster_data):
|
|
|
93
94
|
raster = Raster(api, uuid=mock_raster_data['uuid'], data=mock_raster_data)
|
|
94
95
|
api.put.return_value = {**mock_raster_data, **{'name': 'new_name', 'display_name': 'New Display Name', 'description': 'New Description'}}
|
|
95
96
|
|
|
96
|
-
raster.update(name='new_name', display_name='New Display Name', description='New Description')
|
|
97
|
+
result = raster.update(name='new_name', display_name='New Display Name', description='New Description')
|
|
97
98
|
api.put.assert_called_once_with(
|
|
98
99
|
raster.endpoint,
|
|
99
100
|
{'name': 'new_name', 'display_name': 'New Display Name', 'description': 'New Description'}
|
|
100
101
|
)
|
|
101
|
-
assert raster.name == '
|
|
102
|
-
assert raster.display_name == '
|
|
103
|
-
assert raster.description == '
|
|
102
|
+
assert raster.name == result['name']
|
|
103
|
+
assert raster.display_name == result['display_name']
|
|
104
|
+
assert raster.description == result['description']
|
|
104
105
|
|
|
105
106
|
|
|
106
107
|
def test_delete(api, mock_raster_data):
|
|
@@ -121,12 +122,12 @@ def test_thumbnail(api, mock_raster_data):
|
|
|
121
122
|
assert thumbnail_url == f"{api.base_url}{raster.endpoint}thumbnail/"
|
|
122
123
|
|
|
123
124
|
|
|
124
|
-
def
|
|
125
|
+
def test_raster_info(api, mock_raster_data):
|
|
125
126
|
"""Test getting raster info."""
|
|
126
127
|
raster = Raster(api, uuid=mock_raster_data['uuid'], data=mock_raster_data)
|
|
127
128
|
api.get.return_value = mock_raster_data
|
|
128
129
|
|
|
129
|
-
info = raster.
|
|
130
|
+
info = raster.info
|
|
130
131
|
assert info == mock_raster_data
|
|
131
132
|
api.get.assert_called_once_with(f'{raster.endpoint}info/')
|
|
132
133
|
|
|
@@ -204,6 +205,21 @@ def test_download(api, mock_raster_data):
|
|
|
204
205
|
with pytest.raises(ValueError):
|
|
205
206
|
raster.download()
|
|
206
207
|
|
|
208
|
+
# import warning log
|
|
209
|
+
import builtins
|
|
210
|
+
original_import = builtins.__import__
|
|
211
|
+
|
|
212
|
+
def mocked_import(name, globals=None, locals=None, fromlist=(), level=0):
|
|
213
|
+
if name == "tqdm.auto":
|
|
214
|
+
raise ImportError("No module named 'tqdm'")
|
|
215
|
+
return original_import(name, globals, locals, fromlist, level)
|
|
216
|
+
|
|
217
|
+
with mock.patch("builtins.__import__", side_effect=mocked_import):
|
|
218
|
+
with mock.patch("geobox.api.logger") as mock_logger:
|
|
219
|
+
result = raster._create_progress_bar()
|
|
220
|
+
assert result is None
|
|
221
|
+
mock_logger.warning.assert_called_once()
|
|
222
|
+
|
|
207
223
|
|
|
208
224
|
def test_get_content(api, mock_raster_data):
|
|
209
225
|
"""Test getting raster content."""
|
|
@@ -228,11 +244,11 @@ def test_get_content(api, mock_raster_data):
|
|
|
228
244
|
raster.get_content_file()
|
|
229
245
|
|
|
230
246
|
|
|
231
|
-
def
|
|
247
|
+
def test_get_render_png_url(api, mock_raster_data):
|
|
232
248
|
"""Test getting tile PNG URL."""
|
|
233
249
|
raster = Raster(api, uuid=mock_raster_data['uuid'], data=mock_raster_data)
|
|
234
|
-
url = raster.
|
|
235
|
-
assert url == f"{raster.api.base_url}{raster.endpoint}
|
|
250
|
+
url = raster.get_render_png_url(x=10, y=20, z=1)
|
|
251
|
+
assert url == f"{raster.api.base_url}{raster.endpoint}render/1/10/20.png"
|
|
236
252
|
|
|
237
253
|
|
|
238
254
|
def test_get_tile_pbf_url(api, mock_raster_data):
|
|
@@ -5,6 +5,7 @@ from urllib.parse import urljoin, urlencode
|
|
|
5
5
|
from geobox.user import User
|
|
6
6
|
from geobox.enums import UserRole, UserStatus
|
|
7
7
|
from geobox.plan import Plan
|
|
8
|
+
from geobox.utils import xor_encode
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
def test_init(api, mock_user_data):
|
|
@@ -38,7 +39,7 @@ def test_get_users(api, mock_admin_user_data):
|
|
|
38
39
|
result = User.get_users(api, search='test')
|
|
39
40
|
assert isinstance(result, list)
|
|
40
41
|
assert isinstance(result[0], User)
|
|
41
|
-
api.get.assert_called_once_with('users/?f=json&search=test&return_count=False&skip=0&limit=10
|
|
42
|
+
api.get.assert_called_once_with('users/?f=json&search=test&return_count=False&skip=0&limit=10')
|
|
42
43
|
|
|
43
44
|
def test_create_user(api, mock_admin_user_data):
|
|
44
45
|
api.post.return_value = mock_admin_user_data
|
|
@@ -57,7 +58,7 @@ def test_create_user(api, mock_admin_user_data):
|
|
|
57
58
|
expected_data = {
|
|
58
59
|
"username": mock_admin_user_data['username'],
|
|
59
60
|
"email": mock_admin_user_data['email'],
|
|
60
|
-
"password": 'password',
|
|
61
|
+
"password": xor_encode('password'),
|
|
61
62
|
"role": UserRole.ACCOUNT_ADMIN.value,
|
|
62
63
|
"first_name": mock_admin_user_data['first_name'],
|
|
63
64
|
"last_name": mock_admin_user_data['last_name'],
|
|
@@ -126,7 +127,7 @@ def test_change_password(api, mock_admin_user_data):
|
|
|
126
127
|
with patch.object(api, 'post', return_value={"message": "success"}):
|
|
127
128
|
user.change_password('new_password')
|
|
128
129
|
expected_endpoint = urljoin(user.endpoint, 'change-password')
|
|
129
|
-
api.post.assert_called_once_with(expected_endpoint, {'new_password': 'new_password'}, is_json=False)
|
|
130
|
+
api.post.assert_called_once_with(expected_endpoint, {'new_password': xor_encode('new_password')}, is_json=False)
|
|
130
131
|
|
|
131
132
|
|
|
132
133
|
def test_renew_plan(api, mock_admin_user_data):
|
|
@@ -253,6 +253,17 @@ def test_create_version(api, mock_vector_data, layer_type, mock_version_data):
|
|
|
253
253
|
api.post.assert_called_once_with(f'{layer.endpoint}versions', {'name': 'design_version', 'display_name': 'Design Version', 'description': 'This layer represents design version.'})
|
|
254
254
|
|
|
255
255
|
|
|
256
|
+
@pytest.mark.parametrize("layer_type", [type for type in LayerType])
|
|
257
|
+
def test_get_versions(api, mock_vector_data, layer_type, mock_version_data):
|
|
258
|
+
layer = VectorLayer(api, uuid=mock_vector_data['uuid'], data=mock_vector_data, layer_type=layer_type)
|
|
259
|
+
api.get.return_value = [mock_version_data]
|
|
260
|
+
versions = layer.get_versions()
|
|
261
|
+
assert len(versions) == 1
|
|
262
|
+
assert versions[0].uuid == mock_version_data['uuid']
|
|
263
|
+
assert versions[0].data == mock_version_data
|
|
264
|
+
api.get.assert_called_once_with(f'vectorLayerVersions/?layer_id={layer.id}&f=json&return_count=False&skip=0&limit=10&shared=False')
|
|
265
|
+
|
|
266
|
+
|
|
256
267
|
@pytest.mark.parametrize("layer_type", [type for type in LayerType])
|
|
257
268
|
def test_wfs_access_token(api, mock_vector_data, layer_type):
|
|
258
269
|
layer = VectorLayer(api, uuid=mock_vector_data['uuid'], data=mock_vector_data, layer_type=layer_type)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|