pygeobox 1.0.0__py3-none-any.whl
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.
- pygeobox/__init__.py +63 -0
- pygeobox/api.py +2518 -0
- pygeobox/apikey.py +233 -0
- pygeobox/attachment.py +298 -0
- pygeobox/base.py +364 -0
- pygeobox/basemap.py +163 -0
- pygeobox/dashboard.py +316 -0
- pygeobox/enums.py +355 -0
- pygeobox/exception.py +47 -0
- pygeobox/feature.py +508 -0
- pygeobox/field.py +309 -0
- pygeobox/file.py +494 -0
- pygeobox/log.py +101 -0
- pygeobox/map.py +858 -0
- pygeobox/model3d.py +334 -0
- pygeobox/mosaic.py +647 -0
- pygeobox/plan.py +261 -0
- pygeobox/query.py +668 -0
- pygeobox/raster.py +756 -0
- pygeobox/route.py +63 -0
- pygeobox/scene.py +317 -0
- pygeobox/settings.py +166 -0
- pygeobox/task.py +354 -0
- pygeobox/tile3d.py +295 -0
- pygeobox/tileset.py +585 -0
- pygeobox/usage.py +211 -0
- pygeobox/user.py +424 -0
- pygeobox/utils.py +44 -0
- pygeobox/vectorlayer.py +1150 -0
- pygeobox/version.py +249 -0
- pygeobox/view.py +859 -0
- pygeobox/workflow.py +315 -0
- pygeobox-1.0.0.dist-info/METADATA +106 -0
- pygeobox-1.0.0.dist-info/RECORD +37 -0
- pygeobox-1.0.0.dist-info/WHEEL +5 -0
- pygeobox-1.0.0.dist-info/licenses/LICENSE +21 -0
- pygeobox-1.0.0.dist-info/top_level.txt +1 -0
pygeobox/exception.py
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
class GeoboxError(Exception):
|
2
|
+
"""Base class for all exceptions raised by the Geobox SDK."""
|
3
|
+
pass
|
4
|
+
|
5
|
+
# Error Classes
|
6
|
+
class AuthenticationError(GeoboxError):
|
7
|
+
"""Raised when there is an authentication error."""
|
8
|
+
|
9
|
+
def __init__(self, message="Authentication failed"):
|
10
|
+
self.message = message
|
11
|
+
super().__init__(self.message)
|
12
|
+
|
13
|
+
class AuthorizationError(GeoboxError):
|
14
|
+
"""Raised when there is an authorization error."""
|
15
|
+
|
16
|
+
def __init__(self, message="Authorization failed"):
|
17
|
+
self.message = message
|
18
|
+
super().__init__(self.message)
|
19
|
+
|
20
|
+
class ApiRequestError(GeoboxError):
|
21
|
+
"""Raised when there is an error with the API request."""
|
22
|
+
|
23
|
+
def __init__(self, status_code, message="API request failed"):
|
24
|
+
self.status_code = status_code
|
25
|
+
self.message = f"{message}: Status code {status_code}"
|
26
|
+
super().__init__(self.message)
|
27
|
+
|
28
|
+
class NotFoundError(GeoboxError):
|
29
|
+
"""Raised when a requested resource is not found."""
|
30
|
+
|
31
|
+
def __init__(self, message="Resource not found"):
|
32
|
+
self.message = message
|
33
|
+
super().__init__(self.message)
|
34
|
+
|
35
|
+
class ValidationError(GeoboxError):
|
36
|
+
"""Raised when there is a validation error."""
|
37
|
+
|
38
|
+
def __init__(self, message="Validation error"):
|
39
|
+
self.message = message
|
40
|
+
super().__init__(self.message)
|
41
|
+
|
42
|
+
class ServerError(GeoboxError):
|
43
|
+
"""Raised when there is a server error."""
|
44
|
+
|
45
|
+
def __init__(self, message="Server error"):
|
46
|
+
self.message = message
|
47
|
+
super().__init__(self.message)
|
pygeobox/feature.py
ADDED
@@ -0,0 +1,508 @@
|
|
1
|
+
from urllib.parse import urljoin
|
2
|
+
from typing import Optional, List, Dict, Any, TYPE_CHECKING
|
3
|
+
|
4
|
+
from .base import Base
|
5
|
+
from .enums import FeatureType
|
6
|
+
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
from .vectorlayer import VectorLayer
|
9
|
+
|
10
|
+
class Feature(Base):
|
11
|
+
"""
|
12
|
+
A class representing a feature in a vector layer in Geobox.
|
13
|
+
|
14
|
+
This class provides functionality to create, manage, and manipulate features in a vector layer.
|
15
|
+
It supports various operations including CRUD operations on features, as well as advanced operations like transforming geometries.
|
16
|
+
It also provides properties to access the feature geometry and properties, and a method to transform the feature geometry.
|
17
|
+
"""
|
18
|
+
BASE_SRID = 3857
|
19
|
+
|
20
|
+
def __init__(self,
|
21
|
+
layer: 'VectorLayer',
|
22
|
+
srid: Optional[int] = 3857,
|
23
|
+
data: Optional[Dict] = {}):
|
24
|
+
"""
|
25
|
+
Constructs all the necessary attributes for the Feature object.
|
26
|
+
|
27
|
+
Args:
|
28
|
+
layer (VectorLayer): The vector layer this feature belongs to
|
29
|
+
srid (int, optional): The Spatial Reference System Identifier (default is 3857)
|
30
|
+
data (Dict, optional): The feature data contains the feature geometry and properties
|
31
|
+
|
32
|
+
Example:
|
33
|
+
>>> from geobox import GeoboxClient, Feature
|
34
|
+
>>> client = GeoboxClient()
|
35
|
+
>>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
|
36
|
+
>>> feature = Feature(layer=layer, srid=4326) # example srid set to 4326
|
37
|
+
>>> feature.save()
|
38
|
+
"""
|
39
|
+
super().__init__(api=layer.api)
|
40
|
+
self.layer = layer
|
41
|
+
self._srid = srid
|
42
|
+
self.data = data or {}
|
43
|
+
self.original_geometry = self.data.get('geometry')
|
44
|
+
self.endpoint = urljoin(layer.endpoint, f'features/{self.data.get("id")}/') if self.data.get('id') else None
|
45
|
+
|
46
|
+
|
47
|
+
def __dir__(self) -> List[str]:
|
48
|
+
"""
|
49
|
+
Return a list of available attributes for the Feature object.
|
50
|
+
|
51
|
+
This method extends the default dir() behavior to include:
|
52
|
+
- All keys from the feature data dictionary
|
53
|
+
- All keys from the geometry dictionary
|
54
|
+
- All keys from the properties dictionary
|
55
|
+
|
56
|
+
This allows for better IDE autocompletion and introspection of feature attributes.
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
list: A list of attribute names available on this Feature object.
|
60
|
+
"""
|
61
|
+
return super().__dir__() + list(self.data.keys()) + list(self.data.get('geometry').keys()) + list(self.data.get('properties').keys())
|
62
|
+
|
63
|
+
|
64
|
+
def __repr__(self) -> str:
|
65
|
+
"""
|
66
|
+
Return a string representation of the Feature object.
|
67
|
+
|
68
|
+
Returns:
|
69
|
+
str: A string representation of the Feature object.
|
70
|
+
"""
|
71
|
+
return f"Feature(type={self.feature_type}, id={self.id})"
|
72
|
+
|
73
|
+
|
74
|
+
def __getattr__(self, name: str) -> Any:
|
75
|
+
"""
|
76
|
+
Get an attribute from the resource.
|
77
|
+
|
78
|
+
Args:
|
79
|
+
name (str): The name of the attribute
|
80
|
+
"""
|
81
|
+
if name in self.data:
|
82
|
+
return self.data.get(name)
|
83
|
+
# elif name in self.data['geometry']:
|
84
|
+
# return self.data['geometry'].get(name)
|
85
|
+
elif name in self.data['properties']:
|
86
|
+
return self.data['properties'].get(name)
|
87
|
+
|
88
|
+
raise AttributeError(f"Feature has no attribute {name}")
|
89
|
+
|
90
|
+
|
91
|
+
@property
|
92
|
+
def srid(self) -> int:
|
93
|
+
"""
|
94
|
+
Get the Spatial Reference System Identifier (SRID) of the feature.
|
95
|
+
|
96
|
+
Returns:
|
97
|
+
int: The SRID of the feature.
|
98
|
+
|
99
|
+
Example:
|
100
|
+
>>> from geobox import GeoboxClient
|
101
|
+
>>> client = GeoboxClient()
|
102
|
+
>>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
|
103
|
+
>>> feature = layer.get_feature(id=1)
|
104
|
+
>>> feature.srid # 3857
|
105
|
+
"""
|
106
|
+
return self._srid
|
107
|
+
|
108
|
+
|
109
|
+
@property
|
110
|
+
def feature_type(self) -> 'FeatureType':
|
111
|
+
"""
|
112
|
+
Get the type of the feature.
|
113
|
+
|
114
|
+
Returns:
|
115
|
+
FeatureType: The type of the feature.
|
116
|
+
|
117
|
+
Example:
|
118
|
+
>>> from geobox import GeoboxClient
|
119
|
+
>>> client = GeoboxClient()
|
120
|
+
>>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
|
121
|
+
>>> feature = layer.get_feature(id=1)
|
122
|
+
>>> feature.feature_type
|
123
|
+
"""
|
124
|
+
return FeatureType(self.data.get('geometry').get('type')) if self.data.get('geometry') else None
|
125
|
+
|
126
|
+
|
127
|
+
@property
|
128
|
+
def coordinates(self) -> List[float]:
|
129
|
+
"""
|
130
|
+
Get the coordinates of the ferepoature.
|
131
|
+
|
132
|
+
Returns:
|
133
|
+
list: The coordinates of the feature.
|
134
|
+
|
135
|
+
Example:
|
136
|
+
>>> from geobox import GeoboxClient
|
137
|
+
>>> client = GeoboxClient()
|
138
|
+
>>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
|
139
|
+
>>> feature = layer.get_feature(id=1)
|
140
|
+
>>> feature.coordinates
|
141
|
+
"""
|
142
|
+
return self.data.get('geometry').get('coordinates') if self.data.get('geometry') else None
|
143
|
+
|
144
|
+
|
145
|
+
@coordinates.setter
|
146
|
+
def coordinates(self, value: List[float]) -> None:
|
147
|
+
"""
|
148
|
+
Set the coordinates of the feature.
|
149
|
+
|
150
|
+
Args:
|
151
|
+
value (list): The coordinates to set.
|
152
|
+
|
153
|
+
Example:
|
154
|
+
>>> from geobox import GeoboxClient
|
155
|
+
>>> client = GeoboxClient()
|
156
|
+
>>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
|
157
|
+
>>> feature = layer.get_feature(id=1)
|
158
|
+
>>> feature.coordinates = [10, 20]
|
159
|
+
"""
|
160
|
+
self.data['geometry']['coordinates'] = value
|
161
|
+
|
162
|
+
|
163
|
+
@property
|
164
|
+
def length(self) -> float:
|
165
|
+
"""
|
166
|
+
Returns the length of thefeature geometry (geometry package extra is required!)
|
167
|
+
|
168
|
+
Returns:
|
169
|
+
float: the length of thefeature geometry
|
170
|
+
|
171
|
+
Example:
|
172
|
+
>>> from geobox import GeoboxClient
|
173
|
+
>>> client = GeoboxClient()
|
174
|
+
>>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
|
175
|
+
>>> feature = layer.get_feature(id=1)
|
176
|
+
>>> feature.length
|
177
|
+
"""
|
178
|
+
try:
|
179
|
+
return self.geom_length
|
180
|
+
except AttributeError:
|
181
|
+
endpoint = f'{self.endpoint}length'
|
182
|
+
return self.api.get(endpoint)
|
183
|
+
|
184
|
+
|
185
|
+
@property
|
186
|
+
def area(self) -> float:
|
187
|
+
"""
|
188
|
+
Returns the area of thefeature geometry (geometry package extra is required!)
|
189
|
+
|
190
|
+
Returns:
|
191
|
+
float: the area of thefeature geometry
|
192
|
+
|
193
|
+
Example:
|
194
|
+
>>> from geobox import GeoboxClient
|
195
|
+
>>> client = GeoboxClient()
|
196
|
+
>>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
|
197
|
+
>>> feature = layer.get_feature(id=1)
|
198
|
+
>>> feature.area
|
199
|
+
"""
|
200
|
+
try:
|
201
|
+
return self.geom_area
|
202
|
+
except AttributeError:
|
203
|
+
endpoint = f'{self.endpoint}area'
|
204
|
+
return self.api.get(endpoint)
|
205
|
+
|
206
|
+
|
207
|
+
def save(self) -> None:
|
208
|
+
"""
|
209
|
+
Save the feature. Creates a new feature if feature_id is None, updates existing feature otherwise.
|
210
|
+
|
211
|
+
Returns:
|
212
|
+
None
|
213
|
+
|
214
|
+
Example:
|
215
|
+
>>> from geobox import GeoboxClient
|
216
|
+
>>> client = GeoboxClient()
|
217
|
+
>>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
|
218
|
+
>>> feature = layer.get_feature(id=1)
|
219
|
+
>>> feature.properties['name'] = 'New Name'
|
220
|
+
>>> feature.save()
|
221
|
+
"""
|
222
|
+
data = self.data.copy()
|
223
|
+
try:
|
224
|
+
if self.id:
|
225
|
+
if self.srid != self.BASE_SRID:
|
226
|
+
self.data['geometry'] = self.original_geometry
|
227
|
+
self.update(self.data)
|
228
|
+
self.coordinates = data['geometry']['coordinates']
|
229
|
+
except AttributeError:
|
230
|
+
endpoint = urljoin(self.layer.endpoint, 'features/')
|
231
|
+
response = self.layer.api.post(endpoint, data)
|
232
|
+
self.endpoint = urljoin(self.layer.endpoint, f'features/{response["id"]}/')
|
233
|
+
self.data.update(response)
|
234
|
+
|
235
|
+
|
236
|
+
def delete(self) -> None:
|
237
|
+
"""
|
238
|
+
Delete the feature.
|
239
|
+
|
240
|
+
Returns:
|
241
|
+
None
|
242
|
+
|
243
|
+
Example:
|
244
|
+
>>> from geobox import GeoboxClient
|
245
|
+
>>> client = GeoboxClient()
|
246
|
+
>>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
|
247
|
+
>>> feature = layer.get_feature(id=1)
|
248
|
+
>>> feature.delete()
|
249
|
+
"""
|
250
|
+
super().delete(self.endpoint)
|
251
|
+
|
252
|
+
|
253
|
+
def update(self, geojson: Dict) -> Dict:
|
254
|
+
"""
|
255
|
+
Update the feature data property.
|
256
|
+
|
257
|
+
Args:
|
258
|
+
geojson (Dict): The GeoJSON data for the feature
|
259
|
+
|
260
|
+
Returns:
|
261
|
+
Dict: The response from the API.
|
262
|
+
|
263
|
+
Example:
|
264
|
+
>>> from geobox import GeoboxClient
|
265
|
+
>>> client = GeoboxClient()
|
266
|
+
>>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
|
267
|
+
>>> feature = layer.get_feature(id=1)
|
268
|
+
>>> geojson = {
|
269
|
+
"geometry": {
|
270
|
+
"type": "Point",
|
271
|
+
"coordinates": [10, 20]
|
272
|
+
}
|
273
|
+
}
|
274
|
+
>>> feature.update(geojson)
|
275
|
+
"""
|
276
|
+
return super()._update(self.endpoint, geojson, clean=False)
|
277
|
+
|
278
|
+
|
279
|
+
@classmethod
|
280
|
+
def create_feature(cls, layer: 'VectorLayer', geojson: Dict) -> 'Feature':
|
281
|
+
"""
|
282
|
+
Create a new feature in the vector layer.
|
283
|
+
|
284
|
+
Args:
|
285
|
+
layer (VectorLayer): The vector layer to create the feature in
|
286
|
+
geojson (Dict): The GeoJSON data for the feature
|
287
|
+
|
288
|
+
Returns:
|
289
|
+
Feature: The created feature instance
|
290
|
+
|
291
|
+
Example:
|
292
|
+
>>> from geobox import GeoboxClient
|
293
|
+
>>> from geobox.feature import Feature
|
294
|
+
>>> client = GeoboxClient()
|
295
|
+
>>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
|
296
|
+
>>> geojson = {
|
297
|
+
... "type": "Feature",
|
298
|
+
... "geometry": {"type": "Point", "coordinates": [10, 20]},
|
299
|
+
... "properties": {"name": "My Point"}
|
300
|
+
... }
|
301
|
+
>>> feature = Feature.create_feature(layer, geojson)
|
302
|
+
"""
|
303
|
+
endpoint = urljoin(layer.endpoint, 'features/')
|
304
|
+
return cls._create(layer.api, endpoint, geojson, factory_func=lambda api, item: Feature(layer, data=item))
|
305
|
+
|
306
|
+
|
307
|
+
@classmethod
|
308
|
+
def get_feature(cls, layer: 'VectorLayer', feature_id: int, user_id: int = None) -> 'Feature':
|
309
|
+
"""
|
310
|
+
Get a feature by its ID.
|
311
|
+
|
312
|
+
Args:
|
313
|
+
layer (VectorLayer): The vector layer the feature belongs to
|
314
|
+
feature_id (int): The ID of the feature
|
315
|
+
user_id (int): specific user. privileges required.
|
316
|
+
|
317
|
+
Returns:
|
318
|
+
Feature: The retrieved feature instance
|
319
|
+
|
320
|
+
Example:
|
321
|
+
>>> from geobox import GeoboxClient
|
322
|
+
>>> from geobox.feature import Feature
|
323
|
+
>>> client = GeoboxClient()
|
324
|
+
>>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
|
325
|
+
>>> feature = Feature.get_feature(layer, feature_id=1)
|
326
|
+
"""
|
327
|
+
param = {
|
328
|
+
'f': 'json',
|
329
|
+
'user_id': user_id
|
330
|
+
}
|
331
|
+
endpoint = urljoin(layer.endpoint, f'features/')
|
332
|
+
return cls._get_detail(layer.api, endpoint, uuid=feature_id, params=param, factory_func=lambda api, item: Feature(layer, data=item))
|
333
|
+
|
334
|
+
|
335
|
+
@property
|
336
|
+
def geometry(self) -> 'BaseGeometry':
|
337
|
+
"""
|
338
|
+
Get the feature geometry as a Shapely geometry object.
|
339
|
+
|
340
|
+
Returns:
|
341
|
+
shapely.geometry.BaseGeometry: The Shapely geometry object representing the feature's geometry
|
342
|
+
|
343
|
+
Raises:
|
344
|
+
ValueError: If the geometry is not a dictionary
|
345
|
+
ValueError: If the geometry type is not present in the feature data
|
346
|
+
ValueError: If the geometry coordinates are not present in the feature data
|
347
|
+
ImportError: If shapely is not installed
|
348
|
+
|
349
|
+
Example:
|
350
|
+
>>> from geobox import GeoboxClient
|
351
|
+
>>> from geobox.feature import Feature
|
352
|
+
>>> client = GeoboxClient()
|
353
|
+
>>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
|
354
|
+
>>> feature = layer.get_feature(id=1)
|
355
|
+
>>> feature.geometry
|
356
|
+
"""
|
357
|
+
try:
|
358
|
+
from shapely.geometry import shape
|
359
|
+
except ImportError:
|
360
|
+
raise ImportError(
|
361
|
+
"The 'geometry' extra is required for this function. "
|
362
|
+
"Install it with: pip install pygeobox[geometry]"
|
363
|
+
)
|
364
|
+
|
365
|
+
if not self.data:
|
366
|
+
return None
|
367
|
+
|
368
|
+
elif not self.data.get('geometry'):
|
369
|
+
raise ValueError("Geometry is not present in the feature data")
|
370
|
+
|
371
|
+
elif not isinstance(self.data['geometry'], dict):
|
372
|
+
raise ValueError("Geometry is not a dictionary")
|
373
|
+
|
374
|
+
elif not self.data['geometry'].get('type'):
|
375
|
+
raise ValueError("Geometry type is not present in the feature data")
|
376
|
+
|
377
|
+
elif not self.data['geometry'].get('coordinates'):
|
378
|
+
raise ValueError("Geometry coordinates are not present in the feature data")
|
379
|
+
|
380
|
+
else:
|
381
|
+
return shape(self.data['geometry'])
|
382
|
+
|
383
|
+
|
384
|
+
@geometry.setter
|
385
|
+
def geometry(self, value: object) -> None:
|
386
|
+
"""
|
387
|
+
Set the feature geometry.
|
388
|
+
|
389
|
+
Args:
|
390
|
+
value (object): The geometry to set.
|
391
|
+
|
392
|
+
Raises:
|
393
|
+
ValueError: If geometry type is not supported
|
394
|
+
ValueError: If the geometry has a different type than the layer type
|
395
|
+
ImportError: If shapely is not installed
|
396
|
+
|
397
|
+
Returns:
|
398
|
+
None
|
399
|
+
|
400
|
+
Example:
|
401
|
+
>>> from geobox import GeoboxClient
|
402
|
+
>>> from geobox.feature import Feature
|
403
|
+
>>> from shapely.affinity import translate
|
404
|
+
>>> client = GeoboxClient()
|
405
|
+
>>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
|
406
|
+
>>> feature = layer.get_feature(id=1)
|
407
|
+
>>> geom = feature.geometry
|
408
|
+
>>> geom = translate(geom, 3.0, 0.5) # example change applied to the feature's geometry
|
409
|
+
>>> feature.geometry = geom
|
410
|
+
>>> feature.save()
|
411
|
+
"""
|
412
|
+
try:
|
413
|
+
from shapely.geometry import mapping, Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon
|
414
|
+
except ImportError:
|
415
|
+
raise ImportError(
|
416
|
+
"The 'geometry' extra is required for this function. "
|
417
|
+
"Install it with: pip install pygeobox[geometry]"
|
418
|
+
)
|
419
|
+
|
420
|
+
if not isinstance(value, (Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon)):
|
421
|
+
raise ValueError("Geometry must be a Shapely geometry object")
|
422
|
+
|
423
|
+
elif self.feature_type and value.geom_type != self.feature_type.value:
|
424
|
+
raise ValueError("Geometry must have the same type as the layer type")
|
425
|
+
|
426
|
+
else:
|
427
|
+
self.data['geometry'] = mapping(value)
|
428
|
+
|
429
|
+
|
430
|
+
def transform(self, out_srid: int) -> 'Feature':
|
431
|
+
"""
|
432
|
+
Transform the feature geometry to a new SRID.
|
433
|
+
|
434
|
+
Args:
|
435
|
+
out_srid (int): The target SRID to transform the geometry to (e.g., 4326 for WGS84, 3857 for Web Mercator)
|
436
|
+
|
437
|
+
Returns:
|
438
|
+
Feature: A new Feature instance with transformed geometry.
|
439
|
+
|
440
|
+
Raises:
|
441
|
+
ValueError: If the feature has no geometry or if the transformation fails.
|
442
|
+
ImportError: If pyproj is not installed.
|
443
|
+
|
444
|
+
Example:
|
445
|
+
>>> from geobox import GeoboxClient
|
446
|
+
>>> from geobox.feature import Feature
|
447
|
+
>>> client = GeoboxClient()
|
448
|
+
>>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
|
449
|
+
>>> feature = layer.get_feature(id=1, srid=3857)
|
450
|
+
>>> # Transform from Web Mercator (3857) to WGS84 (4326)
|
451
|
+
>>> transformed = feature.transform(out_srid=4326)
|
452
|
+
>>> transformed.srid # 4326
|
453
|
+
"""
|
454
|
+
try:
|
455
|
+
from pyproj import Transformer
|
456
|
+
from shapely.geometry import Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, mapping
|
457
|
+
except ImportError:
|
458
|
+
raise ImportError(
|
459
|
+
"The 'geometry' extra is required for this function. "
|
460
|
+
"Install it with: pip install pygeobox[geometry]"
|
461
|
+
)
|
462
|
+
|
463
|
+
if not self.data or not self.data.get('geometry'):
|
464
|
+
raise ValueError("Feature geometry is required for transformation")
|
465
|
+
|
466
|
+
# Get the current SRID from the feature or default to 3857 (Web Mercator)
|
467
|
+
current_srid = self.srid or 3857
|
468
|
+
|
469
|
+
# Create transformer
|
470
|
+
transformer = Transformer.from_crs(current_srid, out_srid, always_xy=True)
|
471
|
+
|
472
|
+
# Get the geometry
|
473
|
+
geom = self.geometry
|
474
|
+
|
475
|
+
# Transform coordinates based on geometry type
|
476
|
+
if geom.geom_type == 'Point':
|
477
|
+
x, y = geom.x, geom.y
|
478
|
+
new_x, new_y = transformer.transform(x, y)
|
479
|
+
new_geom = Point(new_x, new_y)
|
480
|
+
elif geom.geom_type == 'LineString':
|
481
|
+
coords = list(geom.coords)
|
482
|
+
new_coords = [transformer.transform(x, y) for x, y in coords]
|
483
|
+
new_geom = LineString(new_coords)
|
484
|
+
elif geom.geom_type == 'Polygon':
|
485
|
+
exterior = [transformer.transform(x, y) for x, y in geom.exterior.coords]
|
486
|
+
interiors = [[transformer.transform(x, y) for x, y in interior.coords] for interior in geom.interiors]
|
487
|
+
new_geom = Polygon(exterior, holes=interiors)
|
488
|
+
elif geom.geom_type == 'MultiPoint':
|
489
|
+
new_geoms = [Point(transformer.transform(point.x, point.y)) for point in geom.geoms]
|
490
|
+
new_geom = MultiPoint(new_geoms)
|
491
|
+
elif geom.geom_type == 'MultiLineString':
|
492
|
+
new_geoms = [LineString([transformer.transform(x, y) for x, y in line.coords]) for line in geom.geoms]
|
493
|
+
new_geom = MultiLineString(new_geoms)
|
494
|
+
elif geom.geom_type == 'MultiPolygon':
|
495
|
+
new_geoms = []
|
496
|
+
for poly in geom.geoms:
|
497
|
+
exterior = [transformer.transform(x, y) for x, y in poly.exterior.coords]
|
498
|
+
interiors = [[transformer.transform(x, y) for x, y in interior.coords] for interior in poly.interiors]
|
499
|
+
new_geoms.append(Polygon(exterior, holes=interiors))
|
500
|
+
new_geom = MultiPolygon(new_geoms)
|
501
|
+
|
502
|
+
# update the feature data
|
503
|
+
self.data['geometry'] = mapping(new_geom)
|
504
|
+
self._srid = out_srid
|
505
|
+
|
506
|
+
return self
|
507
|
+
|
508
|
+
|