geobox 2.2.0__tar.gz → 2.2.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-2.2.0 → geobox-2.2.2}/PKG-INFO +1 -1
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/feature.py +33 -10
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/task.py +4 -1
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/vectorlayer.py +3 -2
- {geobox-2.2.0 → geobox-2.2.2}/geobox/feature.py +46 -11
- {geobox-2.2.0 → geobox-2.2.2}/geobox/task.py +4 -1
- {geobox-2.2.0 → geobox-2.2.2}/geobox/vectorlayer.py +3 -2
- {geobox-2.2.0 → geobox-2.2.2}/geobox.egg-info/PKG-INFO +1 -1
- {geobox-2.2.0 → geobox-2.2.2}/pyproject.toml +1 -1
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_feature.py +56 -3
- {geobox-2.2.0 → geobox-2.2.2}/LICENSE +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/README.md +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/__init__.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/__init__.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/api.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/apikey.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/attachment.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/base.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/basemap.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/dashboard.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/field.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/file.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/layout.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/log.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/map.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/model3d.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/mosaic.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/plan.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/query.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/raster.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/raster_analysis.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/route.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/scene.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/settings.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/tile3d.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/tileset.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/usage.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/user.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/vector_tool.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/version.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/view.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/aio/workflow.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/api.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/apikey.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/attachment.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/base.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/basemap.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/dashboard.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/enums.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/exception.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/field.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/file.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/layout.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/log.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/map.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/model3d.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/mosaic.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/plan.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/query.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/raster.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/raster_analysis.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/route.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/scene.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/settings.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/tile3d.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/tileset.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/usage.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/user.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/utils.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/vector_tool.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/version.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/view.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox/workflow.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox.egg-info/SOURCES.txt +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox.egg-info/dependency_links.txt +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox.egg-info/requires.txt +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/geobox.egg-info/top_level.txt +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/setup.cfg +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_api.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_apikey.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_attachment.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_basemap.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_dashboard.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_field.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_file.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_layout.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_log.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_map.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_model3d.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_mosaic.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_plan.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_query.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_raster.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_raster_analysis.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_route.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_scene.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_settings.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_task.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_tile3d.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_tileset.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_usage.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_user.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_vector_tool.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_vectorlayer.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_version.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_view.py +0 -0
- {geobox-2.2.0 → geobox-2.2.2}/tests/test_workflow.py +0 -0
|
@@ -29,7 +29,11 @@ class AsyncFeature(AsyncBase):
|
|
|
29
29
|
super().__init__(api=layer.api)
|
|
30
30
|
self.layer = layer
|
|
31
31
|
self._srid = srid
|
|
32
|
-
self.data = data or {
|
|
32
|
+
self.data = data or {
|
|
33
|
+
"type": "Feature",
|
|
34
|
+
"geometry": {},
|
|
35
|
+
"properties": {}
|
|
36
|
+
}
|
|
33
37
|
self.original_geometry = self.data.get('geometry')
|
|
34
38
|
self.endpoint = urljoin(layer.endpoint, f'features/{self.data.get("id")}/') if self.data.get('id') else None
|
|
35
39
|
|
|
@@ -58,7 +62,8 @@ class AsyncFeature(AsyncBase):
|
|
|
58
62
|
Returns:
|
|
59
63
|
str: A string representation of the Feature object.
|
|
60
64
|
"""
|
|
61
|
-
|
|
65
|
+
feature_id = getattr(self, "id", "-1")
|
|
66
|
+
return f"AsyncFeature(id={feature_id}, type={self.feature_type})"
|
|
62
67
|
|
|
63
68
|
|
|
64
69
|
def __getattr__(self, name: str) -> Any:
|
|
@@ -210,18 +215,27 @@ class AsyncFeature(AsyncBase):
|
|
|
210
215
|
>>> await feature.save()
|
|
211
216
|
"""
|
|
212
217
|
data = self.data.copy()
|
|
218
|
+
srid = self.srid
|
|
219
|
+
|
|
213
220
|
try:
|
|
214
221
|
if self.id:
|
|
215
222
|
if self.srid != self.BASE_SRID:
|
|
216
|
-
self
|
|
223
|
+
self = self.transform(self.BASE_SRID)
|
|
224
|
+
self.data['geometry'] = self.original_geometry
|
|
217
225
|
await self.update(self.data)
|
|
218
226
|
self.coordinates = data['geometry']['coordinates']
|
|
219
227
|
except AttributeError:
|
|
228
|
+
if self.srid != self.BASE_SRID:
|
|
229
|
+
self = self.transform(self.BASE_SRID)
|
|
220
230
|
endpoint = urljoin(self.layer.endpoint, 'features/')
|
|
221
|
-
|
|
231
|
+
request_data = self.data.copy()
|
|
232
|
+
response = await self.layer.api.post(endpoint, request_data)
|
|
222
233
|
self.endpoint = urljoin(self.layer.endpoint, f'features/{response["id"]}/')
|
|
223
234
|
self.data.update(response)
|
|
224
235
|
|
|
236
|
+
self.data['geometry'] = data['geometry']
|
|
237
|
+
self._srid = srid
|
|
238
|
+
|
|
225
239
|
|
|
226
240
|
async def delete(self) -> None:
|
|
227
241
|
"""
|
|
@@ -267,13 +281,14 @@ class AsyncFeature(AsyncBase):
|
|
|
267
281
|
|
|
268
282
|
|
|
269
283
|
@classmethod
|
|
270
|
-
async def create_feature(cls, layer: 'VectorLayer', geojson: Dict) -> 'AsyncFeature':
|
|
284
|
+
async def create_feature(cls, layer: 'VectorLayer', geojson: Dict, srid: int = 3857) -> 'AsyncFeature':
|
|
271
285
|
"""
|
|
272
286
|
[async] Create a new feature in the vector layer.
|
|
273
287
|
|
|
274
288
|
Args:
|
|
275
289
|
layer (VectorLayer): The vector layer to create the feature in
|
|
276
290
|
geojson (Dict): The GeoJSON data for the feature
|
|
291
|
+
srid (int, optional): the feature srid. default: 3857
|
|
277
292
|
|
|
278
293
|
Returns:
|
|
279
294
|
AsyncFeature: The created feature instance
|
|
@@ -289,8 +304,19 @@ class AsyncFeature(AsyncBase):
|
|
|
289
304
|
... }
|
|
290
305
|
>>> feature = await Feature.create_feature(layer, geojson)
|
|
291
306
|
"""
|
|
307
|
+
feature = AsyncFeature(layer, srid=srid, data=geojson)
|
|
308
|
+
data = feature.data.copy()
|
|
309
|
+
srid = feature.srid
|
|
310
|
+
|
|
311
|
+
if feature.srid != feature.BASE_SRID:
|
|
312
|
+
feature = feature.transform(feature.BASE_SRID)
|
|
313
|
+
|
|
292
314
|
endpoint = urljoin(layer.endpoint, 'features/')
|
|
293
|
-
|
|
315
|
+
|
|
316
|
+
feature = await cls._create(layer.api, endpoint, feature.data, factory_func=lambda api, item: AsyncFeature(layer, data=item))
|
|
317
|
+
feature.data['geometry'] = data['geometry']
|
|
318
|
+
feature._srid = srid
|
|
319
|
+
return feature
|
|
294
320
|
|
|
295
321
|
|
|
296
322
|
@classmethod
|
|
@@ -350,10 +376,7 @@ class AsyncFeature(AsyncBase):
|
|
|
350
376
|
"Install it with: pip install geobox[geometry]"
|
|
351
377
|
)
|
|
352
378
|
|
|
353
|
-
if not self.data:
|
|
354
|
-
return None
|
|
355
|
-
|
|
356
|
-
elif not self.data.get('geometry'):
|
|
379
|
+
if not self.data.get('geometry'):
|
|
357
380
|
raise ValueError("Geometry is not present in the feature data")
|
|
358
381
|
|
|
359
382
|
elif not isinstance(self.data['geometry'], dict):
|
|
@@ -2,6 +2,7 @@ import time
|
|
|
2
2
|
import asyncio
|
|
3
3
|
from urllib.parse import urljoin
|
|
4
4
|
from typing import Optional, Dict, List, Optional, Union, TYPE_CHECKING
|
|
5
|
+
import logging
|
|
5
6
|
|
|
6
7
|
from .base import AsyncBase
|
|
7
8
|
from ..enums import TaskStatus
|
|
@@ -16,6 +17,8 @@ if TYPE_CHECKING:
|
|
|
16
17
|
from ..api import GeoboxClient
|
|
17
18
|
from ..task import Task
|
|
18
19
|
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
19
22
|
|
|
20
23
|
class AsyncTask(AsyncBase):
|
|
21
24
|
|
|
@@ -233,7 +236,7 @@ class AsyncTask(AsyncBase):
|
|
|
233
236
|
return await self._wait(timeout, interval, progress_bar)
|
|
234
237
|
except Exception as e:
|
|
235
238
|
last_exception = e
|
|
236
|
-
|
|
239
|
+
logger.warning(f"[Retry {attempt}/{retry}] Task wait failed: {e}")
|
|
237
240
|
time.sleep(interval)
|
|
238
241
|
raise last_exception
|
|
239
242
|
|
|
@@ -755,12 +755,13 @@ class AsyncVectorLayer(AsyncBase):
|
|
|
755
755
|
return feature
|
|
756
756
|
|
|
757
757
|
|
|
758
|
-
async def create_feature(self, geojson: Dict)-> 'AsyncFeature':
|
|
758
|
+
async def create_feature(self, geojson: Dict, srid: int = AsyncFeature.BASE_SRID)-> 'AsyncFeature':
|
|
759
759
|
"""
|
|
760
760
|
[async] Create a new feature in the layer.
|
|
761
761
|
|
|
762
762
|
Args:
|
|
763
763
|
geojson (Dict): The feature data including properties and geometry.
|
|
764
|
+
srid (int, optional): the feature srid. default: 3857
|
|
764
765
|
|
|
765
766
|
Returns:
|
|
766
767
|
Feature: The newly created feature instance.
|
|
@@ -780,7 +781,7 @@ class AsyncVectorLayer(AsyncBase):
|
|
|
780
781
|
... }
|
|
781
782
|
>>> feature = await layer.create_feature(geojson=geojson)
|
|
782
783
|
"""
|
|
783
|
-
return await AsyncFeature.create_feature(self, geojson)
|
|
784
|
+
return await AsyncFeature.create_feature(self, geojson, srid=srid)
|
|
784
785
|
|
|
785
786
|
|
|
786
787
|
async def delete_features(self,
|
|
@@ -30,13 +30,27 @@ class Feature(Base):
|
|
|
30
30
|
>>> from geobox import GeoboxClient, Feature
|
|
31
31
|
>>> client = GeoboxClient()
|
|
32
32
|
>>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
|
|
33
|
-
>>>
|
|
33
|
+
>>> geojson = {
|
|
34
|
+
... "type": "Feature",
|
|
35
|
+
... "geometry": {
|
|
36
|
+
... "type": "Point",
|
|
37
|
+
... "coordinates": [10.0, 10.0]
|
|
38
|
+
... },
|
|
39
|
+
... "properties": {
|
|
40
|
+
... "name": "Test feature"
|
|
41
|
+
... }
|
|
42
|
+
... }
|
|
43
|
+
>>> feature = Feature(layer=layer, data=geojson, srid=4326) # example srid set to 4326
|
|
34
44
|
>>> feature.save()
|
|
35
45
|
"""
|
|
36
46
|
super().__init__(api=layer.api)
|
|
37
47
|
self.layer = layer
|
|
38
48
|
self._srid = srid
|
|
39
|
-
self.data = data or {
|
|
49
|
+
self.data = data or {
|
|
50
|
+
"type": "Feature",
|
|
51
|
+
"geometry": {},
|
|
52
|
+
"properties": {}
|
|
53
|
+
}
|
|
40
54
|
self.original_geometry = self.data.get('geometry')
|
|
41
55
|
self.endpoint = urljoin(layer.endpoint, f'features/{self.data.get("id")}/') if self.data.get('id') else None
|
|
42
56
|
|
|
@@ -65,8 +79,9 @@ class Feature(Base):
|
|
|
65
79
|
Returns:
|
|
66
80
|
str: A string representation of the Feature object.
|
|
67
81
|
"""
|
|
68
|
-
|
|
69
|
-
|
|
82
|
+
feature_id = getattr(self, "id", "-1")
|
|
83
|
+
return f"Feature(id={feature_id}, type={self.feature_type})"
|
|
84
|
+
|
|
70
85
|
|
|
71
86
|
def __getattr__(self, name: str) -> Any:
|
|
72
87
|
"""
|
|
@@ -217,18 +232,28 @@ class Feature(Base):
|
|
|
217
232
|
>>> feature.save()
|
|
218
233
|
"""
|
|
219
234
|
data = self.data.copy()
|
|
235
|
+
srid = self.srid
|
|
236
|
+
|
|
220
237
|
try:
|
|
221
238
|
if self.id:
|
|
222
239
|
if self.srid != self.BASE_SRID:
|
|
223
|
-
self
|
|
240
|
+
self = self.transform(self.BASE_SRID)
|
|
241
|
+
self.data['geometry'] = self.original_geometry
|
|
224
242
|
self.update(self.data)
|
|
225
243
|
self.coordinates = data['geometry']['coordinates']
|
|
226
244
|
except AttributeError:
|
|
245
|
+
if self.srid != self.BASE_SRID:
|
|
246
|
+
self = self.transform(self.BASE_SRID)
|
|
227
247
|
endpoint = urljoin(self.layer.endpoint, 'features/')
|
|
228
|
-
|
|
248
|
+
request_data = self.data.copy()
|
|
249
|
+
response = self.layer.api.post(endpoint, request_data)
|
|
229
250
|
self.endpoint = urljoin(self.layer.endpoint, f'features/{response["id"]}/')
|
|
230
251
|
self.data.update(response)
|
|
231
252
|
|
|
253
|
+
self.data['geometry'] = data['geometry']
|
|
254
|
+
self._srid = srid
|
|
255
|
+
|
|
256
|
+
|
|
232
257
|
|
|
233
258
|
def delete(self) -> None:
|
|
234
259
|
"""
|
|
@@ -274,13 +299,14 @@ class Feature(Base):
|
|
|
274
299
|
|
|
275
300
|
|
|
276
301
|
@classmethod
|
|
277
|
-
def create_feature(cls, layer: 'VectorLayer', geojson: Dict) -> 'Feature':
|
|
302
|
+
def create_feature(cls, layer: 'VectorLayer', geojson: Dict, srid: int = 3857) -> 'Feature':
|
|
278
303
|
"""
|
|
279
304
|
Create a new feature in the vector layer.
|
|
280
305
|
|
|
281
306
|
Args:
|
|
282
307
|
layer (VectorLayer): The vector layer to create the feature in
|
|
283
308
|
geojson (Dict): The GeoJSON data for the feature
|
|
309
|
+
srid (int, optional): the feature srid. default: 3857
|
|
284
310
|
|
|
285
311
|
Returns:
|
|
286
312
|
Feature: The created feature instance
|
|
@@ -297,8 +323,19 @@ class Feature(Base):
|
|
|
297
323
|
... }
|
|
298
324
|
>>> feature = Feature.create_feature(layer, geojson)
|
|
299
325
|
"""
|
|
326
|
+
feature = Feature(layer, srid=srid, data=geojson)
|
|
327
|
+
data = feature.data.copy()
|
|
328
|
+
srid = feature.srid
|
|
329
|
+
|
|
330
|
+
if feature.srid != feature.BASE_SRID:
|
|
331
|
+
feature = feature.transform(feature.BASE_SRID)
|
|
332
|
+
|
|
300
333
|
endpoint = urljoin(layer.endpoint, 'features/')
|
|
301
|
-
|
|
334
|
+
|
|
335
|
+
feature = cls._create(layer.api, endpoint, feature.data, factory_func=lambda api, item: Feature(layer, data=item))
|
|
336
|
+
feature.data['geometry'] = data['geometry']
|
|
337
|
+
feature._srid = srid
|
|
338
|
+
return feature
|
|
302
339
|
|
|
303
340
|
|
|
304
341
|
@classmethod
|
|
@@ -359,10 +396,8 @@ class Feature(Base):
|
|
|
359
396
|
"Install it with: pip install geobox[geometry]"
|
|
360
397
|
)
|
|
361
398
|
|
|
362
|
-
if not self.data:
|
|
363
|
-
return None
|
|
364
399
|
|
|
365
|
-
|
|
400
|
+
if not self.data.get('geometry'):
|
|
366
401
|
raise ValueError("Geometry is not present in the feature data")
|
|
367
402
|
|
|
368
403
|
elif not isinstance(self.data['geometry'], dict):
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from urllib.parse import urljoin, urlencode
|
|
2
2
|
from typing import Optional, Dict, List, Optional, Union, TYPE_CHECKING
|
|
3
3
|
import time
|
|
4
|
+
import logging
|
|
4
5
|
|
|
5
6
|
from .base import Base
|
|
6
7
|
from .enums import TaskStatus
|
|
@@ -15,6 +16,8 @@ if TYPE_CHECKING:
|
|
|
15
16
|
from .aio import AsyncGeoboxClient
|
|
16
17
|
from .aio.task import AsyncTask
|
|
17
18
|
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
18
21
|
|
|
19
22
|
class Task(Base):
|
|
20
23
|
|
|
@@ -230,7 +233,7 @@ class Task(Base):
|
|
|
230
233
|
return self._wait(timeout, interval, progress_bar)
|
|
231
234
|
except Exception as e:
|
|
232
235
|
last_exception = e
|
|
233
|
-
|
|
236
|
+
logger.warning(f"[Retry {attempt}/{retry}] Task wait failed: {e}")
|
|
234
237
|
time.sleep(interval)
|
|
235
238
|
raise last_exception
|
|
236
239
|
|
|
@@ -756,12 +756,13 @@ class VectorLayer(Base):
|
|
|
756
756
|
return feature
|
|
757
757
|
|
|
758
758
|
|
|
759
|
-
def create_feature(self, geojson: Dict)-> 'Feature':
|
|
759
|
+
def create_feature(self, geojson: Dict, srid: int = Feature.BASE_SRID)-> 'Feature':
|
|
760
760
|
"""
|
|
761
761
|
Create a new feature in the layer.
|
|
762
762
|
|
|
763
763
|
Args:
|
|
764
764
|
geojson (Dict): The feature data including properties and geometry.
|
|
765
|
+
srid (int, optional): the feature srid. default: 3857
|
|
765
766
|
|
|
766
767
|
Returns:
|
|
767
768
|
Feature: The newly created feature instance.
|
|
@@ -781,7 +782,7 @@ class VectorLayer(Base):
|
|
|
781
782
|
... }
|
|
782
783
|
>>> feature = layer.create_feature(geojson=geojson)
|
|
783
784
|
"""
|
|
784
|
-
return Feature.create_feature(self, geojson)
|
|
785
|
+
return Feature.create_feature(self, geojson, srid=srid)
|
|
785
786
|
|
|
786
787
|
|
|
787
788
|
def delete_features(self, q: str = None, bbox: str = None, bbox_srid: int = None, feature_ids: List[int] = None,
|
|
@@ -107,6 +107,26 @@ def test_save_srid(api, mock_vector_data, mock_feature_data, feature_type):
|
|
|
107
107
|
assert feature.srid == 3857
|
|
108
108
|
assert feature.coordinates == coords
|
|
109
109
|
|
|
110
|
+
# without id
|
|
111
|
+
feature_id = feature_data['id']
|
|
112
|
+
del feature_data['id']
|
|
113
|
+
# Mock response should include the id as the API returns the created feature with id
|
|
114
|
+
response_data = feature_data.copy()
|
|
115
|
+
response_data['id'] = feature_id
|
|
116
|
+
api.post.return_value = response_data
|
|
117
|
+
feature = Feature(layer, srid=3857, data=feature_data)
|
|
118
|
+
feature.transform(4326)
|
|
119
|
+
coords = feature.coordinates
|
|
120
|
+
feature.save()
|
|
121
|
+
assert feature.srid == 4326
|
|
122
|
+
assert feature.coordinates == coords
|
|
123
|
+
|
|
124
|
+
feature = Feature(layer, srid=3857, data=feature_data)
|
|
125
|
+
coords = feature.coordinates
|
|
126
|
+
feature.save()
|
|
127
|
+
assert feature.srid == 3857
|
|
128
|
+
assert feature.coordinates == coords
|
|
129
|
+
|
|
110
130
|
|
|
111
131
|
@pytest.mark.parametrize("feature_type", [type.value for type in FeatureType])
|
|
112
132
|
def test_delete(api, mock_vector_data, mock_feature_data, feature_type):
|
|
@@ -159,7 +179,7 @@ def test_create_feature(api, mock_vector_data, mock_feature_data, feature_type):
|
|
|
159
179
|
|
|
160
180
|
feature = Feature.create_feature(
|
|
161
181
|
layer=layer,
|
|
162
|
-
geojson=expected_request
|
|
182
|
+
geojson=expected_request,
|
|
163
183
|
)
|
|
164
184
|
api.post.assert_called_once_with(f'{layer.endpoint}features/', expected_request)
|
|
165
185
|
assert isinstance(feature, Feature)
|
|
@@ -168,6 +188,39 @@ def test_create_feature(api, mock_vector_data, mock_feature_data, feature_type):
|
|
|
168
188
|
assert feature.data == api_response
|
|
169
189
|
|
|
170
190
|
|
|
191
|
+
@pytest.mark.parametrize("feature_type", [type.value for type in FeatureType])
|
|
192
|
+
def test_create_feature_with_srid(api, mock_vector_data, mock_feature_data, feature_type):
|
|
193
|
+
"""Test creating a feature for different geometry types"""
|
|
194
|
+
layer = VectorLayer(api, uuid=mock_vector_data['uuid'], layer_type=LayerType.MultiPoint, data=mock_vector_data)
|
|
195
|
+
feature_data = mock_feature_data[feature_type]
|
|
196
|
+
|
|
197
|
+
expected_request = {
|
|
198
|
+
'type': 'Feature',
|
|
199
|
+
'geometry': feature_data['geometry'],
|
|
200
|
+
'properties': {'name': f'test_{feature_type.lower()}'}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
api_response = {
|
|
204
|
+
'type': 'Feature',
|
|
205
|
+
'geometry': feature_data['geometry'],
|
|
206
|
+
'properties': {'name': f'test_{feature_type.lower()}'},
|
|
207
|
+
'id': feature_data['id'],
|
|
208
|
+
'bbox': feature_data['bbox']
|
|
209
|
+
}
|
|
210
|
+
api.post.return_value = api_response
|
|
211
|
+
|
|
212
|
+
feature = Feature.create_feature(
|
|
213
|
+
layer=layer,
|
|
214
|
+
geojson=expected_request,
|
|
215
|
+
srid=4326
|
|
216
|
+
)
|
|
217
|
+
api.post.assert_called_once_with(f'{layer.endpoint}features/', expected_request)
|
|
218
|
+
assert isinstance(feature, Feature)
|
|
219
|
+
assert feature.layer == layer
|
|
220
|
+
assert feature.srid == 4326
|
|
221
|
+
assert feature.data == api_response
|
|
222
|
+
|
|
223
|
+
|
|
171
224
|
@pytest.mark.parametrize("feature_type", [type.value for type in FeatureType])
|
|
172
225
|
def test_get_feature(api, mock_vector_data, mock_feature_data, feature_type):
|
|
173
226
|
"""Test getting a feature for different geometry types"""
|
|
@@ -200,8 +253,8 @@ def test_geometry_no_data(api, mock_vector_data):
|
|
|
200
253
|
layer = VectorLayer(api, uuid=mock_vector_data['uuid'], layer_type=LayerType('Point'), data=mock_vector_data)
|
|
201
254
|
feature = Feature(layer, srid=3857, data=None) # No data
|
|
202
255
|
|
|
203
|
-
|
|
204
|
-
|
|
256
|
+
with pytest.raises(ValueError, match="Geometry is not present in the feature data"):
|
|
257
|
+
feature.geometry
|
|
205
258
|
|
|
206
259
|
|
|
207
260
|
def test_geometry_no_geometry_field(api, mock_vector_data):
|
|
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
|
|
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
|