pygeobox 1.0.3__py3-none-any.whl → 1.0.4__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/vectorlayer.py CHANGED
@@ -1,1150 +1,1150 @@
1
- from urllib.parse import urljoin
2
- from typing import List, Optional, TYPE_CHECKING, Dict, Union
3
-
4
- from .base import Base
5
- from .utils import clean_data
6
- from .exception import NotFoundError
7
- from .field import Field, FieldType
8
- from .feature import Feature
9
- from .enums import LayerType
10
- from .task import Task
11
- from .enums import InputGeomType, FileOutputFormat
12
- from .version import VectorLayerVersion
13
-
14
-
15
- if TYPE_CHECKING:
16
- from .api import GeoboxClient
17
- from .view import VectorLayerView
18
- from .user import User
19
-
20
- class VectorLayer(Base):
21
- """
22
- A class representing a vector layer in Geobox.
23
-
24
- This class provides functionality to create, manage, and manipulate vector layers.
25
- It supports various operations including CRUD operations on layers, features, and fields,
26
- as well as advanced operations like importing/exporting features and calculating field values.
27
- """
28
- BASE_ENDPOINT = 'vectorLayers/'
29
-
30
- def __init__(self,
31
- api: 'GeoboxClient',
32
- uuid: str,
33
- layer_type: LayerType,
34
- data: Optional[Dict] = {}):
35
- """
36
- Initialize a VectorLayer instance.
37
-
38
- Args:
39
- api (GeoboxClient): The GeoboxClient instance for making requests.
40
- uuid (str): The unique identifier for the layer.
41
- layer_type (LayerType): The type of geometry stored in the layer.
42
- data (Dict, optional): Additional layer metadata and configuration.
43
- """
44
- super().__init__(api=api, uuid=uuid, data=data)
45
- self.layer_type = layer_type if isinstance(layer_type, LayerType) else LayerType(layer_type)
46
-
47
-
48
- def __repr__(self) -> str:
49
- """
50
- Return a string representation of the VectorLayer object.
51
-
52
- Returns:
53
- str: A string representation of the VectorLayer object.
54
- """
55
- return f"VectorLayer(name={self.name}, layer_type={self.layer_type})"
56
-
57
-
58
- @classmethod
59
- def get_vectors(cls, api: 'GeoboxClient', **kwargs) -> Union[List['VectorLayer'], int]:
60
- """
61
- Get a list of vector layers with optional filtering and pagination.
62
-
63
- Args:
64
- api (GeoboxClient): The GeoboxClient instance for making requests.
65
-
66
- Keyword Args:
67
- include_settings (bool): Whether to include layer settings. Default is False.
68
- temporary (bool): Whether to return temporary layers, default is False
69
- q (str): Query filter based on OGC CQL standard. e.g. "field1 LIKE '%GIS%' AND created_at > '2021-01-01'"
70
- 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.
71
- search_fields (str): Comma separated list of fields for searching
72
- 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.
73
- return_count (bool): Whether to return total count. default is False.
74
- skip (int): Number of layers to skip. default is 0.
75
- limit (int): Maximum number of layers to return. default is 10.
76
- user_id (int): Specific user. privileges required.
77
- shared (bool): Whether to return shared layers. default is False.
78
-
79
- Returns:
80
- List[VectorLayer] | int: A list of VectorLayer instances or the layers count if return_count is True.
81
-
82
- Example:
83
- >>> from geobox import GeoboxClient
84
- >>> from geobox.vectorlayer import VectorLayer
85
- >>> client = GeoboxClient()
86
- >>> layers = VectorLayer.get_vectors(api=client,
87
- ... include_settings=True,
88
- ... skip=0,
89
- ... limit=100,
90
- ... return_count=False,
91
- ... search="my_layer",
92
- ... search_fields="name, description",
93
- ... order_by="name",
94
- ... shared=True)
95
- or
96
- >>> layers = client.get_vectors(include_settings=True,
97
- ... skip=0,
98
- ... limit=100,
99
- ... return_count=False,
100
- ... search="my_layer",
101
- ... search_fields="name, description",
102
- ... order_by="name",
103
- ... shared=True)
104
- """
105
- params = {
106
- 'f': 'json',
107
- 'include_settings': kwargs.get('include_settings', False),
108
- 'temporary': kwargs.get('temporary', False),
109
- 'q': kwargs.get('q', None),
110
- 'search': kwargs.get('search', None),
111
- 'search_fields': kwargs.get('search_fields', None),
112
- 'order_by': kwargs.get('order_by', None),
113
- 'return_count': kwargs.get('return_count', False),
114
- 'skip': kwargs.get('skip', 0),
115
- 'limit': kwargs.get('limit', 10),
116
- 'user_id': kwargs.get('user_id', None),
117
- 'shared': kwargs.get('shared', False)
118
- }
119
- return super()._get_list(api=api,
120
- endpoint=cls.BASE_ENDPOINT,
121
- params=params,
122
- factory_func=lambda api, item: VectorLayer(api, item['uuid'], LayerType(item['layer_type']), item))
123
-
124
-
125
- @classmethod
126
- def get_vector(cls, api: 'GeoboxClient', uuid: str, user_id: int = None) -> 'VectorLayer':
127
- """
128
- Get a specific vector layer by its UUID.
129
-
130
- Args:
131
- api (GeoboxClient): The GeoboxClient instance for making requests.
132
- uuid (str): The UUID of the layer to retrieve.
133
- user_id (int, optional): Specific user. privileges required.
134
-
135
- Returns:
136
- VectorLayer: The requested layer instance.
137
-
138
- Raises:
139
- NotFoundError: If the layer with the specified UUID is not found.
140
-
141
- Example:
142
- >>> from geobox import GeoboxClient
143
- >>> from geobox.vectorlayer import VectorLayer
144
- >>> client = GeoboxClient()
145
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
146
- or
147
- >>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
148
- """
149
- params = {
150
- 'f': 'json',
151
- 'user_id': user_id
152
- }
153
- return super()._get_detail(api=api,
154
- endpoint=cls.BASE_ENDPOINT,
155
- uuid=uuid,
156
- params=params,
157
- factory_func=lambda api, item: VectorLayer(api, item['uuid'], LayerType(item['layer_type']), item))
158
-
159
-
160
- @classmethod
161
- def get_vector_by_name(cls, api: 'GeoboxClient', name: str, user_id: int = None) -> Union['VectorLayer', None]:
162
- """
163
- Get a vector layer by name
164
-
165
- Args:
166
- api (GeoboxClient): The GeoboxClient instance for making requests.
167
- name (str): the name of the vector to get
168
- user_id (int, optional): specific user. privileges required.
169
-
170
- Returns:
171
- VectorLayer | None: returns the vector if a vector matches the given name, else None
172
-
173
- Example:
174
- >>> from geobox import GeoboxClient
175
- >>> from geobox.vectorlayer import VectorLayer
176
- >>> client = GeoboxClient()
177
- >>> layer = VectorLayer.get_vector_by_name(client, name='test')
178
- or
179
- >>> layer = client.get_vector_by_name(name='test')
180
- """
181
- layers = cls.get_vectors(api, q=f"name = '{name}'", user_id=user_id)
182
- if layers and layers[0].name == name:
183
- return layers[0]
184
- else:
185
- return None
186
-
187
-
188
- @classmethod
189
- def get_vectors_by_ids(cls, api: 'GeoboxClient', ids: List[int], user_id: int = None, include_settings: bool = False) -> List['VectorLayer']:
190
- """
191
- Get vector layers by their IDs.
192
-
193
- Args:
194
- api (GeoboxClient): The GeoboxClient instance for making requests.
195
- ids (List[int]): The IDs of the layers to retrieve.
196
- user_id (int, optional): Specific user. privileges required.
197
- include_settings (bool, optional): Whether to include the layer settings. default is False.
198
-
199
- Returns:
200
- List[VectorLayer]: The list of VectorLayer instances.
201
-
202
- Example:
203
- >>> from geobox import GeoboxClient
204
- >>> from geobox.vectorlayer import VectorLayer
205
- >>> client = GeoboxClient()
206
- >>> layers = VectorLayer.get_vectors_by_ids(api=client, ids=[1, 2, 3])
207
- or
208
- >>> layers = client.get_vectors_by_ids(ids=[1, 2, 3])
209
- """
210
- params = {
211
- 'ids': ids,
212
- 'user_id': user_id,
213
- 'include_settings': include_settings
214
- }
215
- return super()._get_list_by_ids(api=api,
216
- endpoint=f'{cls.BASE_ENDPOINT}get-layers/',
217
- params=params,
218
- factory_func=lambda api, item: VectorLayer(api, item['uuid'], LayerType(item['layer_type']), item))
219
-
220
-
221
- @classmethod
222
- def create_vector(cls,
223
- api: 'GeoboxClient',
224
- name: str,
225
- layer_type: LayerType,
226
- display_name: str = None,
227
- description: str = None,
228
- has_z: bool = False,
229
- fields: List = None) -> 'VectorLayer':
230
- """
231
- Create a new vector layer.
232
-
233
- Args:
234
- api (GeoboxClient): The GeoboxClient instance for making requests.
235
- name (str): The name of the layer.
236
- layer_type (LayerType): The type of geometry to store.
237
- display_name (str, optional): A human-readable name for the layer. default is None.
238
- description (str, optional): A description of the layer. default is None.
239
- has_z (bool, optional): Whether the layer includes Z coordinates. default is False.
240
- fields (List, optional): List of field definitions for the layer. default is None.
241
-
242
- Returns:
243
- VectorLayer: The newly created layer instance.
244
-
245
- Raises:
246
- ValidationError: If the layer data is invalid.
247
-
248
- Example:
249
- >>> from geobox import GeoboxClient
250
- >>> from geobox.vectorlayer import VectorLayer
251
- >>> client = GeoboxClient()
252
- >>> layer = VectorLayer.create_vector(api=client,
253
- ... name="my_layer",
254
- ... layer_type=LayerType.Point,
255
- ... display_name="My Layer",
256
- ... description="This is a description of my layer",
257
- ... has_z=False,
258
- ... fields=[{"name": "my_field", "datatype": "FieldTypeString"}])
259
- or
260
- >>> layer = client.create_vector(name="my_layer",
261
- ... layer_type=LayerType.Point,
262
- ... display_name="My Layer",
263
- ... description="This is a description of my layer",
264
- ... has_z=False,
265
- ... fields=[{"name": "my_field", "datatype": "FieldTypeString"}])
266
- """
267
- data = {
268
- "name": name,
269
- "layer_type": layer_type.value,
270
- "display_name": display_name,
271
- "description": description,
272
- "has_z": has_z,
273
- "fields": fields
274
- }
275
- return super()._create(api=api,
276
- endpoint=cls.BASE_ENDPOINT,
277
- data=data,
278
- factory_func=lambda api, item: VectorLayer(api, item['uuid'], layer_type, item))
279
-
280
-
281
- def update(self, **kwargs) -> Dict:
282
- """
283
- Update the layer's properties.
284
-
285
- Keyword Args:
286
- name (str): The new name for the layer.
287
- display_name (str): The new display name for the layer.
288
- description (str): The new description for the layer.
289
-
290
- Returns:
291
- Dict: The updated layer data.
292
-
293
- Raises:
294
- ValidationError: If the update data is invalid.
295
-
296
- Example:
297
- >>> from geobox import GeoboxClient
298
- >>> from geobox.vectorlayer import VectorLayer
299
- >>> client = GeoboxClient()
300
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
301
- >>> layer.update(name="new_name")
302
- >>> layer.update(display_name="new_display_name")
303
- >>> layer.update(description="new_description")
304
- """
305
- data = {
306
- "name": kwargs.get('name'),
307
- "display_name": kwargs.get('display_name'),
308
- "description": kwargs.get('description')
309
- }
310
- return super()._update(endpoint=self.endpoint, data=data)
311
-
312
-
313
- def delete(self) -> None:
314
- """
315
- Delete the layer.
316
-
317
- Returns:
318
- None
319
-
320
- Example:
321
- >>> from geobox import GeoboxClient
322
- >>> from geobox.vectorlayer import VectorLayer
323
- >>> client = GeoboxClient()
324
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
325
- >>> layer.delete()
326
- """
327
- super().delete(endpoint=self.endpoint)
328
-
329
-
330
- def make_permanent(self) -> None:
331
- """
332
- Make the layer permanent.
333
- """
334
- endpoint = urljoin(self.endpoint, 'makePermanent/')
335
- self.api.post(endpoint, is_json=False)
336
-
337
-
338
- def share(self, users: List['User']) -> None:
339
- """
340
- Shares the layer with specified users.
341
-
342
- Args:
343
- users (List[User]): The list of user objects to share the layer with.
344
-
345
- Returns:
346
- None
347
-
348
- Example:
349
- >>> from geobox import GeoboxClient
350
- >>> from geobox.vectorlayer import VectorLayer
351
- >>> client = GeoboxClient()
352
- >>> layer = VectorLayer.get_vector(client, uuid="12345678-1234-5678-1234-567812345678")
353
- >>> users = client.search_users(search="John")
354
- >>> layer.share(users=users)
355
- """
356
- super()._share(self.endpoint, users)
357
-
358
-
359
- def unshare(self, users: List['User']) -> None:
360
- """
361
- Unshares the layer with specified users.
362
-
363
- Args:
364
- users (List[User]): The list of user objectss to unshare the layer with.
365
-
366
- Returns:
367
- None
368
-
369
- Example:
370
- >>> from geobox import GeoboxClient
371
- >>> from geobox.vectorlayer import VectorLayer
372
- >>> client = GeoboxClient()
373
- >>> layer = VectorLayer.get_vector(client, uuid="12345678-1234-5678-1234-567812345678")
374
- >>> users = client.search_users(search="John")
375
- >>> layer.unshare(users=users)
376
- """
377
- super()._unshare(self.endpoint, users)
378
-
379
-
380
- def get_shared_users(self, search: str = None, skip: int = 0, limit: int = 10) -> List['User']:
381
- """
382
- Retrieves the list of users the layer is shared with.
383
-
384
- Args:
385
- search (str, optional): The search query.
386
- skip (int, optional): The number of users to skip.
387
- limit (int, optional): The maximum number of users to retrieve.
388
-
389
- Returns:
390
- List[User]: The list of shared users.
391
-
392
- Example:
393
- >>> from geobox import GeoboxClient
394
- >>> from geobox.vectorlayer import VectorLayer
395
- >>> client = GeoboxClient()
396
- >>> layer = VectorLayer.get_vector(client, uuid="12345678-1234-5678-1234-567812345678")
397
- >>> layer.get_shared_users(search='John', skip=0, limit=10)
398
- """
399
- params = {
400
- 'search': search,
401
- 'skip': skip,
402
- 'limit': limit
403
- }
404
- return super()._get_shared_users(self.endpoint, params)
405
-
406
-
407
- def create_version(self, name: str, display_name: str = None, description: str = None) -> 'VectorLayerVersion':
408
- """
409
- Create a version from the layer
410
-
411
- Args:
412
- name (str): the name of the version.
413
- display_name (str, optional): the display name of the version.
414
- description (str, optional): the description of the version.
415
-
416
- Returns:
417
- VectorLayerVersion: the object of the version.
418
-
419
- Example:
420
- >>> from geobox import GeoboxClient
421
- >>> from geobox.vectorlayer import VectoLayer
422
- >>> client = GeoboxClient()
423
- >>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
424
- >>> version = layer.create_version(name="my_version")
425
- """
426
- data = {
427
- "name": name,
428
- "display_name": display_name,
429
- "description": description
430
- }
431
- endpoint = urljoin(self.endpoint, 'versions')
432
- return super()._create(self.api, endpoint, data, factory_func=lambda api, item: VectorLayerVersion(api, item['uuid'], item))
433
-
434
-
435
- @property
436
- def wfs(self) -> str:
437
- """
438
- Get the WFS endpoint for the layer.
439
-
440
- Returns:
441
- str: The WFS endpoint for the layer.
442
-
443
- Example:
444
- >>> from geobox import GeoboxClient
445
- >>> from geobox.vectorlayer import VectorLayer
446
- >>> client = GeoboxClient()
447
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
448
- >>> layer.wfs
449
- """
450
- return f'{self.api.base_url}{self.endpoint}wfs/'
451
-
452
-
453
- def get_fields(self) -> List['Field']:
454
- """
455
- Get all fields in the layer.
456
-
457
- Returns:
458
- List[Field]: A list of Field instances representing the layer's fields.
459
-
460
- Example:
461
- >>> from geobox import GeoboxClient
462
- >>> from geobox.vectorlayer import VectorLayer
463
- >>> client = GeoboxClient()
464
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
465
- >>> fields = layer.get_fields()
466
- """
467
- endpoint = urljoin(self.endpoint, 'fields/')
468
- return super()._get_list(api=self.api,
469
- endpoint=endpoint,
470
- factory_func=lambda api, item: Field(layer=self, data_type=FieldType(item['datatype']), field_id=item['id'], data=item))
471
-
472
-
473
- def get_field(self, field_id: str = None, name: str = None) -> 'Field':
474
- """
475
- Get a specific field by its ID.
476
-
477
- Args:
478
- field_id (str, optional): The ID of the field to retrieve.
479
- name (str, optional): The name of the field to retrieve.
480
-
481
- Returns:
482
- Field: The requested field instance.
483
-
484
- Raises:
485
- ValueError: Either id or name must be provided.
486
- NotFoundError: If the field with the specified ID or name is not found.
487
-
488
- Example:
489
- >>> from geobox import GeoboxClient
490
- >>> from geobox.vectorlayer import VectorLayer
491
- >>> client = GeoboxClient()
492
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
493
- >>> field = layer.get_field(field_id="1")
494
- """
495
- if not (field_id or name):
496
- raise ValueError('Either "field_id" or "name" must be provided')
497
-
498
- if field_id:
499
- field = next((f for f in self.get_fields() if str(f.id) == str(field_id)), None)
500
- elif name:
501
- field = next((f for f in self.get_fields() if f.name == name), None)
502
-
503
- if not field:
504
- if field_id:
505
- raise NotFoundError(f'Field with ID {field_id} not found in layer {self.name}')
506
- elif name:
507
- raise NotFoundError(f'Field with name "{name}" not found in layer {self.name}')
508
-
509
- return field
510
-
511
-
512
- def add_field(self, name: str, data_type: 'FieldType', data: Dict = {}) -> 'Field':
513
- """
514
- Add a new field to the layer.
515
-
516
- Args:
517
- name (str): The name of the new field.
518
- data_type (FieldType): The data type of the new field.
519
- data (Dict): Additional field properties (display_name, description, etc.).
520
-
521
- Returns:
522
- Field: The newly created field instance.
523
-
524
- Raises:
525
- ValidationError: If the field data is invalid.
526
-
527
- Example:
528
- >>> from geobox import GeoboxClient
529
- >>> from geobox.vectorlayer import VectorLayer
530
- >>> client = GeoboxClient()
531
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
532
- >>> field = layer.add_field(name="new_field", data_type=FieldType.String)
533
- """
534
- return Field.create_field(self.api, layer=self, name=name, data_type=data_type, data=data)
535
-
536
-
537
- def calculate_field(self,
538
- target_field: str,
539
- expression: str,
540
- q: str = None,
541
- bbox: List = None,
542
- bbox_srid: int = None,
543
- feature_ids: List = None,
544
- run_async: bool = True,
545
- user_id: int = None) -> 'Task':
546
- """
547
- Calculate values for a field based on an expression.
548
-
549
- Args:
550
- target_field (str): The field to calculate values for.
551
- expression (str): The expression to use for calculation.
552
- q (str, optional): Query to filter features. default is None.
553
- bbox (List, optional): Bounding box to filter features. default is None.
554
- bbox_srid (int, optional): Spatial reference ID for the bounding box. default is None.
555
- feature_ids (List, optional): List of specific feature IDs to include. default is None
556
- run_async (bool, optional): Whether to run the calculation asynchronously. default is True.
557
- user_id (int, optional): Specific user. privileges required.
558
-
559
- Returns:
560
- Task: The task instance of the calculation operation.
561
-
562
- Example:
563
- >>> from geobox import GeoboxClient
564
- >>> from geobox.vectorlayer import VectorLayer
565
- >>> client = GeoboxClient()
566
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
567
- >>> task = layer.calculate_field(target_field="target_field",
568
- ... expression="expression",
569
- ... q="name like 'my_layer'",
570
- ... bbox=[10, 20, 30, 40],
571
- ... bbox_srid=3857,
572
- ... feature_ids=[1, 2, 3],
573
- ... run_async=True)
574
- """
575
- data = clean_data({
576
- "target_field": target_field,
577
- "expression": expression,
578
- "q": q,
579
- "bbox": bbox,
580
- "bbox_srid": bbox_srid,
581
- "feature_ids": feature_ids,
582
- "run_async": run_async,
583
- "user_id": user_id
584
- })
585
-
586
- endpoint = urljoin(self.endpoint, 'calculateField/')
587
- response = self.api.post(endpoint, data, is_json=False)
588
- task = Task.get_task(self.api, response.get('task_uuid'))
589
- return task
590
-
591
-
592
- def get_features(self, **kwargs) -> Union[List['Feature'], int]:
593
- """
594
- Get features from the layer with optional filtering and pagination.
595
-
596
- Keyword Args:
597
- quant_factor (int): Quantization factor. This parameter is only used by topojson encoder and is ignored for other formats. Higher quantizaion value means higher geometry precision. default is 1000000.
598
- skip (int): Number of features to skip. default is 0.
599
- limit (int): Maximum number of features to return. default is 100.
600
- user_id (int): Specific user. privileges required.
601
- 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.
602
- search_fields (str): comma separated list of fields for searching.
603
- skip_geometry (bool): Whether to exclude geometry data. default is False.
604
- return_count (bool): Whether to return total count. default is False.
605
- feature_ids (list): Comma separated list of feature ids which should be filtered.
606
- select_fields (str): comma separated field names which should be included to the result. default is "[ALL]".
607
- skip_fields (str): comma separated field names which should be excluded from the result.
608
- out_srid (int): srid (epsg code) of result features. e.g. 4326. default is 3857.
609
- order_by (str): comma separated list of fields for sorting results [field1 A|D, field2 A|D, …]. e.g. name A, length D. NOTE: "A" denotes ascending order and "D" denotes descending order.
610
- q (str): query filter based on OGC CQL standard. e.g. Name LIKE '%GIS%' AND INTERSECTS(geometry, 'SRID=3857;POLYGON((4901948 2885079, 7049893 2885079, 7049893 4833901, 4901948 4833901, 4901948 2885079))').
611
- bbox (str): Bounding box to filter features by. e.g. [50.275, 35.1195, 51.4459, 36.0416].
612
- bbox_srid (int): srid (epsg code) of bbox. e.g. 4326. default is 3857.
613
-
614
- Returns:
615
- List[Feature] | int: A list of Feature instances or the features count if return_count is True.
616
-
617
- Example:
618
- >>> from geobox import GeoboxClient
619
- >>> from geobox.vectorlayer import VectorLayer
620
- >>> client = GeoboxClient()
621
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
622
- >>> features = layer.get_features(quant_factor=1000000,
623
- ... skip=0,
624
- ... limit=100,
625
- ... skip_geometry=False,
626
- ... return_count=False,
627
- ... select_fields="fclass, osm_id",
628
- ... out_srid=3857,
629
- ... bbox_srid=3857)
630
- """
631
- params = {
632
- 'f': 'json',
633
- 'quant_factor': kwargs.get('quant_factor', 1000000),
634
- 'skip': kwargs.get('skip', 0),
635
- 'limit': kwargs.get('limit', 100),
636
- 'user_id': kwargs.get('user_id', None),
637
- 'search': kwargs.get('search', None),
638
- 'search_fields': kwargs.get('search_fields', None),
639
- 'skip_geometry': kwargs.get('skip_geometry', False),
640
- 'return_count': kwargs.get('return_count', False),
641
- 'feature_ids': kwargs.get('feature_ids', None),
642
- 'select_fields': kwargs.get('select_fields', '[ALL]'),
643
- 'skip_fields': kwargs.get('skip_fields', None),
644
- 'out_srid': kwargs.get('out_srid', 3857),
645
- 'order_by': kwargs.get('order_by', None),
646
- 'q': kwargs.get('q', None),
647
- 'bbox': kwargs.get('bbox', None),
648
- 'bbox_srid': kwargs.get('bbox_srid', 3857)
649
- }
650
-
651
- return super()._get_list(api=self.api,
652
- endpoint=f'{self.endpoint}features/',
653
- params=params,
654
- factory_func=lambda api, item, srid: Feature(self, srid, item),
655
- geojson=True)
656
-
657
-
658
- def get_feature(self, feature_id: int, out_srid: int = Feature.BASE_SRID) -> 'Feature':
659
- """
660
- Get a specific feature by its ID.
661
-
662
- Args:
663
- feature_id (int): The ID of the feature to retrieve.
664
- out_srid (int, optional): Output spatial reference ID. default is 3857.
665
-
666
- Returns:
667
- Feature: The requested feature instance.
668
-
669
- Raises:
670
- NotFoundError: If the feature with the specified ID is not found.
671
-
672
- Example:
673
- >>> from geobox import GeoboxClient
674
- >>> from geobox.vectorlayer import VectorLayer
675
- >>> client = GeoboxClient()
676
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
677
- >>> feature = layer.get_feature(feature_id=1, out_srid=4326)
678
- """
679
- endpoint = f"{self.endpoint}features/{feature_id}"
680
- response = self.api.get(endpoint)
681
- feature = Feature(self, data=response)
682
- if out_srid != Feature.BASE_SRID:
683
- feature.transform(out_srid)
684
-
685
- return feature
686
-
687
-
688
- def create_feature(self, geojson: Dict)-> 'Feature':
689
- """
690
- Create a new feature in the layer.
691
-
692
- Args:
693
- geojson (Dict): The feature data including properties and geometry.
694
-
695
- Returns:
696
- Feature: The newly created feature instance.
697
-
698
- Raises:
699
- ValidationError: If the feature data is invalid.
700
-
701
- Example:
702
- >>> from geobox import GeoboxClient
703
- >>> from geobox.vectorlayer import VectorLayer
704
- >>> client = GeoboxClient()
705
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
706
- >>> geojson = {
707
- ... "type": "Feature",
708
- ... "geometry": {"type": "Point", "coordinates": [10, 20]},
709
- ... "properties": {"name": "My Point"}
710
- ... }
711
- >>> feature = layer.create_feature(geojson=geojson)
712
- """
713
- return Feature.create_feature(self, geojson)
714
-
715
-
716
- def delete_features(self, q: str = None, bbox: List[float] = None, bbox_srid: int = None, feature_ids: List[int] = None,
717
- run_async: bool = True, user_id: int = None) -> 'Task':
718
- """
719
- Delete features from the layer based on specified criteria.
720
-
721
- Args:
722
- q (Optional[str]): Query to filter features to delete.
723
- bbox (Optional[List[float]]): Bounding box to filter features.
724
- bbox_srid (Optional[int]): Spatial reference ID for the bounding box.
725
- feature_ids (Optional[List[int]]): List of specific feature IDs to delete.
726
- run_async (Optional[bool]): Whether to run the deletion asynchronously. default is True.
727
- user_id (Optional[int]): Specific user. privileges required.
728
-
729
- Returns:
730
- Task: The task instance of the deletion operation.
731
-
732
- Raises:
733
- ValidationError: If the deletion parameters are invalid.
734
-
735
- Example:
736
- >>> from geobox import GeoboxClient
737
- >>> from geobox.vectorlayer import VectorLayer
738
- >>> client = GeoboxClient()
739
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
740
- >>> layer.delete_features(q="name like 'my_layer'",
741
- ... bbox=[10, 20, 30, 40],
742
- ... bbox_srid=3857,
743
- ... feature_ids=[1, 2, 3],
744
- ... run_async=True)
745
- """
746
- data = clean_data({
747
- "q": q,
748
- "bbox": bbox,
749
- "bbox_srid": bbox_srid,
750
- "feature_ids": feature_ids,
751
- "run_async": run_async,
752
- "user_id": user_id
753
- })
754
-
755
- endpoint = urljoin(self.endpoint, 'deleteFeatures/')
756
- response = self.api.post(endpoint, data, is_json=False)
757
- task = Task.get_task(self.api, response.get('task_id'))
758
- return task
759
-
760
-
761
- def import_features(self, file_uuid: str, input_geom_type: 'InputGeomType', input_layer_name: str = None, input_dataset: str = None,
762
- user_id: int = None, input_srid: int = None, file_encoding: str = "utf-8",
763
- replace_domain_codes_by_values: bool = False, report_errors: bool = True) -> 'Task':
764
- """
765
- Import features from a file into the layer.
766
-
767
- Args:
768
- file_uuid (str): UUID of the uploaded file to import.
769
- input_geom_type (InputGeomType): Type of geometry in the input file.
770
- input_layer_name (str, optional): Name of the layer in the input file.
771
- input_dataset (str, optional): Name of the dataset in the input file.
772
- user_id (int, optional): Specific user. privileges required.
773
- input_srid (int, optional): Spatial reference ID of the input data.
774
- file_encoding (str, optional): Character encoding of the input file.
775
- replace_domain_codes_by_values (bool, optional): Whether to replace domain codes with values.
776
- report_errors (bool, optional): Whether to report import errors.
777
-
778
- Returns:
779
- Task: The task instance of the import operation.
780
-
781
- Raises:
782
- ValidationError: If the import parameters are invalid.
783
-
784
- Example:
785
- >>> from geobox import GeoboxClient
786
- >>> from geobox.vectorlayer import VectorLayer
787
- >>> client = GeoboxClient()
788
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
789
- >>> task = layer.import_features(file_uuid="12345678-1234-5678-1234-567812345678",
790
- ... input_geom_type=InputGeomType.POINT,
791
- ... input_layer_name="my_layer",
792
- ... input_dataset="my_dataset",
793
- ... input_srid=3857,
794
- ... file_encoding="utf-8",
795
- ... replace_domain_codes_by_values=False,
796
- ... report_errors=True)
797
- """
798
- data = clean_data({
799
- "file_uuid": file_uuid,
800
- "input_layer": input_layer_name,
801
- "input_geom_type": input_geom_type.value if isinstance(input_geom_type, InputGeomType) else input_geom_type,
802
- "replace_domain_codes_by_values": replace_domain_codes_by_values,
803
- "input_dataset": input_dataset,
804
- "user_id": user_id,
805
- "input_srid": input_srid,
806
- "file_encoding": file_encoding,
807
- "report_errors": report_errors
808
- })
809
-
810
- endpoint = urljoin(self.endpoint, 'import/')
811
- response = self.api.post(endpoint, data, is_json=False)
812
- task = Task.get_task(self.api, response.get('task_id'))
813
- return task
814
-
815
-
816
- def export_features(self, out_filename: str, out_format: 'FileOutputFormat', replace_domain_codes_by_values: bool = False,
817
- run_async: bool = True, bbox: List[float] = None, out_srid: int = None, zipped: bool = True,
818
- feature_ids: List[int] = None, bbox_srid: int = None, q: str = None, fields: List[str] = None) -> 'Task':
819
- """
820
- Export features from the layer to a file.
821
-
822
- Args:
823
- out_filename (str): Name of the output file.
824
- out_format (FileOutputFormat): Format of the output file (e.g., 'Shapefile', 'GPKG', 'GeoJSON', 'CSV', 'KML', 'DXF').
825
- replace_domain_codes_by_values (bool, optional): Whether to replace domain codes with values.
826
- run_async (bool, optional): Whether to run the export asynchronously.
827
- bbox (List, optional): Bounding box to filter features.
828
- out_srid (int): Spatial reference ID for the output.
829
- zipped (bool, optional): Whether to compress the output file.
830
- feature_ids (List[int], optional): List of specific feature IDs to export.
831
- bbox_srid (int, optional): Spatial reference ID for the bounding box.
832
- q (str, optional): Query to filter features.
833
- fields (List[str], optional): List of fields to include in the export.
834
-
835
- Returns:
836
- Task: The task instance of the export operation.
837
-
838
- Raises:
839
- ValidationError: If the export parameters are invalid.
840
-
841
- Example:
842
- >>> from geobox import GeoboxClient
843
- >>> from geobox.vectorlayer import VectorLayer
844
- >>> client = GeoboxClient()
845
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
846
- >>> task = layer.export_features(out_filename="output.shp",
847
- ... out_format="shp",
848
- ... replace_domain_codes_by_values=False,
849
- ... run_async=True,
850
- ... bbox=[10, 20, 30, 40],
851
- ... out_srid=3857,
852
- ... zipped=True,
853
- ... feature_ids=[1, 2, 3])
854
- """
855
- data = clean_data({
856
- "replace_domain_codes_by_values": replace_domain_codes_by_values,
857
- "out_format": out_format.value,
858
- "run_async": run_async,
859
- "bbox": bbox,
860
- "out_srid": out_srid,
861
- "zipped": zipped,
862
- "feature_ids": feature_ids,
863
- "bbox_srid": bbox_srid,
864
- "q": q,
865
- "out_filename": out_filename,
866
- "fields": fields
867
- })
868
-
869
- endpoint = urljoin(self.endpoint, 'export/')
870
- response = self.api.post(endpoint, data, is_json=False)
871
- task = Task.get_task(self.api, response.get('task_id'))
872
- return task
873
-
874
-
875
- def create_view(self, name: str, display_name: str = None, description: str = None,
876
- view_filter: str = None, view_extent: Dict = None, view_cols: str = None) -> 'VectorLayerView':
877
- """
878
- Create a view of the vector layer.
879
-
880
- Args:
881
- name (str): The name of the view.
882
- display_name (str, optional): The display name of the view.
883
- description (str, optional): The description of the view.
884
- view_filter (str, optional): The filter for the view.
885
- view_extent (List[float], optional): The extent of the view.
886
- view_cols (str, optional): The columns to include in the view.
887
-
888
- Returns:
889
- VectorLayerView: The created view instance.
890
-
891
- Raises:
892
- ValidationError: If the view parameters are invalid.
893
-
894
- Example:
895
- >>> from geobox import GeoboxClient
896
- >>> from geobox.vectorlayer import VectorLayer
897
- >>> client = GeoboxClient()
898
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
899
- >>> view = layer.create_view(name="my_view",
900
- ... display_name="My View",
901
- ... description="This is a view of my layer",
902
- ... view_filter="province_name = 'Tehran'",
903
- ... view_extent=[10, 20, 30, 40],
904
- ... view_cols="[ALL]")
905
- """
906
- from .view import VectorLayerView
907
-
908
- data = {
909
- "name": name,
910
- "display_name": display_name,
911
- "description": description,
912
- "view_filter": view_filter,
913
- "view_extent": view_extent,
914
- "view_cols": view_cols
915
- }
916
-
917
- return super()._create(api=self.api,
918
- endpoint=f'{self.endpoint}views/',
919
- data=data,
920
- factory_func=lambda api, item: VectorLayerView(api, item['uuid'], self.layer_type, item))
921
-
922
-
923
- def get_tile(self, x: int, y: int, z: int) -> str:
924
- """
925
- Get a vector tile for the layer.
926
-
927
- Args:
928
- x (int): X coordinate of the tile.
929
- y (int): Y coordinate of the tile.
930
- z (int): Zoom level of the tile.
931
-
932
- Returns:
933
- str: The vector tile data.
934
-
935
- Example:
936
- >>> from geobox import GeoboxClient
937
- >>> from geobox.vectorlayer import VectorLayer
938
- >>> client = GeoboxClient()
939
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
940
- >>> tile = layer.get_tile(x=10, y=20, z=1)
941
- """
942
- endpoint = f'{self.BASE_ENDPOINT}{self.endpoint}tiles/{z}/{x}/{y}.pbf'
943
- return endpoint
944
-
945
-
946
- def get_tile_json(self) -> Dict:
947
- """
948
- Get the vector tile JSON configuration for the layer.
949
-
950
- Returns:
951
- Dict: The vector tile JSON configuration.
952
-
953
- Example:
954
- >>> from geobox import GeoboxClient
955
- >>> from geobox.vectorlayer import VectorLayer
956
- >>> client = GeoboxClient()
957
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
958
- >>> tile_json = layer.get_tile_json()
959
- """
960
- endpoint = urljoin(self.endpoint, 'tilejson.json')
961
- return self.api.get(endpoint)
962
-
963
-
964
- @property
965
- def settings(self) -> Dict:
966
- """
967
- Get the layer's settings.
968
-
969
- Returns:
970
- Dict: The layer settings.
971
-
972
- Example:
973
- >>> from geobox import GeoboxClient
974
- >>> from geobox.vectorlayer import VectorLayer
975
- >>> client = GeoboxClient()
976
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
977
- >>> setting = layer.setting
978
- """
979
- return super()._get_settings(endpoint=self.endpoint)
980
-
981
-
982
- def set_settings(self, **kwargs) -> Dict:
983
- """
984
- Set the settings of the Vector Layer.
985
-
986
- Keyword Args:
987
- title_field (str): The field to use as the title.
988
- domain_display_type (str): The type of domain display.
989
- allow_export (bool): Whether to allow export.
990
- editable (bool): Whether to allow editing.
991
- edit_geometry (bool): Whether to allow editing the geometry.
992
- editable_attributes (str): The attributes to allow editing.
993
- allow_insert (bool): Whether to allow inserting.
994
- allow_delete (bool): Whether to allow deleting.
995
- min_zoom (int): The minimum zoom level.
996
- max_zoom (int): The maximum zoom level.
997
- max_features (int): The maximum number of features.
998
- filter_features (bool): Whether to filter features.
999
- fields (List[str]): The fields to include in the layer.
1000
- use_cache (bool): Whether to use caching.
1001
- cache_until_zoom (int): The zoom level to cache until.
1002
-
1003
- Returns:
1004
- Dict: The updated settings.
1005
-
1006
- Raises:
1007
- ValidationError: If the settings data is invalid.
1008
-
1009
- Example:
1010
- >>> from geobox import GeoboxClient
1011
- >>> from geobox.vectorlayer import VectorLayer
1012
- >>> client = GeoboxClient()
1013
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
1014
- >>> layer.set_settings(title_field="name",
1015
- ... domain_display_type="Value",
1016
- ... allow_export=True,
1017
- ... editable=True,
1018
- ... edit_geometry=True,
1019
- ... editable_attributes="[ALL]",
1020
- ... allow_insert=True,
1021
- ... allow_delete=True,
1022
- ... min_zoom=0,
1023
- ... max_zoom=24,
1024
- ... max_features=65536,
1025
- ... filter_features=True,
1026
- ... fields=["id"],
1027
- ... use_cache=True,
1028
- ... cache_until_zoom=17)
1029
- """
1030
- general_settings = {'title_field', 'domain_display_type', 'allow_export'}
1031
- edit_settings = {'editable', 'edit_geometry', 'editable_attributes', 'allow_insert', 'allow_delete'}
1032
- tile_settings = {'min_zoom', 'max_zoom', 'max_features', 'filter_features', 'fields', 'use_cache', 'cache_until_zoom'}
1033
-
1034
- settings = {
1035
- 'general_settings': {},
1036
- 'edit_settings': {},
1037
- 'tile_settings': {}
1038
- }
1039
-
1040
- for key, value in kwargs.items():
1041
- if key in general_settings:
1042
- settings['general_settings'][key] = value
1043
- elif key in edit_settings:
1044
- settings['edit_settings'][key] = value
1045
- elif key in tile_settings:
1046
- settings['tile_settings'][key] = value
1047
-
1048
- return super()._set_settings(endpoint=self.endpoint, data=settings)
1049
-
1050
-
1051
- def seed_cache(self, from_zoom: int = None, to_zoom: int = None, ignore_cache: bool = False, workers: int = 1, user_id: int = None) -> List['Task']:
1052
- """
1053
- Seed the cache for the layer.
1054
-
1055
- Args:
1056
- from_zoom (int, optional): The zoom level to start caching from.
1057
- to_zoom (int, optional): The zoom level to stop caching at.
1058
- ignore_cache (bool, optional): Whether to ignore the cache. default is False.
1059
- workers (int, optional): The number of workers to use. default is 1.
1060
- user_id (int, optional): Specific user. privileges required.
1061
-
1062
- Returns:
1063
- List[Task]: The task instance of the cache seeding operation.
1064
-
1065
- Example:
1066
- >>> from geobox import GeoboxClient
1067
- >>> from geobox.vectorlayer import VectorLayer
1068
- >>> client = GeoboxClient()
1069
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
1070
- >>> task = layer.seed_cache(from_zoom=0, to_zoom=10, ignore_cache=False, workers=1)
1071
- """
1072
- data = {
1073
- 'from_zoom': from_zoom,
1074
- 'to_zoom': to_zoom,
1075
- 'ignore_cache': ignore_cache,
1076
- 'workers': workers,
1077
- 'user_id': user_id
1078
- }
1079
- return super()._seed_cache(endpoint=self.endpoint, data=data)
1080
-
1081
-
1082
- def clear_cache(self) -> None:
1083
- """
1084
- Clear the layer's cache.
1085
-
1086
- Returns:
1087
- None
1088
-
1089
- Example:
1090
- >>> from geobox import GeoboxClient
1091
- >>> from geobox.vectorlayer import VectorLayer
1092
- >>> client = GeoboxClient()
1093
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
1094
- >>> layer.clear_cache()
1095
- """
1096
- super()._clear_cache(endpoint=self.endpoint)
1097
-
1098
-
1099
- @property
1100
- def cache_size(self) -> int:
1101
- """
1102
- Get the size of the layer's cache.
1103
-
1104
- Returns:
1105
- int: The size of the layer's cache.
1106
-
1107
- Example:
1108
- >>> from geobox import GeoboxClient
1109
- >>> from geobox.vectorlayer import VectorLayer
1110
- >>> client = GeoboxClient()
1111
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
1112
- >>> layer.cache_size
1113
- """
1114
- return super()._cache_size(endpoint=self.endpoint)
1115
-
1116
-
1117
- def update_stats(self) -> None:
1118
- """
1119
- Update the layer's statistics.
1120
-
1121
- Returns:
1122
- None
1123
-
1124
- Example:
1125
- >>> from geobox import GeoboxClient
1126
- >>> from geobox.vectorlayer import VectorLayer
1127
- >>> client = GeoboxClient()
1128
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
1129
- >>> layer.update_stats()
1130
- """
1131
- endpoint = urljoin(self.endpoint, 'updateStats/')
1132
- return self.api.post(endpoint)
1133
-
1134
-
1135
- def prune_edited_areas(self) -> None:
1136
- """
1137
- Prune edited areas. This method eliminates edited areas when there are too many of them. Cache builder uses this edited areas for partial cache generating.
1138
-
1139
- Returns:
1140
- None
1141
-
1142
- Example:
1143
- >>> from geobox import GeoboxClient
1144
- >>> from geobox.vectorlayer import VectorLayer
1145
- >>> client = GeoboxClient()
1146
- >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
1147
- >>> layer.prune_edited_areas()
1148
- """
1149
- endpoint = urljoin(self.endpoint, 'prune/')
1
+ from urllib.parse import urljoin
2
+ from typing import List, Optional, TYPE_CHECKING, Dict, Union
3
+
4
+ from .base import Base
5
+ from .utils import clean_data
6
+ from .exception import NotFoundError
7
+ from .field import Field, FieldType
8
+ from .feature import Feature
9
+ from .enums import LayerType
10
+ from .task import Task
11
+ from .enums import InputGeomType, FileOutputFormat
12
+ from .version import VectorLayerVersion
13
+
14
+
15
+ if TYPE_CHECKING:
16
+ from .api import GeoboxClient
17
+ from .view import VectorLayerView
18
+ from .user import User
19
+
20
+ class VectorLayer(Base):
21
+ """
22
+ A class representing a vector layer in Geobox.
23
+
24
+ This class provides functionality to create, manage, and manipulate vector layers.
25
+ It supports various operations including CRUD operations on layers, features, and fields,
26
+ as well as advanced operations like importing/exporting features and calculating field values.
27
+ """
28
+ BASE_ENDPOINT = 'vectorLayers/'
29
+
30
+ def __init__(self,
31
+ api: 'GeoboxClient',
32
+ uuid: str,
33
+ layer_type: LayerType,
34
+ data: Optional[Dict] = {}):
35
+ """
36
+ Initialize a VectorLayer instance.
37
+
38
+ Args:
39
+ api (GeoboxClient): The GeoboxClient instance for making requests.
40
+ uuid (str): The unique identifier for the layer.
41
+ layer_type (LayerType): The type of geometry stored in the layer.
42
+ data (Dict, optional): Additional layer metadata and configuration.
43
+ """
44
+ super().__init__(api=api, uuid=uuid, data=data)
45
+ self.layer_type = layer_type if isinstance(layer_type, LayerType) else LayerType(layer_type)
46
+
47
+
48
+ def __repr__(self) -> str:
49
+ """
50
+ Return a string representation of the VectorLayer object.
51
+
52
+ Returns:
53
+ str: A string representation of the VectorLayer object.
54
+ """
55
+ return f"VectorLayer(name={self.name}, layer_type={self.layer_type})"
56
+
57
+
58
+ @classmethod
59
+ def get_vectors(cls, api: 'GeoboxClient', **kwargs) -> Union[List['VectorLayer'], int]:
60
+ """
61
+ Get a list of vector layers with optional filtering and pagination.
62
+
63
+ Args:
64
+ api (GeoboxClient): The GeoboxClient instance for making requests.
65
+
66
+ Keyword Args:
67
+ include_settings (bool): Whether to include layer settings. Default is False.
68
+ temporary (bool): Whether to return temporary layers, default is False
69
+ q (str): Query filter based on OGC CQL standard. e.g. "field1 LIKE '%GIS%' AND created_at > '2021-01-01'"
70
+ 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.
71
+ search_fields (str): Comma separated list of fields for searching
72
+ 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.
73
+ return_count (bool): Whether to return total count. default is False.
74
+ skip (int): Number of layers to skip. default is 0.
75
+ limit (int): Maximum number of layers to return. default is 10.
76
+ user_id (int): Specific user. privileges required.
77
+ shared (bool): Whether to return shared layers. default is False.
78
+
79
+ Returns:
80
+ List[VectorLayer] | int: A list of VectorLayer instances or the layers count if return_count is True.
81
+
82
+ Example:
83
+ >>> from geobox import GeoboxClient
84
+ >>> from geobox.vectorlayer import VectorLayer
85
+ >>> client = GeoboxClient()
86
+ >>> layers = VectorLayer.get_vectors(api=client,
87
+ ... include_settings=True,
88
+ ... skip=0,
89
+ ... limit=100,
90
+ ... return_count=False,
91
+ ... search="my_layer",
92
+ ... search_fields="name, description",
93
+ ... order_by="name",
94
+ ... shared=True)
95
+ or
96
+ >>> layers = client.get_vectors(include_settings=True,
97
+ ... skip=0,
98
+ ... limit=100,
99
+ ... return_count=False,
100
+ ... search="my_layer",
101
+ ... search_fields="name, description",
102
+ ... order_by="name",
103
+ ... shared=True)
104
+ """
105
+ params = {
106
+ 'f': 'json',
107
+ 'include_settings': kwargs.get('include_settings', False),
108
+ 'temporary': kwargs.get('temporary', False),
109
+ 'q': kwargs.get('q', None),
110
+ 'search': kwargs.get('search', None),
111
+ 'search_fields': kwargs.get('search_fields', None),
112
+ 'order_by': kwargs.get('order_by', None),
113
+ 'return_count': kwargs.get('return_count', False),
114
+ 'skip': kwargs.get('skip', 0),
115
+ 'limit': kwargs.get('limit', 10),
116
+ 'user_id': kwargs.get('user_id', None),
117
+ 'shared': kwargs.get('shared', False)
118
+ }
119
+ return super()._get_list(api=api,
120
+ endpoint=cls.BASE_ENDPOINT,
121
+ params=params,
122
+ factory_func=lambda api, item: VectorLayer(api, item['uuid'], LayerType(item['layer_type']), item))
123
+
124
+
125
+ @classmethod
126
+ def get_vector(cls, api: 'GeoboxClient', uuid: str, user_id: int = None) -> 'VectorLayer':
127
+ """
128
+ Get a specific vector layer by its UUID.
129
+
130
+ Args:
131
+ api (GeoboxClient): The GeoboxClient instance for making requests.
132
+ uuid (str): The UUID of the layer to retrieve.
133
+ user_id (int, optional): Specific user. privileges required.
134
+
135
+ Returns:
136
+ VectorLayer: The requested layer instance.
137
+
138
+ Raises:
139
+ NotFoundError: If the layer with the specified UUID is not found.
140
+
141
+ Example:
142
+ >>> from geobox import GeoboxClient
143
+ >>> from geobox.vectorlayer import VectorLayer
144
+ >>> client = GeoboxClient()
145
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
146
+ or
147
+ >>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
148
+ """
149
+ params = {
150
+ 'f': 'json',
151
+ 'user_id': user_id
152
+ }
153
+ return super()._get_detail(api=api,
154
+ endpoint=cls.BASE_ENDPOINT,
155
+ uuid=uuid,
156
+ params=params,
157
+ factory_func=lambda api, item: VectorLayer(api, item['uuid'], LayerType(item['layer_type']), item))
158
+
159
+
160
+ @classmethod
161
+ def get_vector_by_name(cls, api: 'GeoboxClient', name: str, user_id: int = None) -> Union['VectorLayer', None]:
162
+ """
163
+ Get a vector layer by name
164
+
165
+ Args:
166
+ api (GeoboxClient): The GeoboxClient instance for making requests.
167
+ name (str): the name of the vector to get
168
+ user_id (int, optional): specific user. privileges required.
169
+
170
+ Returns:
171
+ VectorLayer | None: returns the vector if a vector matches the given name, else None
172
+
173
+ Example:
174
+ >>> from geobox import GeoboxClient
175
+ >>> from geobox.vectorlayer import VectorLayer
176
+ >>> client = GeoboxClient()
177
+ >>> layer = VectorLayer.get_vector_by_name(client, name='test')
178
+ or
179
+ >>> layer = client.get_vector_by_name(name='test')
180
+ """
181
+ layers = cls.get_vectors(api, q=f"name = '{name}'", user_id=user_id)
182
+ if layers and layers[0].name == name:
183
+ return layers[0]
184
+ else:
185
+ return None
186
+
187
+
188
+ @classmethod
189
+ def get_vectors_by_ids(cls, api: 'GeoboxClient', ids: List[int], user_id: int = None, include_settings: bool = False) -> List['VectorLayer']:
190
+ """
191
+ Get vector layers by their IDs.
192
+
193
+ Args:
194
+ api (GeoboxClient): The GeoboxClient instance for making requests.
195
+ ids (List[int]): The IDs of the layers to retrieve.
196
+ user_id (int, optional): Specific user. privileges required.
197
+ include_settings (bool, optional): Whether to include the layer settings. default is False.
198
+
199
+ Returns:
200
+ List[VectorLayer]: The list of VectorLayer instances.
201
+
202
+ Example:
203
+ >>> from geobox import GeoboxClient
204
+ >>> from geobox.vectorlayer import VectorLayer
205
+ >>> client = GeoboxClient()
206
+ >>> layers = VectorLayer.get_vectors_by_ids(api=client, ids=[1, 2, 3])
207
+ or
208
+ >>> layers = client.get_vectors_by_ids(ids=[1, 2, 3])
209
+ """
210
+ params = {
211
+ 'ids': ids,
212
+ 'user_id': user_id,
213
+ 'include_settings': include_settings
214
+ }
215
+ return super()._get_list_by_ids(api=api,
216
+ endpoint=f'{cls.BASE_ENDPOINT}get-layers/',
217
+ params=params,
218
+ factory_func=lambda api, item: VectorLayer(api, item['uuid'], LayerType(item['layer_type']), item))
219
+
220
+
221
+ @classmethod
222
+ def create_vector(cls,
223
+ api: 'GeoboxClient',
224
+ name: str,
225
+ layer_type: LayerType,
226
+ display_name: str = None,
227
+ description: str = None,
228
+ has_z: bool = False,
229
+ fields: List = None) -> 'VectorLayer':
230
+ """
231
+ Create a new vector layer.
232
+
233
+ Args:
234
+ api (GeoboxClient): The GeoboxClient instance for making requests.
235
+ name (str): The name of the layer.
236
+ layer_type (LayerType): The type of geometry to store.
237
+ display_name (str, optional): A human-readable name for the layer. default is None.
238
+ description (str, optional): A description of the layer. default is None.
239
+ has_z (bool, optional): Whether the layer includes Z coordinates. default is False.
240
+ fields (List, optional): List of field definitions for the layer. default is None.
241
+
242
+ Returns:
243
+ VectorLayer: The newly created layer instance.
244
+
245
+ Raises:
246
+ ValidationError: If the layer data is invalid.
247
+
248
+ Example:
249
+ >>> from geobox import GeoboxClient
250
+ >>> from geobox.vectorlayer import VectorLayer
251
+ >>> client = GeoboxClient()
252
+ >>> layer = VectorLayer.create_vector(api=client,
253
+ ... name="my_layer",
254
+ ... layer_type=LayerType.Point,
255
+ ... display_name="My Layer",
256
+ ... description="This is a description of my layer",
257
+ ... has_z=False,
258
+ ... fields=[{"name": "my_field", "datatype": "FieldTypeString"}])
259
+ or
260
+ >>> layer = client.create_vector(name="my_layer",
261
+ ... layer_type=LayerType.Point,
262
+ ... display_name="My Layer",
263
+ ... description="This is a description of my layer",
264
+ ... has_z=False,
265
+ ... fields=[{"name": "my_field", "datatype": "FieldTypeString"}])
266
+ """
267
+ data = {
268
+ "name": name,
269
+ "layer_type": layer_type.value,
270
+ "display_name": display_name,
271
+ "description": description,
272
+ "has_z": has_z,
273
+ "fields": fields
274
+ }
275
+ return super()._create(api=api,
276
+ endpoint=cls.BASE_ENDPOINT,
277
+ data=data,
278
+ factory_func=lambda api, item: VectorLayer(api, item['uuid'], layer_type, item))
279
+
280
+
281
+ def update(self, **kwargs) -> Dict:
282
+ """
283
+ Update the layer's properties.
284
+
285
+ Keyword Args:
286
+ name (str): The new name for the layer.
287
+ display_name (str): The new display name for the layer.
288
+ description (str): The new description for the layer.
289
+
290
+ Returns:
291
+ Dict: The updated layer data.
292
+
293
+ Raises:
294
+ ValidationError: If the update data is invalid.
295
+
296
+ Example:
297
+ >>> from geobox import GeoboxClient
298
+ >>> from geobox.vectorlayer import VectorLayer
299
+ >>> client = GeoboxClient()
300
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
301
+ >>> layer.update(name="new_name")
302
+ >>> layer.update(display_name="new_display_name")
303
+ >>> layer.update(description="new_description")
304
+ """
305
+ data = {
306
+ "name": kwargs.get('name'),
307
+ "display_name": kwargs.get('display_name'),
308
+ "description": kwargs.get('description')
309
+ }
310
+ return super()._update(endpoint=self.endpoint, data=data)
311
+
312
+
313
+ def delete(self) -> None:
314
+ """
315
+ Delete the layer.
316
+
317
+ Returns:
318
+ None
319
+
320
+ Example:
321
+ >>> from geobox import GeoboxClient
322
+ >>> from geobox.vectorlayer import VectorLayer
323
+ >>> client = GeoboxClient()
324
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
325
+ >>> layer.delete()
326
+ """
327
+ super().delete(endpoint=self.endpoint)
328
+
329
+
330
+ def make_permanent(self) -> None:
331
+ """
332
+ Make the layer permanent.
333
+ """
334
+ endpoint = urljoin(self.endpoint, 'makePermanent/')
335
+ self.api.post(endpoint, is_json=False)
336
+
337
+
338
+ def share(self, users: List['User']) -> None:
339
+ """
340
+ Shares the layer with specified users.
341
+
342
+ Args:
343
+ users (List[User]): The list of user objects to share the layer with.
344
+
345
+ Returns:
346
+ None
347
+
348
+ Example:
349
+ >>> from geobox import GeoboxClient
350
+ >>> from geobox.vectorlayer import VectorLayer
351
+ >>> client = GeoboxClient()
352
+ >>> layer = VectorLayer.get_vector(client, uuid="12345678-1234-5678-1234-567812345678")
353
+ >>> users = client.search_users(search="John")
354
+ >>> layer.share(users=users)
355
+ """
356
+ super()._share(self.endpoint, users)
357
+
358
+
359
+ def unshare(self, users: List['User']) -> None:
360
+ """
361
+ Unshares the layer with specified users.
362
+
363
+ Args:
364
+ users (List[User]): The list of user objectss to unshare the layer with.
365
+
366
+ Returns:
367
+ None
368
+
369
+ Example:
370
+ >>> from geobox import GeoboxClient
371
+ >>> from geobox.vectorlayer import VectorLayer
372
+ >>> client = GeoboxClient()
373
+ >>> layer = VectorLayer.get_vector(client, uuid="12345678-1234-5678-1234-567812345678")
374
+ >>> users = client.search_users(search="John")
375
+ >>> layer.unshare(users=users)
376
+ """
377
+ super()._unshare(self.endpoint, users)
378
+
379
+
380
+ def get_shared_users(self, search: str = None, skip: int = 0, limit: int = 10) -> List['User']:
381
+ """
382
+ Retrieves the list of users the layer is shared with.
383
+
384
+ Args:
385
+ search (str, optional): The search query.
386
+ skip (int, optional): The number of users to skip.
387
+ limit (int, optional): The maximum number of users to retrieve.
388
+
389
+ Returns:
390
+ List[User]: The list of shared users.
391
+
392
+ Example:
393
+ >>> from geobox import GeoboxClient
394
+ >>> from geobox.vectorlayer import VectorLayer
395
+ >>> client = GeoboxClient()
396
+ >>> layer = VectorLayer.get_vector(client, uuid="12345678-1234-5678-1234-567812345678")
397
+ >>> layer.get_shared_users(search='John', skip=0, limit=10)
398
+ """
399
+ params = {
400
+ 'search': search,
401
+ 'skip': skip,
402
+ 'limit': limit
403
+ }
404
+ return super()._get_shared_users(self.endpoint, params)
405
+
406
+
407
+ def create_version(self, name: str, display_name: str = None, description: str = None) -> 'VectorLayerVersion':
408
+ """
409
+ Create a version from the layer
410
+
411
+ Args:
412
+ name (str): the name of the version.
413
+ display_name (str, optional): the display name of the version.
414
+ description (str, optional): the description of the version.
415
+
416
+ Returns:
417
+ VectorLayerVersion: the object of the version.
418
+
419
+ Example:
420
+ >>> from geobox import GeoboxClient
421
+ >>> from geobox.vectorlayer import VectoLayer
422
+ >>> client = GeoboxClient()
423
+ >>> layer = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
424
+ >>> version = layer.create_version(name="my_version")
425
+ """
426
+ data = {
427
+ "name": name,
428
+ "display_name": display_name,
429
+ "description": description
430
+ }
431
+ endpoint = urljoin(self.endpoint, 'versions')
432
+ return super()._create(self.api, endpoint, data, factory_func=lambda api, item: VectorLayerVersion(api, item['uuid'], item))
433
+
434
+
435
+ @property
436
+ def wfs(self) -> str:
437
+ """
438
+ Get the WFS endpoint for the layer.
439
+
440
+ Returns:
441
+ str: The WFS endpoint for the layer.
442
+
443
+ Example:
444
+ >>> from geobox import GeoboxClient
445
+ >>> from geobox.vectorlayer import VectorLayer
446
+ >>> client = GeoboxClient()
447
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
448
+ >>> layer.wfs
449
+ """
450
+ return f'{self.api.base_url}{self.endpoint}wfs/'
451
+
452
+
453
+ def get_fields(self) -> List['Field']:
454
+ """
455
+ Get all fields in the layer.
456
+
457
+ Returns:
458
+ List[Field]: A list of Field instances representing the layer's fields.
459
+
460
+ Example:
461
+ >>> from geobox import GeoboxClient
462
+ >>> from geobox.vectorlayer import VectorLayer
463
+ >>> client = GeoboxClient()
464
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
465
+ >>> fields = layer.get_fields()
466
+ """
467
+ endpoint = urljoin(self.endpoint, 'fields/')
468
+ return super()._get_list(api=self.api,
469
+ endpoint=endpoint,
470
+ factory_func=lambda api, item: Field(layer=self, data_type=FieldType(item['datatype']), field_id=item['id'], data=item))
471
+
472
+
473
+ def get_field(self, field_id: str = None, name: str = None) -> 'Field':
474
+ """
475
+ Get a specific field by its ID.
476
+
477
+ Args:
478
+ field_id (str, optional): The ID of the field to retrieve.
479
+ name (str, optional): The name of the field to retrieve.
480
+
481
+ Returns:
482
+ Field: The requested field instance.
483
+
484
+ Raises:
485
+ ValueError: Either id or name must be provided.
486
+ NotFoundError: If the field with the specified ID or name is not found.
487
+
488
+ Example:
489
+ >>> from geobox import GeoboxClient
490
+ >>> from geobox.vectorlayer import VectorLayer
491
+ >>> client = GeoboxClient()
492
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
493
+ >>> field = layer.get_field(field_id="1")
494
+ """
495
+ if not (field_id or name):
496
+ raise ValueError('Either "field_id" or "name" must be provided')
497
+
498
+ if field_id:
499
+ field = next((f for f in self.get_fields() if str(f.id) == str(field_id)), None)
500
+ elif name:
501
+ field = next((f for f in self.get_fields() if f.name == name), None)
502
+
503
+ if not field:
504
+ if field_id:
505
+ raise NotFoundError(f'Field with ID {field_id} not found in layer {self.name}')
506
+ elif name:
507
+ raise NotFoundError(f'Field with name "{name}" not found in layer {self.name}')
508
+
509
+ return field
510
+
511
+
512
+ def add_field(self, name: str, data_type: 'FieldType', data: Dict = {}) -> 'Field':
513
+ """
514
+ Add a new field to the layer.
515
+
516
+ Args:
517
+ name (str): The name of the new field.
518
+ data_type (FieldType): The data type of the new field.
519
+ data (Dict): Additional field properties (display_name, description, etc.).
520
+
521
+ Returns:
522
+ Field: The newly created field instance.
523
+
524
+ Raises:
525
+ ValidationError: If the field data is invalid.
526
+
527
+ Example:
528
+ >>> from geobox import GeoboxClient
529
+ >>> from geobox.vectorlayer import VectorLayer
530
+ >>> client = GeoboxClient()
531
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
532
+ >>> field = layer.add_field(name="new_field", data_type=FieldType.String)
533
+ """
534
+ return Field.create_field(self.api, layer=self, name=name, data_type=data_type, data=data)
535
+
536
+
537
+ def calculate_field(self,
538
+ target_field: str,
539
+ expression: str,
540
+ q: str = None,
541
+ bbox: List = None,
542
+ bbox_srid: int = None,
543
+ feature_ids: List = None,
544
+ run_async: bool = True,
545
+ user_id: int = None) -> 'Task':
546
+ """
547
+ Calculate values for a field based on an expression.
548
+
549
+ Args:
550
+ target_field (str): The field to calculate values for.
551
+ expression (str): The expression to use for calculation.
552
+ q (str, optional): Query to filter features. default is None.
553
+ bbox (List, optional): Bounding box to filter features. default is None.
554
+ bbox_srid (int, optional): Spatial reference ID for the bounding box. default is None.
555
+ feature_ids (List, optional): List of specific feature IDs to include. default is None
556
+ run_async (bool, optional): Whether to run the calculation asynchronously. default is True.
557
+ user_id (int, optional): Specific user. privileges required.
558
+
559
+ Returns:
560
+ Task: The task instance of the calculation operation.
561
+
562
+ Example:
563
+ >>> from geobox import GeoboxClient
564
+ >>> from geobox.vectorlayer import VectorLayer
565
+ >>> client = GeoboxClient()
566
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
567
+ >>> task = layer.calculate_field(target_field="target_field",
568
+ ... expression="expression",
569
+ ... q="name like 'my_layer'",
570
+ ... bbox=[10, 20, 30, 40],
571
+ ... bbox_srid=3857,
572
+ ... feature_ids=[1, 2, 3],
573
+ ... run_async=True)
574
+ """
575
+ data = clean_data({
576
+ "target_field": target_field,
577
+ "expression": expression,
578
+ "q": q,
579
+ "bbox": bbox,
580
+ "bbox_srid": bbox_srid,
581
+ "feature_ids": feature_ids,
582
+ "run_async": run_async,
583
+ "user_id": user_id
584
+ })
585
+
586
+ endpoint = urljoin(self.endpoint, 'calculateField/')
587
+ response = self.api.post(endpoint, data, is_json=False)
588
+ task = Task.get_task(self.api, response.get('task_uuid'))
589
+ return task
590
+
591
+
592
+ def get_features(self, **kwargs) -> Union[List['Feature'], int]:
593
+ """
594
+ Get features from the layer with optional filtering and pagination.
595
+
596
+ Keyword Args:
597
+ quant_factor (int): Quantization factor. This parameter is only used by topojson encoder and is ignored for other formats. Higher quantizaion value means higher geometry precision. default is 1000000.
598
+ skip (int): Number of features to skip. default is 0.
599
+ limit (int): Maximum number of features to return. default is 100.
600
+ user_id (int): Specific user. privileges required.
601
+ 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.
602
+ search_fields (str): comma separated list of fields for searching.
603
+ skip_geometry (bool): Whether to exclude geometry data. default is False.
604
+ return_count (bool): Whether to return total count. default is False.
605
+ feature_ids (list): Comma separated list of feature ids which should be filtered.
606
+ select_fields (str): comma separated field names which should be included to the result. default is "[ALL]".
607
+ skip_fields (str): comma separated field names which should be excluded from the result.
608
+ out_srid (int): srid (epsg code) of result features. e.g. 4326. default is 3857.
609
+ order_by (str): comma separated list of fields for sorting results [field1 A|D, field2 A|D, …]. e.g. name A, length D. NOTE: "A" denotes ascending order and "D" denotes descending order.
610
+ q (str): query filter based on OGC CQL standard. e.g. Name LIKE '%GIS%' AND INTERSECTS(geometry, 'SRID=3857;POLYGON((4901948 2885079, 7049893 2885079, 7049893 4833901, 4901948 4833901, 4901948 2885079))').
611
+ bbox (str): Bounding box to filter features by. e.g. [50.275, 35.1195, 51.4459, 36.0416].
612
+ bbox_srid (int): srid (epsg code) of bbox. e.g. 4326. default is 3857.
613
+
614
+ Returns:
615
+ List[Feature] | int: A list of Feature instances or the features count if return_count is True.
616
+
617
+ Example:
618
+ >>> from geobox import GeoboxClient
619
+ >>> from geobox.vectorlayer import VectorLayer
620
+ >>> client = GeoboxClient()
621
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
622
+ >>> features = layer.get_features(quant_factor=1000000,
623
+ ... skip=0,
624
+ ... limit=100,
625
+ ... skip_geometry=False,
626
+ ... return_count=False,
627
+ ... select_fields="fclass, osm_id",
628
+ ... out_srid=3857,
629
+ ... bbox_srid=3857)
630
+ """
631
+ params = {
632
+ 'f': 'json',
633
+ 'quant_factor': kwargs.get('quant_factor', 1000000),
634
+ 'skip': kwargs.get('skip', 0),
635
+ 'limit': kwargs.get('limit', 100),
636
+ 'user_id': kwargs.get('user_id', None),
637
+ 'search': kwargs.get('search', None),
638
+ 'search_fields': kwargs.get('search_fields', None),
639
+ 'skip_geometry': kwargs.get('skip_geometry', False),
640
+ 'return_count': kwargs.get('return_count', False),
641
+ 'feature_ids': kwargs.get('feature_ids', None),
642
+ 'select_fields': kwargs.get('select_fields', '[ALL]'),
643
+ 'skip_fields': kwargs.get('skip_fields', None),
644
+ 'out_srid': kwargs.get('out_srid', 3857),
645
+ 'order_by': kwargs.get('order_by', None),
646
+ 'q': kwargs.get('q', None),
647
+ 'bbox': kwargs.get('bbox', None),
648
+ 'bbox_srid': kwargs.get('bbox_srid', 3857)
649
+ }
650
+
651
+ return super()._get_list(api=self.api,
652
+ endpoint=f'{self.endpoint}features/',
653
+ params=params,
654
+ factory_func=lambda api, item, srid: Feature(self, srid, item),
655
+ geojson=True)
656
+
657
+
658
+ def get_feature(self, feature_id: int, out_srid: int = Feature.BASE_SRID) -> 'Feature':
659
+ """
660
+ Get a specific feature by its ID.
661
+
662
+ Args:
663
+ feature_id (int): The ID of the feature to retrieve.
664
+ out_srid (int, optional): Output spatial reference ID. default is 3857.
665
+
666
+ Returns:
667
+ Feature: The requested feature instance.
668
+
669
+ Raises:
670
+ NotFoundError: If the feature with the specified ID is not found.
671
+
672
+ Example:
673
+ >>> from geobox import GeoboxClient
674
+ >>> from geobox.vectorlayer import VectorLayer
675
+ >>> client = GeoboxClient()
676
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
677
+ >>> feature = layer.get_feature(feature_id=1, out_srid=4326)
678
+ """
679
+ endpoint = f"{self.endpoint}features/{feature_id}"
680
+ response = self.api.get(endpoint)
681
+ feature = Feature(self, data=response)
682
+ if out_srid != Feature.BASE_SRID:
683
+ feature.transform(out_srid)
684
+
685
+ return feature
686
+
687
+
688
+ def create_feature(self, geojson: Dict)-> 'Feature':
689
+ """
690
+ Create a new feature in the layer.
691
+
692
+ Args:
693
+ geojson (Dict): The feature data including properties and geometry.
694
+
695
+ Returns:
696
+ Feature: The newly created feature instance.
697
+
698
+ Raises:
699
+ ValidationError: If the feature data is invalid.
700
+
701
+ Example:
702
+ >>> from geobox import GeoboxClient
703
+ >>> from geobox.vectorlayer import VectorLayer
704
+ >>> client = GeoboxClient()
705
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
706
+ >>> geojson = {
707
+ ... "type": "Feature",
708
+ ... "geometry": {"type": "Point", "coordinates": [10, 20]},
709
+ ... "properties": {"name": "My Point"}
710
+ ... }
711
+ >>> feature = layer.create_feature(geojson=geojson)
712
+ """
713
+ return Feature.create_feature(self, geojson)
714
+
715
+
716
+ def delete_features(self, q: str = None, bbox: List[float] = None, bbox_srid: int = None, feature_ids: List[int] = None,
717
+ run_async: bool = True, user_id: int = None) -> 'Task':
718
+ """
719
+ Delete features from the layer based on specified criteria.
720
+
721
+ Args:
722
+ q (Optional[str]): Query to filter features to delete.
723
+ bbox (Optional[List[float]]): Bounding box to filter features.
724
+ bbox_srid (Optional[int]): Spatial reference ID for the bounding box.
725
+ feature_ids (Optional[List[int]]): List of specific feature IDs to delete.
726
+ run_async (Optional[bool]): Whether to run the deletion asynchronously. default is True.
727
+ user_id (Optional[int]): Specific user. privileges required.
728
+
729
+ Returns:
730
+ Task: The task instance of the deletion operation.
731
+
732
+ Raises:
733
+ ValidationError: If the deletion parameters are invalid.
734
+
735
+ Example:
736
+ >>> from geobox import GeoboxClient
737
+ >>> from geobox.vectorlayer import VectorLayer
738
+ >>> client = GeoboxClient()
739
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
740
+ >>> layer.delete_features(q="name like 'my_layer'",
741
+ ... bbox=[10, 20, 30, 40],
742
+ ... bbox_srid=3857,
743
+ ... feature_ids=[1, 2, 3],
744
+ ... run_async=True)
745
+ """
746
+ data = clean_data({
747
+ "q": q,
748
+ "bbox": bbox,
749
+ "bbox_srid": bbox_srid,
750
+ "feature_ids": feature_ids,
751
+ "run_async": run_async,
752
+ "user_id": user_id
753
+ })
754
+
755
+ endpoint = urljoin(self.endpoint, 'deleteFeatures/')
756
+ response = self.api.post(endpoint, data, is_json=False)
757
+ task = Task.get_task(self.api, response.get('task_id'))
758
+ return task
759
+
760
+
761
+ def import_features(self, file_uuid: str, input_geom_type: 'InputGeomType', input_layer_name: str = None, input_dataset: str = None,
762
+ user_id: int = None, input_srid: int = None, file_encoding: str = "utf-8",
763
+ replace_domain_codes_by_values: bool = False, report_errors: bool = True) -> 'Task':
764
+ """
765
+ Import features from a file into the layer.
766
+
767
+ Args:
768
+ file_uuid (str): UUID of the uploaded file to import.
769
+ input_geom_type (InputGeomType): Type of geometry in the input file.
770
+ input_layer_name (str, optional): Name of the layer in the input file.
771
+ input_dataset (str, optional): Name of the dataset in the input file.
772
+ user_id (int, optional): Specific user. privileges required.
773
+ input_srid (int, optional): Spatial reference ID of the input data.
774
+ file_encoding (str, optional): Character encoding of the input file.
775
+ replace_domain_codes_by_values (bool, optional): Whether to replace domain codes with values.
776
+ report_errors (bool, optional): Whether to report import errors.
777
+
778
+ Returns:
779
+ Task: The task instance of the import operation.
780
+
781
+ Raises:
782
+ ValidationError: If the import parameters are invalid.
783
+
784
+ Example:
785
+ >>> from geobox import GeoboxClient
786
+ >>> from geobox.vectorlayer import VectorLayer
787
+ >>> client = GeoboxClient()
788
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
789
+ >>> task = layer.import_features(file_uuid="12345678-1234-5678-1234-567812345678",
790
+ ... input_geom_type=InputGeomType.POINT,
791
+ ... input_layer_name="my_layer",
792
+ ... input_dataset="my_dataset",
793
+ ... input_srid=3857,
794
+ ... file_encoding="utf-8",
795
+ ... replace_domain_codes_by_values=False,
796
+ ... report_errors=True)
797
+ """
798
+ data = clean_data({
799
+ "file_uuid": file_uuid,
800
+ "input_layer": input_layer_name,
801
+ "input_geom_type": input_geom_type.value if isinstance(input_geom_type, InputGeomType) else input_geom_type,
802
+ "replace_domain_codes_by_values": replace_domain_codes_by_values,
803
+ "input_dataset": input_dataset,
804
+ "user_id": user_id,
805
+ "input_srid": input_srid,
806
+ "file_encoding": file_encoding,
807
+ "report_errors": report_errors
808
+ })
809
+
810
+ endpoint = urljoin(self.endpoint, 'import/')
811
+ response = self.api.post(endpoint, data, is_json=False)
812
+ task = Task.get_task(self.api, response.get('task_id'))
813
+ return task
814
+
815
+
816
+ def export_features(self, out_filename: str, out_format: 'FileOutputFormat', replace_domain_codes_by_values: bool = False,
817
+ run_async: bool = True, bbox: List[float] = None, out_srid: int = None, zipped: bool = True,
818
+ feature_ids: List[int] = None, bbox_srid: int = None, q: str = None, fields: List[str] = None) -> 'Task':
819
+ """
820
+ Export features from the layer to a file.
821
+
822
+ Args:
823
+ out_filename (str): Name of the output file.
824
+ out_format (FileOutputFormat): Format of the output file (e.g., 'Shapefile', 'GPKG', 'GeoJSON', 'CSV', 'KML', 'DXF').
825
+ replace_domain_codes_by_values (bool, optional): Whether to replace domain codes with values.
826
+ run_async (bool, optional): Whether to run the export asynchronously.
827
+ bbox (List, optional): Bounding box to filter features.
828
+ out_srid (int): Spatial reference ID for the output.
829
+ zipped (bool, optional): Whether to compress the output file.
830
+ feature_ids (List[int], optional): List of specific feature IDs to export.
831
+ bbox_srid (int, optional): Spatial reference ID for the bounding box.
832
+ q (str, optional): Query to filter features.
833
+ fields (List[str], optional): List of fields to include in the export.
834
+
835
+ Returns:
836
+ Task: The task instance of the export operation.
837
+
838
+ Raises:
839
+ ValidationError: If the export parameters are invalid.
840
+
841
+ Example:
842
+ >>> from geobox import GeoboxClient
843
+ >>> from geobox.vectorlayer import VectorLayer
844
+ >>> client = GeoboxClient()
845
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
846
+ >>> task = layer.export_features(out_filename="output.shp",
847
+ ... out_format="shp",
848
+ ... replace_domain_codes_by_values=False,
849
+ ... run_async=True,
850
+ ... bbox=[10, 20, 30, 40],
851
+ ... out_srid=3857,
852
+ ... zipped=True,
853
+ ... feature_ids=[1, 2, 3])
854
+ """
855
+ data = clean_data({
856
+ "replace_domain_codes_by_values": replace_domain_codes_by_values,
857
+ "out_format": out_format.value,
858
+ "run_async": run_async,
859
+ "bbox": bbox,
860
+ "out_srid": out_srid,
861
+ "zipped": zipped,
862
+ "feature_ids": feature_ids,
863
+ "bbox_srid": bbox_srid,
864
+ "q": q,
865
+ "out_filename": out_filename,
866
+ "fields": fields
867
+ })
868
+
869
+ endpoint = urljoin(self.endpoint, 'export/')
870
+ response = self.api.post(endpoint, data, is_json=False)
871
+ task = Task.get_task(self.api, response.get('task_id'))
872
+ return task
873
+
874
+
875
+ def create_view(self, name: str, display_name: str = None, description: str = None,
876
+ view_filter: str = None, view_extent: Dict = None, view_cols: str = None) -> 'VectorLayerView':
877
+ """
878
+ Create a view of the vector layer.
879
+
880
+ Args:
881
+ name (str): The name of the view.
882
+ display_name (str, optional): The display name of the view.
883
+ description (str, optional): The description of the view.
884
+ view_filter (str, optional): The filter for the view.
885
+ view_extent (List[float], optional): The extent of the view.
886
+ view_cols (str, optional): The columns to include in the view.
887
+
888
+ Returns:
889
+ VectorLayerView: The created view instance.
890
+
891
+ Raises:
892
+ ValidationError: If the view parameters are invalid.
893
+
894
+ Example:
895
+ >>> from geobox import GeoboxClient
896
+ >>> from geobox.vectorlayer import VectorLayer
897
+ >>> client = GeoboxClient()
898
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
899
+ >>> view = layer.create_view(name="my_view",
900
+ ... display_name="My View",
901
+ ... description="This is a view of my layer",
902
+ ... view_filter="province_name = 'Tehran'",
903
+ ... view_extent=[10, 20, 30, 40],
904
+ ... view_cols="[ALL]")
905
+ """
906
+ from .view import VectorLayerView
907
+
908
+ data = {
909
+ "name": name,
910
+ "display_name": display_name,
911
+ "description": description,
912
+ "view_filter": view_filter,
913
+ "view_extent": view_extent,
914
+ "view_cols": view_cols
915
+ }
916
+
917
+ return super()._create(api=self.api,
918
+ endpoint=f'{self.endpoint}views/',
919
+ data=data,
920
+ factory_func=lambda api, item: VectorLayerView(api, item['uuid'], self.layer_type, item))
921
+
922
+
923
+ def get_tile(self, x: int, y: int, z: int) -> str:
924
+ """
925
+ Get a vector tile for the layer.
926
+
927
+ Args:
928
+ x (int): X coordinate of the tile.
929
+ y (int): Y coordinate of the tile.
930
+ z (int): Zoom level of the tile.
931
+
932
+ Returns:
933
+ str: The vector tile data.
934
+
935
+ Example:
936
+ >>> from geobox import GeoboxClient
937
+ >>> from geobox.vectorlayer import VectorLayer
938
+ >>> client = GeoboxClient()
939
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
940
+ >>> tile = layer.get_tile(x=10, y=20, z=1)
941
+ """
942
+ endpoint = f'{self.BASE_ENDPOINT}{self.endpoint}tiles/{z}/{x}/{y}.pbf'
943
+ return endpoint
944
+
945
+
946
+ def get_tile_json(self) -> Dict:
947
+ """
948
+ Get the vector tile JSON configuration for the layer.
949
+
950
+ Returns:
951
+ Dict: The vector tile JSON configuration.
952
+
953
+ Example:
954
+ >>> from geobox import GeoboxClient
955
+ >>> from geobox.vectorlayer import VectorLayer
956
+ >>> client = GeoboxClient()
957
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
958
+ >>> tile_json = layer.get_tile_json()
959
+ """
960
+ endpoint = urljoin(self.endpoint, 'tilejson.json')
961
+ return self.api.get(endpoint)
962
+
963
+
964
+ @property
965
+ def settings(self) -> Dict:
966
+ """
967
+ Get the layer's settings.
968
+
969
+ Returns:
970
+ Dict: The layer settings.
971
+
972
+ Example:
973
+ >>> from geobox import GeoboxClient
974
+ >>> from geobox.vectorlayer import VectorLayer
975
+ >>> client = GeoboxClient()
976
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
977
+ >>> setting = layer.setting
978
+ """
979
+ return super()._get_settings(endpoint=self.endpoint)
980
+
981
+
982
+ def set_settings(self, **kwargs) -> Dict:
983
+ """
984
+ Set the settings of the Vector Layer.
985
+
986
+ Keyword Args:
987
+ title_field (str): The field to use as the title.
988
+ domain_display_type (str): The type of domain display.
989
+ allow_export (bool): Whether to allow export.
990
+ editable (bool): Whether to allow editing.
991
+ edit_geometry (bool): Whether to allow editing the geometry.
992
+ editable_attributes (str): The attributes to allow editing.
993
+ allow_insert (bool): Whether to allow inserting.
994
+ allow_delete (bool): Whether to allow deleting.
995
+ min_zoom (int): The minimum zoom level.
996
+ max_zoom (int): The maximum zoom level.
997
+ max_features (int): The maximum number of features.
998
+ filter_features (bool): Whether to filter features.
999
+ fields (List[str]): The fields to include in the layer.
1000
+ use_cache (bool): Whether to use caching.
1001
+ cache_until_zoom (int): The zoom level to cache until.
1002
+
1003
+ Returns:
1004
+ Dict: The updated settings.
1005
+
1006
+ Raises:
1007
+ ValidationError: If the settings data is invalid.
1008
+
1009
+ Example:
1010
+ >>> from geobox import GeoboxClient
1011
+ >>> from geobox.vectorlayer import VectorLayer
1012
+ >>> client = GeoboxClient()
1013
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
1014
+ >>> layer.set_settings(title_field="name",
1015
+ ... domain_display_type="Value",
1016
+ ... allow_export=True,
1017
+ ... editable=True,
1018
+ ... edit_geometry=True,
1019
+ ... editable_attributes="[ALL]",
1020
+ ... allow_insert=True,
1021
+ ... allow_delete=True,
1022
+ ... min_zoom=0,
1023
+ ... max_zoom=24,
1024
+ ... max_features=65536,
1025
+ ... filter_features=True,
1026
+ ... fields=["id"],
1027
+ ... use_cache=True,
1028
+ ... cache_until_zoom=17)
1029
+ """
1030
+ general_settings = {'title_field', 'domain_display_type', 'allow_export'}
1031
+ edit_settings = {'editable', 'edit_geometry', 'editable_attributes', 'allow_insert', 'allow_delete'}
1032
+ tile_settings = {'min_zoom', 'max_zoom', 'max_features', 'filter_features', 'fields', 'use_cache', 'cache_until_zoom'}
1033
+
1034
+ settings = {
1035
+ 'general_settings': {},
1036
+ 'edit_settings': {},
1037
+ 'tile_settings': {}
1038
+ }
1039
+
1040
+ for key, value in kwargs.items():
1041
+ if key in general_settings:
1042
+ settings['general_settings'][key] = value
1043
+ elif key in edit_settings:
1044
+ settings['edit_settings'][key] = value
1045
+ elif key in tile_settings:
1046
+ settings['tile_settings'][key] = value
1047
+
1048
+ return super()._set_settings(endpoint=self.endpoint, data=settings)
1049
+
1050
+
1051
+ def seed_cache(self, from_zoom: int = None, to_zoom: int = None, ignore_cache: bool = False, workers: int = 1, user_id: int = None) -> List['Task']:
1052
+ """
1053
+ Seed the cache for the layer.
1054
+
1055
+ Args:
1056
+ from_zoom (int, optional): The zoom level to start caching from.
1057
+ to_zoom (int, optional): The zoom level to stop caching at.
1058
+ ignore_cache (bool, optional): Whether to ignore the cache. default is False.
1059
+ workers (int, optional): The number of workers to use. default is 1.
1060
+ user_id (int, optional): Specific user. privileges required.
1061
+
1062
+ Returns:
1063
+ List[Task]: The task instance of the cache seeding operation.
1064
+
1065
+ Example:
1066
+ >>> from geobox import GeoboxClient
1067
+ >>> from geobox.vectorlayer import VectorLayer
1068
+ >>> client = GeoboxClient()
1069
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
1070
+ >>> task = layer.seed_cache(from_zoom=0, to_zoom=10, ignore_cache=False, workers=1)
1071
+ """
1072
+ data = {
1073
+ 'from_zoom': from_zoom,
1074
+ 'to_zoom': to_zoom,
1075
+ 'ignore_cache': ignore_cache,
1076
+ 'workers': workers,
1077
+ 'user_id': user_id
1078
+ }
1079
+ return super()._seed_cache(endpoint=self.endpoint, data=data)
1080
+
1081
+
1082
+ def clear_cache(self) -> None:
1083
+ """
1084
+ Clear the layer's cache.
1085
+
1086
+ Returns:
1087
+ None
1088
+
1089
+ Example:
1090
+ >>> from geobox import GeoboxClient
1091
+ >>> from geobox.vectorlayer import VectorLayer
1092
+ >>> client = GeoboxClient()
1093
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
1094
+ >>> layer.clear_cache()
1095
+ """
1096
+ super()._clear_cache(endpoint=self.endpoint)
1097
+
1098
+
1099
+ @property
1100
+ def cache_size(self) -> int:
1101
+ """
1102
+ Get the size of the layer's cache.
1103
+
1104
+ Returns:
1105
+ int: The size of the layer's cache.
1106
+
1107
+ Example:
1108
+ >>> from geobox import GeoboxClient
1109
+ >>> from geobox.vectorlayer import VectorLayer
1110
+ >>> client = GeoboxClient()
1111
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
1112
+ >>> layer.cache_size
1113
+ """
1114
+ return super()._cache_size(endpoint=self.endpoint)
1115
+
1116
+
1117
+ def update_stats(self) -> None:
1118
+ """
1119
+ Update the layer's statistics.
1120
+
1121
+ Returns:
1122
+ None
1123
+
1124
+ Example:
1125
+ >>> from geobox import GeoboxClient
1126
+ >>> from geobox.vectorlayer import VectorLayer
1127
+ >>> client = GeoboxClient()
1128
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
1129
+ >>> layer.update_stats()
1130
+ """
1131
+ endpoint = urljoin(self.endpoint, 'updateStats/')
1132
+ return self.api.post(endpoint)
1133
+
1134
+
1135
+ def prune_edited_areas(self) -> None:
1136
+ """
1137
+ Prune edited areas. This method eliminates edited areas when there are too many of them. Cache builder uses this edited areas for partial cache generating.
1138
+
1139
+ Returns:
1140
+ None
1141
+
1142
+ Example:
1143
+ >>> from geobox import GeoboxClient
1144
+ >>> from geobox.vectorlayer import VectorLayer
1145
+ >>> client = GeoboxClient()
1146
+ >>> layer = VectorLayer.get_vector(api=client, uuid="12345678-1234-5678-1234-567812345678")
1147
+ >>> layer.prune_edited_areas()
1148
+ """
1149
+ endpoint = urljoin(self.endpoint, 'prune/')
1150
1150
  return self.api.post(endpoint)