geobox 2.0.1__py3-none-any.whl → 2.2.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.
Files changed (70) hide show
  1. geobox/__init__.py +61 -63
  2. geobox/aio/__init__.py +61 -63
  3. geobox/aio/api.py +489 -473
  4. geobox/aio/apikey.py +263 -263
  5. geobox/aio/attachment.py +341 -339
  6. geobox/aio/base.py +261 -262
  7. geobox/aio/basemap.py +196 -196
  8. geobox/aio/dashboard.py +340 -342
  9. geobox/aio/feature.py +23 -33
  10. geobox/aio/field.py +315 -321
  11. geobox/aio/file.py +72 -72
  12. geobox/aio/layout.py +340 -341
  13. geobox/aio/log.py +23 -23
  14. geobox/aio/map.py +1033 -1034
  15. geobox/aio/model3d.py +415 -415
  16. geobox/aio/mosaic.py +696 -696
  17. geobox/aio/plan.py +314 -314
  18. geobox/aio/query.py +693 -693
  19. geobox/aio/raster.py +907 -869
  20. geobox/aio/raster_analysis.py +740 -0
  21. geobox/aio/route.py +4 -4
  22. geobox/aio/scene.py +340 -342
  23. geobox/aio/settings.py +18 -18
  24. geobox/aio/task.py +404 -402
  25. geobox/aio/tile3d.py +337 -339
  26. geobox/aio/tileset.py +102 -103
  27. geobox/aio/usage.py +52 -51
  28. geobox/aio/user.py +506 -507
  29. geobox/aio/vector_tool.py +1968 -0
  30. geobox/aio/vectorlayer.py +315 -306
  31. geobox/aio/version.py +272 -273
  32. geobox/aio/view.py +1019 -983
  33. geobox/aio/workflow.py +340 -341
  34. geobox/api.py +18 -2
  35. geobox/apikey.py +262 -262
  36. geobox/attachment.py +336 -337
  37. geobox/base.py +384 -384
  38. geobox/basemap.py +194 -194
  39. geobox/dashboard.py +339 -341
  40. geobox/enums.py +432 -348
  41. geobox/feature.py +5 -5
  42. geobox/field.py +320 -320
  43. geobox/file.py +4 -4
  44. geobox/layout.py +339 -340
  45. geobox/log.py +4 -4
  46. geobox/map.py +1031 -1032
  47. geobox/model3d.py +410 -410
  48. geobox/mosaic.py +696 -696
  49. geobox/plan.py +313 -313
  50. geobox/query.py +691 -691
  51. geobox/raster.py +907 -863
  52. geobox/raster_analysis.py +737 -0
  53. geobox/scene.py +341 -342
  54. geobox/settings.py +194 -194
  55. geobox/task.py +399 -400
  56. geobox/tile3d.py +337 -338
  57. geobox/tileset.py +4 -4
  58. geobox/usage.py +3 -3
  59. geobox/user.py +503 -503
  60. geobox/vector_tool.py +1968 -0
  61. geobox/vectorlayer.py +5 -5
  62. geobox/version.py +272 -272
  63. geobox/view.py +981 -981
  64. geobox/workflow.py +338 -339
  65. {geobox-2.0.1.dist-info → geobox-2.2.0.dist-info}/METADATA +15 -1
  66. geobox-2.2.0.dist-info/RECORD +72 -0
  67. geobox-2.0.1.dist-info/RECORD +0 -68
  68. {geobox-2.0.1.dist-info → geobox-2.2.0.dist-info}/WHEEL +0 -0
  69. {geobox-2.0.1.dist-info → geobox-2.2.0.dist-info}/licenses/LICENSE +0 -0
  70. {geobox-2.0.1.dist-info → geobox-2.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,737 @@
1
+ from typing import List, Dict, Optional, TYPE_CHECKING, Union
2
+
3
+ from geobox.field import Field
4
+
5
+ from .vectorlayer import VectorLayer
6
+ from .view import VectorLayerView
7
+ from .base import Base
8
+ from .raster import Raster
9
+ from .enums import (AnalysisDataType, PolygonizeConnectivity, AnalysisResampleMethod,
10
+ SlopeUnit, AnalysisAlgorithm, RangeBound, DistanceUnit)
11
+ from .utils import clean_data
12
+
13
+ if TYPE_CHECKING:
14
+ from . import GeoboxClient
15
+ from .task import Task
16
+
17
+ class RasterAnalysis(Base):
18
+
19
+ BASE_ENDPOINT = 'analysis/'
20
+
21
+ def __init__(self, api: 'GeoboxClient'):
22
+ """
23
+ Initialize a RasterAnalysis instance.
24
+
25
+ Args:
26
+ api (GeoboxClient): The GeoboxClient instance for making requests.
27
+ """
28
+ super().__init__(api)
29
+
30
+
31
+ def __repr__(self) -> str:
32
+ return f"RasterAnalysis()"
33
+
34
+
35
+ def rasterize(self,
36
+ layer: Union[VectorLayer, VectorLayerView],
37
+ output_raster_name: str,
38
+ pixel_size: int = 10,
39
+ nodata: Optional[int] = -9999,
40
+ data_type: Optional[AnalysisDataType] = AnalysisDataType.int16,
41
+ burn_value: Optional[int] = 1,
42
+ burn_attribute: Optional[str] = None,
43
+ user_id: Optional[int] = None) -> 'Task':
44
+ """
45
+ Rasterize a vector layer
46
+
47
+ This method converts a vector layer (or view) to a raster dataset using the specified parameters.
48
+ You can control the output raster's name, pixel size, data type, nodata value, and the value to burn (either a constant or from an attribute field).
49
+ Only users with Publisher role or higher can perform this operation.
50
+
51
+ Args:
52
+ layer (VectorLayer | VectorLayerView): VectorLayer or VectorLayerView instance
53
+ output_raster_name (str): Name for the output raster dataset
54
+ pixel_size (int, optional): Pixel size for the output raster (must be > 0). default: 10
55
+ nodata (int, optional): NoData value to use in the output raster. default: -9999
56
+ data_type (AnalysisDataType, optional): Data type for the output raster (e.g., int16, float32). default: AnalysisDataType.int16
57
+ burn_value (int, optional): Value to burn into the raster for all features (if burn_attribute is not set). default: 1
58
+ burn_attribute (str, optional): Name of the attribute field to use for burning values into the raster
59
+ user_id (int, optional): specific user. priviledges required!
60
+
61
+ Returns:
62
+ Task: task instance of the process
63
+
64
+ Example:
65
+ >>> from geobox import GeoboxClient
66
+ >>> from geobox.raster_analysis import RasterAnalysis
67
+ >>> client = GeoboxClient()
68
+ >>> vector = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
69
+ >>> task = client.raster_analysis.rasterize(layer=vector, output_raster_name='test')
70
+ or
71
+ >>> raster_analysis = RasterAnalysis(client)
72
+ >>> task = raster_analysis.rasterize(layer=vector, output_raster_name='test')
73
+ """
74
+ if not isinstance(layer, VectorLayer) and not isinstance(layer, VectorLayerView):
75
+ raise ValueError("'layer' input only accepts vector layer and view objects!")
76
+
77
+ endpoint = f'{self.BASE_ENDPOINT}rasterize/'
78
+
79
+ data = clean_data({
80
+ 'layer_uuid': layer.uuid,
81
+ 'output_raster_name': output_raster_name,
82
+ 'is_view': False if isinstance(layer, VectorLayer) else True,
83
+ 'pixel_size': pixel_size,
84
+ 'nodata': nodata,
85
+ 'data_type': data_type.value,
86
+ 'burn_value': burn_value,
87
+ 'burn_attribute': burn_attribute,
88
+ 'user_id': user_id
89
+ })
90
+
91
+ response = self.api.post(endpoint=endpoint, payload=data, is_json=False)
92
+ return self.api.get_task(response['task_id'])
93
+
94
+
95
+ def polygonize(self,
96
+ raster: 'Raster',
97
+ output_layer_name: str,
98
+ band_index: int = 1,
99
+ value_field: Optional[str] = None,
100
+ mask_nodata: bool = False,
101
+ connectivity: PolygonizeConnectivity = PolygonizeConnectivity.connected_4,
102
+ keep_values: Optional[str] = None,
103
+ layer_name: Optional[str] = None,
104
+ user_id: Optional[int] = None) -> 'Task':
105
+ """
106
+ Convert a raster to vector polygons
107
+
108
+ vectorizes a raster (polygonize) to a vector dataset (*.gpkg). Only users with Publisher role or higher can perform this operation
109
+
110
+ Args:
111
+ raster (Raster): Raster instance
112
+ output_layer_name (str): Name for the output vector layer.
113
+ band_index (int, optional): Raster band to polygonize. default: 1
114
+ value_field (str, optional): Name of attribute field storing the pixel value. default: None
115
+ mask_nodata (bool, optional): If True, NoData pixels are excluded using the band mask. default: False
116
+ connectivity (PolygonizeConnectivity, optional): 4 or 8 connectivity for region grouping. default: PolygonizeConnectivity.connected_4
117
+ keep_values (str, optional): JSON array of values to keep (e.g., '[1,2,3]'). default: None
118
+ layer_name (str, optional): Output layer name. default: None
119
+ user_id (int, optional): specific user. priviledges required!
120
+
121
+ Returns:
122
+ Task: task instance of the process
123
+
124
+ Example:
125
+ >>> from geobox import GeoboxClient
126
+ >>> from geobox.raster_analysis import RasterAnalysis
127
+ >>> client = GeoboxClient()
128
+ >>> raster = client.get_raster(uuid="12345678-1234-5678-1234-567812345678")
129
+ >>> task = client.raster_analysis.polygonize(raster=raster, output_layer_name='test')
130
+ or
131
+ >>> raster_analysis = RasterAnalysis(client)
132
+ >>> task = raster_analysis.polygonize(raster=raster, output_layer_name='test')
133
+ """
134
+ endpoint = f'{self.BASE_ENDPOINT}polygonize/'
135
+
136
+ data = clean_data({
137
+ 'raster_uuid': raster.uuid,
138
+ 'output_layer_name': output_layer_name,
139
+ 'band_index': band_index,
140
+ 'value_field': value_field,
141
+ 'mask_nodata': mask_nodata,
142
+ 'connectivity': connectivity.value,
143
+ 'keep_values': keep_values,
144
+ 'layer_name': layer_name,
145
+ 'user_id': user_id
146
+ })
147
+
148
+ response = self.api.post(endpoint=endpoint, payload=data, is_json=False)
149
+ return self.api.get_task(response['task_id'])
150
+
151
+
152
+ def clip(self,
153
+ raster: 'Raster',
154
+ layer: Union[VectorLayer, VectorLayerView],
155
+ output_raster_name: str,
156
+ where: Optional[str] = None,
157
+ dst_nodata: int = -9999,
158
+ crop: bool = True,
159
+ resample: AnalysisResampleMethod = AnalysisResampleMethod.near,
160
+ user_id: Optional[int] = None) -> 'Task':
161
+ """
162
+ Clip a raster using a vector layer as a mask
163
+
164
+ clips a raster dataset using a vector layer as the clipping boundary. Only users with Publisher role or higher can perform this operation
165
+
166
+ Args:
167
+ raster (Raster): Raster instance
168
+ layer (VectorLayer | VectorLayerView): VectorLayer or VectorLayerView instance
169
+ output_raster_name (str): Name for the output raster dataset
170
+ where (str, optional): Optional attribute filter, e.g. 'VEG=forest'.
171
+ dst_nodata (int, optional): Output NoData value. default: -9999
172
+ crop (bool, optional): True=shrink extent to polygon(s); False=keep full extent but mask outside. default: True
173
+ resample (CropResample, optional): Resampling method: 'near', 'bilinear', 'cubic', 'lanczos', etc. default: CropResample.near
174
+ user_id (int, optional): specific user. priviledges required!
175
+
176
+ Returns:
177
+ Task: task instance of the process
178
+
179
+ Example:
180
+ >>> from geobox import GeoboxClient
181
+ >>> from geobox.raster_analysis import RasterAnalysis
182
+ >>> client = GeoboxClient()
183
+ >>> raster = client.get_raster(uuid="12345678-1234-5678-1234-567812345678")
184
+ >>> vector = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
185
+ >>> task = client.raster_analysis.clip(layer=vector, output_raster_name='test')
186
+ or
187
+ >>> raster_analysis = RasterAnalysis(client)
188
+ >>> task = raster_analysis.clip(raster=raster, layer=vector, output_raster_name='test')
189
+ """
190
+ if not isinstance(layer, VectorLayer) and not isinstance(layer, VectorLayerView):
191
+ raise ValueError("'layer' input only accepts vector layer and view objects!")
192
+
193
+ endpoint = f'{self.BASE_ENDPOINT}clip/'
194
+
195
+ data = clean_data({
196
+ 'raster_uuid': raster.uuid,
197
+ 'layer_uuid': layer.uuid,
198
+ 'output_raster_name': output_raster_name,
199
+ 'is_view': False if isinstance(layer, VectorLayer) else True,
200
+ 'where': where,
201
+ 'dst_nodata': dst_nodata,
202
+ 'crop': crop,
203
+ 'resample': resample.value,
204
+ 'user_id': user_id
205
+ })
206
+
207
+ response = self.api.post(endpoint=endpoint, payload=data, is_json=False)
208
+ return self.api.get_task(response['task_id'])
209
+
210
+
211
+ def calculator(self,
212
+ variables: str,
213
+ expr: str,
214
+ output_raster_name: str,
215
+ match_raster_uuid: Optional[str] = None,
216
+ resample: AnalysisResampleMethod = AnalysisResampleMethod.bilinear,
217
+ out_dtype: AnalysisDataType = AnalysisDataType.float32,
218
+ dst_nodata: int = -9999,
219
+ user_id: Optional[int] = None) -> 'Task':
220
+ """
221
+ Perform raster calculator operations on multiple raster datasets.
222
+
223
+ it allows you to perform mathematical operations on one or more raster datasets using NumPy expressions.
224
+ Variables in the expression correspond to raster datasets specified in the variables dictionary.
225
+
226
+ Examples:
227
+ NDVI calculation: variables='{"NIR": "raster_uuid_1", "RED": "raster_uuid_2"}', expr="(NIR-RED)/(NIR+RED)"
228
+ Slope threshold: variables='{"SLOPE": "raster_uuid_1"}', expr="np.where(SLOPE>30,1,0)"
229
+ Multi-band operations: variables='{"IMG": ["raster_uuid_1", 2]}', expr="IMG*2"
230
+
231
+ Args:
232
+ variables (str): JSON string mapping variable names to raster specifications. Format: '{"NIR": "raster_uuid_1", "RED": "raster_uuid_2"}' or '{"IMG": ["raster_uuid_1", 2]}' for multi-band operations.
233
+ expr (str): Mathematical expression using NumPy syntax. Use variable names from the variables dict, e.g., '(NIR-RED)/(NIR+RED)' or 'where(SLOPE>30,1,0)' or 'where((dist_to_highway < 1000) & (slope < 10), 1, 0)' .Supported functions: np, sin, cos, tan, asin, acos, atan, sinh, cosh, tanh, exp, log, log10, sqrt, abs, floor, ceil, round, minimum, maximum, clip, where, isnan, isfinite, pi, e.
234
+ output_raster_name (str): Name for the output raster dataset.
235
+ match_raster_uuid (str, optional): Optional raster UUID to match the output grid and projection. If not provided, the first variable becomes the reference grid.
236
+ resample (CropResample, optional): Resampling method: 'near', 'bilinear', 'cubic', 'lanczos', etc. default: CropResample.near
237
+ out_dtype (AnalysisDataType, optional): Data type for the output raster (e.g., int16, float32). default: AnalysisDataType.float32
238
+ dst_nodata (int, optional): NoData value for the output raster. default = -9999
239
+ user_id (int, optional): specific user. priviledges required!
240
+
241
+ Returns:
242
+ Task: task instance of the process
243
+
244
+ Example:
245
+ >>> from geobox import GeoboxClient
246
+ >>> from geobox.raster_analysis import RasterAnalysis
247
+ >>> client = GeoboxClient()
248
+ >>> raster = client.get_raster(uuid="12345678-1234-5678-1234-567812345678")
249
+ >>> task = client.raster_analysis.calculator(variables={"NIR": "raster_uuid_1", "RED": "raster_uuid_2"},
250
+ ... expr='where(SLOPE>30,1,0)',
251
+ ... output_raster_name='test')
252
+ or
253
+ >>> raster_analysis = RasterAnalysis(client)
254
+ >>> task = raster_analysis.raster_analysis.calculator(variables={"NIR": "raster_uuid_1", "RED": "raster_uuid_2"},
255
+ ... expr='where(SLOPE>30,1,0)',
256
+ ... output_raster_name='test')
257
+ """
258
+ endpoint = f'{self.BASE_ENDPOINT}calculator/'
259
+
260
+ data = clean_data({
261
+ 'variables': variables,
262
+ 'expr': expr,
263
+ 'output_raster_name': output_raster_name,
264
+ 'match_raster_uuid': match_raster_uuid,
265
+ 'resample': resample.value,
266
+ 'out_dtype': out_dtype.value,
267
+ 'dst_nodata': dst_nodata,
268
+ 'user_id': user_id
269
+ })
270
+
271
+ response = self.api.post(endpoint=endpoint, payload=data, is_json=False)
272
+ return self.api.get_task(response['task_id'])
273
+
274
+
275
+ def slope(self,
276
+ raster: 'Raster',
277
+ output_raster_name: str,
278
+ slope_units: SlopeUnit = SlopeUnit.degree,
279
+ algorithm: AnalysisAlgorithm = AnalysisAlgorithm.Horn,
280
+ scale: int = 1,
281
+ compute_edges: bool = True,
282
+ nodata_out: int = -9999,
283
+ user_id: Optional[int] = None) -> 'Task':
284
+ """
285
+ Calculate slope from a DEM raster.
286
+
287
+ This endpoint creates a slope raster from a Digital Elevation Model (DEM). Only users with Publisher role or higher can perform this operation.
288
+
289
+ Args:
290
+ raster (Raster): DEM Raster instance
291
+ output_raster_name (str): Name for the output raster dataset.
292
+ slope_units (SlopeUnit, optional): Slope units: 'degree' or 'percent'. default: SlopeUnit.degree
293
+ algorithm (AnalysisAlgorithm, optional): Algorithm: 'Horn' or 'ZevenbergenThorne'. default: AnalysisAlgorithm.Horn
294
+ scale (int, optional): Ratio of vertical units to horizontal units. default: 1
295
+ compute_edges (bool, optional): Whether to compute edges. default: True
296
+ nodata (int, optional): NoData value for the output raster. default = -9999
297
+ user_id (int, optional): specific user. priviledges required!
298
+
299
+ Returns:
300
+ Task: task instance of the process
301
+
302
+ Example:
303
+ >>> from geobox import GeoboxClient
304
+ >>> from geobox.raster_analysis import RasterAnalysis
305
+ >>> client = GeoboxClient()
306
+ >>> raster = client.get_raster(uuid="12345678-1234-5678-1234-567812345678")
307
+ >>> task = client.raster_analysis.slope(raster=raster, output_raster_name='test')
308
+ or
309
+ >>> raster_analysis = RasterAnalysis(client)
310
+ >>> task = raster_analysis.slope(raster=raster, output_raster_name='test')
311
+ """
312
+ endpoint = f'{self.BASE_ENDPOINT}slope/'
313
+
314
+ data = clean_data({
315
+ 'raster_uuid': raster.uuid,
316
+ 'output_raster_name': output_raster_name,
317
+ 'slope_units': slope_units.value,
318
+ 'algorithm': algorithm.value,
319
+ 'scale': scale,
320
+ 'compute_edges': compute_edges,
321
+ 'nodata_out': nodata_out,
322
+ 'user_id': user_id
323
+ })
324
+
325
+ response = self.api.post(endpoint=endpoint, payload=data, is_json=False)
326
+ return self.api.get_task(response['task_id'])
327
+
328
+
329
+ def aspect(self,
330
+ raster: 'Raster',
331
+ output_raster_name: str,
332
+ algorithm: AnalysisAlgorithm = AnalysisAlgorithm.Horn,
333
+ trigonometric: bool = False,
334
+ zero_for_flat: bool = True,
335
+ compute_edges: bool = True,
336
+ nodata_out: int = -9999,
337
+ user_id: Optional[int] = None) -> 'Task':
338
+ """
339
+ Calculate aspect from a DEM raster.
340
+
341
+ it creates an aspect raster (degrees 0–360) from a Digital Elevation Model (DEM).
342
+ Only users with Publisher role or higher can perform this operation.
343
+
344
+ Args:
345
+ raster (Raster): DEM Raster instance
346
+ output_raster_name (str): Name for the output raster dataset.
347
+ algorithm (AnalysisAlgorithm, optional): Algorithm: 'Horn' or 'ZevenbergenThorne'. default: AnalysisAlgorithm.Horn
348
+ trigonometric (bool, optional): False: azimuth (0°=N, 90°=E, clockwise); True: 0°=E, counter-clockwise. default: False
349
+ zero_for_flat (bool, optional): Set flats (slope==0) to 0 instead of NoData. default: True
350
+ compute_edges (bool, optional): Whether to compute edges. default: True
351
+ nodata (int, optional): NoData value for the output raster. default = -9999
352
+ user_id (int, optional): specific user. priviledges required!
353
+
354
+ Returns:
355
+ Task: task instance of the process
356
+
357
+ Example:
358
+ >>> from geobox import GeoboxClient
359
+ >>> from geobox.raster_analysis import RasterAnalysis
360
+
361
+ >>> client = GeoboxClient()
362
+ >>> raster = client.get_raster(uuid="12345678-1234-5678-1234-567812345678")
363
+ >>> task = cient.raster_analysis.aspect(raster=raster, output_raster_name='test')
364
+ or
365
+ >>> raster_analysis = RasterAnalysis(client)
366
+ >>> task = raster_analysis.aspect(raster=raster, output_raster_name='test')
367
+ """
368
+
369
+ endpoint = f'{self.BASE_ENDPOINT}aspect/'
370
+
371
+ data = clean_data({
372
+ 'raster_uuid': raster.uuid,
373
+ 'output_raster_name': output_raster_name,
374
+ 'algorithm': algorithm.value,
375
+ 'trigonometric': trigonometric,
376
+ 'zero_for_flat': zero_for_flat,
377
+ 'compute_edges': compute_edges,
378
+ 'nodata_out': nodata_out,
379
+ 'user_id': user_id
380
+ })
381
+
382
+ response = self.api.post(endpoint=endpoint, payload=data, is_json=False)
383
+ return self.api.get_task(response['task_id'])
384
+
385
+
386
+ def reclassify(self,
387
+ raster: 'Raster',
388
+ output_raster_name: str,
389
+ rules: str,
390
+ default_value: Optional[int] = None,
391
+ nodata_in: int = -9999,
392
+ nodata_out: int = -9999,
393
+ out_dtype: AnalysisDataType = AnalysisDataType.int16,
394
+ inclusive: RangeBound = RangeBound.left,
395
+ user_id: Optional[int] = None) -> 'Task':
396
+ """
397
+ Reclassify a raster using value mapping or class breaks.
398
+
399
+ This endpoint reclassifies raster values according to specified rules.
400
+ Only users with Publisher role or higher can perform this operation.
401
+
402
+ Args:
403
+ raster (Raster): Raster instance
404
+ output_raster_name (str): Name for the output reclassified raster dataset.
405
+ rules (str): JSON string containing reclassification rules.
406
+ For mode='exact', it should be a dict {old_value: new_value}.
407
+ For mode='range', it should be a list of (low, high, new_value).
408
+ Example for mode='exact': '{"1": 10, "2": 20, "3": 30}'.
409
+ Example for mode='range': '[[0, 10, 1], [10, 20, 2], [20, 30, 3]]'.
410
+ the method would detect the mode type based on the rules input.
411
+ default_value (str, optional): Value to assign when a pixel matches no rule.
412
+ nodata_in (int, optional): NoData of input. If None, tries to get from the input raster.
413
+ nodata_out (int, optional): NoData value to set on output band.
414
+ out_dtype (AnalysisDataType, optional): Output data type. default: AnalysisDataType.int16
415
+ inclusive (RangeBound, optional): Range bound semantics for mode='range': 'left', 'right', 'both', 'neither'. default: RangeBound.left
416
+ user_id (int, optional): specific user. priviledges required!
417
+
418
+ Returns:
419
+ Task: task instance of the process
420
+
421
+ Example:
422
+ >>> from geobox import GeoboxClient
423
+ >>> from geobox.raster_analysis import RasterAnalysis
424
+ >>> client = GeoboxClient()
425
+ >>> raster = client.get_raster(uuid="12345678-1234-5678-1234-567812345678")
426
+ >>> task = client.raster_analysis.reclassify(raster=raster, output_raster_name='test', rules='{"1": 10, "2": 20, "3": 30}')
427
+ or
428
+ >>> raster_analysis = RasterAnalysis(client)
429
+ >>> task = raster_analysis.reclassify(raster=raster, output_raster_name='test', rules='{"1": 10, "2": 20, "3": 30}')
430
+ """
431
+ endpoint = f'{self.BASE_ENDPOINT}reclassify/'
432
+
433
+ data = clean_data({
434
+ 'raster_uuid': raster.uuid,
435
+ 'output_raster_name': output_raster_name,
436
+ 'rules': rules,
437
+ 'mode': 'exact' if isinstance(rules, dict) else 'range' if isinstance(rules, list) else None,
438
+ 'default_value': default_value,
439
+ 'nodata_in': nodata_in,
440
+ 'nodata_out': nodata_out,
441
+ 'out_dtype': out_dtype.value,
442
+ 'inclusive': inclusive.value,
443
+ 'user_id': user_id
444
+ })
445
+
446
+ response = self.api.post(endpoint=endpoint, payload=data, is_json=False)
447
+ return self.api.get_task(response['task_id'])
448
+
449
+
450
+ def resample(self,
451
+ raster: 'Raster',
452
+ output_raster_name: str,
453
+ out_res: Optional[str] = None,
454
+ scale_factor: Optional[str] = None,
455
+ match_raster_uuid: Optional[str] = None,
456
+ resample_method: AnalysisResampleMethod = AnalysisResampleMethod.near,
457
+ dst_nodata: int = -9999,
458
+ user_id: Optional[int] = None) -> 'Task':
459
+ """
460
+ Resample a raster to a different resolution.
461
+
462
+ it resamples a raster using GDAL Warp.
463
+ Exactly one of out_res, scale_factor, or match_raster_uuid must be provided.
464
+ Only users with Publisher role or higher can perform this operation.
465
+
466
+ Args:
467
+ raster (Raster): Raster instance
468
+ output_raster_name (str): Name for the output reclassified raster dataset.
469
+ out_res (str, optional): Output resolution as 'x_res,y_res' (e.g., '10,10').
470
+ scale_factor (int, optional): Scale factor (e.g., 2.0 for 2x finer resolution).
471
+ match_raster_uuid (str, optional): UUID of reference raster to match resolution/extent.
472
+ resample_method (AnalysisResampleMethod, optional): Resampling method: 'near', 'bilinear', 'cubic', 'lanczos', etc.
473
+ dst_nodata (int, optional): Output NoData value.
474
+ user_id (int, optional): specific user. priviledges required!
475
+
476
+ Returns:
477
+ Task: task instance of the process
478
+
479
+ Example:
480
+ >>> from geobox import GeoboxClient
481
+ >>> from geobox.raster_analysis import RasterAnalysis
482
+ >>> client = GeoboxClient()
483
+ >>> raster = client.get_raster(uuid="12345678-1234-5678-1234-567812345678")
484
+ >>> task = client.raster_analysis.resample(raster=raster, output_raster_name='test', out_res='10,10')
485
+ or
486
+ >>> raster_analysis = RasterAnalysis(client)
487
+ >>> task = raster_analysis.resample(raster=raster, output_raster_name='test', out_res='10,10')
488
+ """
489
+ if sum(x is not None for x in [out_res, scale_factor, match_raster_uuid]) != 1:
490
+ raise ValueError('Exactly one of out_res, scale_factor, or match_raster_uuid must be provided!')
491
+
492
+ endpoint = f'{self.BASE_ENDPOINT}resample/'
493
+
494
+ data = clean_data({
495
+ 'raster_uuid': raster.uuid,
496
+ 'output_raster_name': output_raster_name,
497
+ 'out_res': out_res,
498
+ 'scale_factor': scale_factor,
499
+ 'match_raster_uuid': match_raster_uuid,
500
+ 'resample_method': resample_method.value,
501
+ 'dst_nodata': dst_nodata,
502
+ 'user_id': user_id
503
+ })
504
+
505
+ response = self.api.post(endpoint=endpoint, payload=data, is_json=False)
506
+ return self.api.get_task(response['task_id'])
507
+
508
+
509
+ def idw_interpolation(self,
510
+ layer: Union[VectorLayer, VectorLayerView],
511
+ output_raster_name: str,
512
+ z_field: Field,
513
+ match_raster_uuid: Optional[str] = None,
514
+ pixel_size: int = 10,
515
+ extent: Optional[str] = None,
516
+ power: float = 2.0,
517
+ smoothing: float = 0.0,
518
+ max_points: int = 16,
519
+ radius: int = 1000,
520
+ nodata: int = -9999,
521
+ out_dtype: AnalysisDataType = AnalysisDataType.float32,
522
+ user_id: Optional[int] = None) -> 'Task':
523
+ """
524
+ Create an IDW (Inverse Distance Weighting) interpolation raster from point data.
525
+
526
+ it creates a raster using IDW interpolation from point data in a vector layer.
527
+ Only users with Publisher role or higher can perform this operation.
528
+
529
+ Args:
530
+ layer (VectorLayer | VectorLayerview): layer containing point data
531
+ output_raster_name (str): Name for the output IDW raster dataset.
532
+ z_field (Field): the field containing the values to interpolate.
533
+ match_raster_uuid (str, optional): UUID of reference raster to match resolution/extent.
534
+ pixel_size (int, optional): Pixel size for the output raster. default: 10
535
+ extent (str, optional): Extent as 'minX,minY,maxX,maxY'.
536
+ power (float, optional): Power parameter for IDW. default: 2.0
537
+ smoothing (float, optional): Smoothing parameter for IDW. default: 0.0
538
+ max_points (int, optional): Maximum number of neighbors to use. default: 16
539
+ radius (int, optional): Search radius in map units. default: 1000
540
+ nodata (int, optional): NoData value for the output raster. default: -9999
541
+ out_dtype (AnalysisDataType, optional): Output data type.
542
+ user_id (int, optional): specific user. priviledges required!
543
+
544
+ Returns:
545
+ Task: task instance of the process
546
+
547
+ Example:
548
+ >>> from geobox import GeoboxClient
549
+ >>> from geobox.raster_analysis import RasterAnalysis
550
+ >>> client = GeoboxClient()
551
+ >>> vector = client.get_vector(uuid="12345678-1234-5678-1234-567812345678")
552
+ >>> field = vector.get_field_by_name('field_name')
553
+ >>> task = client.raster_analysis.idw_interpolation(layer=vector, output_raster_name='test', z_field=field)
554
+ or
555
+ >>> raster_analysis = RasterAnalysis(client)
556
+ >>> task = raster_analysis.idw_interpolation(layer=vector, output_raster_name='test', z_field=field)
557
+ """
558
+ endpoint = f'{self.BASE_ENDPOINT}idw/'
559
+
560
+ data = clean_data({
561
+ 'layer_uuid': layer.uuid,
562
+ 'output_raster_name': output_raster_name,
563
+ 'z_field': z_field.name,
564
+ 'is_view': False if isinstance(layer, VectorLayer) else True,
565
+ 'match_raster_uuid': match_raster_uuid,
566
+ 'pixel_size': pixel_size,
567
+ 'extent': extent,
568
+ 'power': power,
569
+ 'smoothing': smoothing,
570
+ 'max_points': max_points,
571
+ 'radius': radius,
572
+ 'nodata': nodata,
573
+ 'out_dtype': out_dtype.value,
574
+ 'user_id': user_id
575
+ })
576
+
577
+ response = self.api.post(endpoint=endpoint, payload=data, is_json=False)
578
+ return self.api.get_task(response['task_id'])
579
+
580
+
581
+ def constant(self,
582
+ output_raster_name: str,
583
+ extent: str,
584
+ value : int,
585
+ pixel_size: int = 10,
586
+ dtype: AnalysisDataType = AnalysisDataType.float32,
587
+ nodata: int = -9999,
588
+ align_to: Optional[str] = None,
589
+ user_id: Optional[int] = None) -> 'Task':
590
+ """
591
+ Create a raster filled with a constant value.
592
+
593
+ It creates a north-up GeoTIFF filled with a constant value.
594
+ Only users with Publisher role or higher can perform this operation.
595
+
596
+ Args:
597
+ output_raster_name (str): Name for the output constant raster dataset.
598
+ extent (str): Extent as 'minX,minY,maxX,maxY' (e.g., '0,0,100,100').
599
+ value (int): Constant value to fill the raster with.
600
+ pixel_size (int, optional): Pixel size for the output raster (must be > 0). default: 10
601
+ dtype (AnalysisDataType, optoinal): Output data type. default: AnalysisDataType.float32
602
+ nodata (int, optional): NoData value for the raster. default: -9999
603
+ align_to (str, optional): Grid origin to snap to as 'x0,y0' (e.g., '0,0').
604
+ user_id (int, optional): specific user. priviledges required!
605
+
606
+ Returns:
607
+ Task: task instance of the process
608
+
609
+ Example:
610
+ >>> from geobox import GeoboxClient
611
+ >>> from geobox.raster_analysis import RasterAnalysis
612
+ >>> client = GeoboxClient()
613
+ >>> task = client.raster_analysis.constant(output_raster_name='test', extent='0,0,100,100', value=10)
614
+ or
615
+ >>> raster_analysis = RasterAnalysis(client)
616
+ >>> task = raster_analysis.constant(output_raster_name='test', extent='0,0,100,100', value=10)
617
+ """
618
+ endpoint = f'{self.BASE_ENDPOINT}constant/'
619
+
620
+ data = clean_data({
621
+ 'output_raster_name': output_raster_name,
622
+ 'extent': extent,
623
+ 'value': value,
624
+ 'pixel_size': pixel_size,
625
+ 'dtype': dtype.value,
626
+ 'nodata': nodata,
627
+ 'align_to': align_to,
628
+ 'user_id': user_id
629
+ })
630
+
631
+ response = self.api.post(endpoint=endpoint, payload=data, is_json=False)
632
+ return self.api.get_task(response['task_id'])
633
+
634
+
635
+ def fill_nodata(self,
636
+ raster: 'Raster',
637
+ output_raster_name: str,
638
+ band: Union[int, str] = 1,
639
+ nodata: Optional[int] = None,
640
+ max_search_dist: Optional[int] = None,
641
+ smoothing_iterations: Optional[int] = None,
642
+ mask_raster_uuid: Optional[str] = None,
643
+ user_id: Optional[int] = None) -> 'Task':
644
+ """
645
+ Fill NoData regions in a raster using GDAL's FillNodata algorithm.
646
+
647
+ it fills gaps (NoData regions) in a raster by interpolating values from surrounding valid pixels.
648
+ This is commonly used for data cleaning and gap filling in remote sensing and elevation data.
649
+ Only users with Publisher role or higher can perform this operation.
650
+
651
+ Args:
652
+ raster (Raster): the input raster to fill NoData regions in
653
+ output_raster_name (str): Name for the output filled raster dataset.
654
+ band (int | str): 1-based band index to process or 'all' to process all bands. default: 1
655
+ nodata (int, optional): NoData value to use. If None, uses the band's existing NoData.
656
+ max_search_dist (int, optoinal): Maximum distance in pixels to search for valid data.
657
+ smoothing_iterations (int, optional): Number of smoothing iterations to apply.
658
+ mask_raster_uuid (str, optional): Optional UUID of a mask raster (0=masked, >0=valid).
659
+ user_id (int, optional): specific user. priviledges required!
660
+
661
+ Returns:
662
+ Task: task instance of the process
663
+
664
+ Example:
665
+ >>> from geobox import GeoboxClient
666
+ >>> from geobox.raster_analysis import RasterAnalysis
667
+ >>> client = GeoboxClient()
668
+ >>> raster = client.get_raster(uuid="12345678-1234-5678-1234-567812345678")
669
+ >>> task = client.raster_analysis.fill_nodata(raster=raster, output_raster_name='test')
670
+ or
671
+ >>> raster_analysis = RasterAnalysis(client)
672
+ >>> task = raster_analysis.fill_nodata(raster=raster, output_raster_name='test')
673
+ """
674
+ endpoint = f'{self.BASE_ENDPOINT}fill/'
675
+
676
+ data = clean_data({
677
+ 'raster_uuid': raster.uuid,
678
+ 'output_raster_name': output_raster_name,
679
+ 'band': band,
680
+ 'nodata': nodata,
681
+ 'max_search_dist': max_search_dist,
682
+ 'smoothing_iterations': smoothing_iterations,
683
+ 'mask_raster_uuid': mask_raster_uuid,
684
+ 'user_id': user_id
685
+ })
686
+
687
+ response = self.api.post(endpoint=endpoint, payload=data, is_json=False)
688
+ return self.api.get_task(response['task_id'])
689
+
690
+
691
+ def proximity(self,
692
+ raster: 'Raster',
693
+ output_raster_name: str,
694
+ dist_units: DistanceUnit = DistanceUnit.GEO,
695
+ burn_value: int = 1,
696
+ nodata: int = -9999,
697
+ user_id: Optional[int] = None) -> 'Task':
698
+ """
699
+ Create a proximity (distance) raster from a raster layer.
700
+
701
+ it creates a raster showing the distance from each pixel to the nearest pixel in the input raster layer.
702
+ Only users with Publisher role or higher can perform this operation.
703
+
704
+ Args:
705
+ raster (Raster): the raster layer to create proximity raster from.
706
+ output_raster_name (str): Name for the output proximity raster dataset.
707
+ dist_units (DistanceUnit, optional): Distance units: 'GEO' for georeferenced units, 'PIXEL' for pixels. default: DistanceUnit.GEO
708
+ burn_value (int, optional): Value treated as targets (distance 0). default: 1
709
+ nodata (int, optional): NoData value to use in the output raster. default: -9999
710
+ user_id (int, optional): specific user. priviledges required!
711
+
712
+ Returns:
713
+ Task: task instance of the process
714
+
715
+ Example:
716
+ >>> from geobox import GeoboxClient
717
+ >>> from geobox.raster_analysis import RasterAnalysis
718
+ >>> client = GeoboxClient()
719
+ >>> raster = client.get_raster(uuid="12345678-1234-5678-1234-567812345678")
720
+ >>> task = client.raster_analysis.proximity(raster=raster, output_raster_name='test')
721
+ or
722
+ >>> raster_analysis = RasterAnalysis(client)
723
+ >>> task = RasterAnalysis.proximity(raster=raster, output_raster_name='test')
724
+ """
725
+ endpoint = f'{self.BASE_ENDPOINT}proximity/'
726
+
727
+ data = clean_data({
728
+ 'raster_uuid': raster.uuid,
729
+ 'output_raster_name': output_raster_name,
730
+ 'dist_units': dist_units.value,
731
+ 'burn_value': burn_value,
732
+ 'nodata': nodata,
733
+ 'user_id': user_id
734
+ })
735
+
736
+ response = self.api.post(endpoint=endpoint, payload=data, is_json=False)
737
+ return self.api.get_task(response['task_id'])