terrakit 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. {terrakit-0.2.0 → terrakit-0.2.2}/PKG-INFO +1 -1
  2. {terrakit-0.2.0 → terrakit-0.2.2}/pyproject.toml +1 -1
  3. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/collections.json +6 -6
  4. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/data_connectors/nasa_earthdata.py +33 -12
  5. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/data_connectors/sentinelhub.py +39 -7
  6. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/geodata_utils.py +61 -29
  7. {terrakit-0.2.0 → terrakit-0.2.2}/README.md +0 -0
  8. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/__init__.py +0 -0
  9. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/__main__.py +0 -0
  10. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/_version.py +0 -0
  11. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/chip/__init__.py +0 -0
  12. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/chip/tiling.py +0 -0
  13. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/__init__.py +0 -0
  14. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/connector.py +0 -0
  15. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/data_connectors/__init__.py +0 -0
  16. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/data_connectors/cds_utils/cordex_domains.json +0 -0
  17. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/data_connectors/cds_utils/cordex_utils.py +0 -0
  18. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/data_connectors/cds_utils/derived-era5-single-levels-daily-statistics_constraints.json +0 -0
  19. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/data_connectors/cds_utils/projections-cordex-domains-single-levels_constraints.json +0 -0
  20. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/data_connectors/cds_utils/projections-cordex-domains-single-levels_constraints_variables.json +0 -0
  21. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/data_connectors/climate_data_store.py +0 -0
  22. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/data_connectors/connector_template.py +0 -0
  23. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/data_connectors/ibmresearch_stac.py +0 -0
  24. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/data_connectors/local_file_system.py +0 -0
  25. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/data_connectors/sentinel_aws.py +0 -0
  26. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/data_connectors/theweathercompany.py +0 -0
  27. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/download_data.py +0 -0
  28. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/raster_file_reader.py +0 -0
  29. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/transformations/__init__.py +0 -0
  30. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/transformations/download_transformation_template.py +0 -0
  31. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/transformations/impute_nans_xarray.py +0 -0
  32. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/download/transformations/scale_data_xarray.py +0 -0
  33. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/general_utils/__init__.py +0 -0
  34. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/general_utils/curation_metadata.py +0 -0
  35. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/general_utils/defaults.py +0 -0
  36. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/general_utils/exceptions.py +0 -0
  37. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/general_utils/geospatial_util.py +0 -0
  38. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/general_utils/labels_downloader.py +0 -0
  39. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/general_utils/plotting.py +0 -0
  40. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/general_utils/rest.py +0 -0
  41. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/general_utils/statistics.py +0 -0
  42. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/store/__init__.py +0 -0
  43. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/store/taco.py +0 -0
  44. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/terrakit.py +0 -0
  45. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/transform/__init__.py +0 -0
  46. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/transform/labels.py +0 -0
  47. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/upload/__init__.py +0 -0
  48. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/validate/__init__.py +0 -0
  49. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/validate/data_connector.py +0 -0
  50. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/validate/download_model.py +0 -0
  51. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/validate/helpers.py +0 -0
  52. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/validate/labels_model.py +0 -0
  53. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/validate/pipeline_model.py +0 -0
  54. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/validate/store_model.py +0 -0
  55. {terrakit-0.2.0 → terrakit-0.2.2}/terrakit/validate/tiling_model.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: terrakit
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: A Python package for finding and getting geospatial data.
5
5
  Author: Blair Edwards, Rosie Lickorish, Leonardo Pondian Tizzei, Fred Ochieng Otieno, Beldine Moturi, Catherine Wanjiru, Benedikt Blumenstiel, Brian Ogolla, Romeo Kienzler
6
6
  Author-email: Blair Edwards <bedwards@uk.ibm.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "TerraKit"
3
- version = "0.2.0"
3
+ version = "0.2.2"
4
4
  description = "A Python package for finding and getting geospatial data."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -86,7 +86,7 @@
86
86
  "B02"
87
87
  ],
88
88
  "resolution_m": 10,
89
- "query_template": "Template(\"\"\" //VERSION=3 \\n function setup() { return { input: [{ bands: ${bands}, units: \"DN\" }], output: { bands: ${num_bands}, sampleType: \"INT16\" } }; } function evaluatePixel(sample) { return ${band_samples}; } \"\"\")",
89
+ "query_template": " //VERSION=3 \\n function setup() { return { input: [{ bands: ${bands}, units: \"DN\" }], output: { bands: ${num_bands}, sampleType: \"INT16\" } }; } function evaluatePixel(sample) { return ${band_samples}; } ",
90
90
  "collection_name": "s2_l1c",
91
91
  "modality_tag": "S2L1C",
92
92
  "data_collection": "DataCollection.SENTINEL2_L1C",
@@ -111,7 +111,7 @@
111
111
  },
112
112
  "connector": "sentinelhub",
113
113
  "resolution_m": null,
114
- "query_template": "Template(\"\"\" //VERSION=3 \\n\nfunction setup() { return { input: [\"DEM\"], output:{ id: \"default\", bands: 1, sampleType: SampleType.FLOAT32 } } }\nfunction evaluatePixel(sample) { return [sample.DEM] } \"\"\")",
114
+ "query_template": " //VERSION=3 \\n\nfunction setup() { return { input: [\"DEM\"], output:{ id: \"default\", bands: 1, sampleType: SampleType.FLOAT32 } } }\nfunction evaluatePixel(sample) { return [sample.DEM] } ",
115
115
  "collection_name": "dem",
116
116
  "modality_tag": "DEM",
117
117
  "data_collection": "DataCollection.DEM_COPERNICUS_30",
@@ -151,7 +151,7 @@
151
151
  "connector": "sentinelhub",
152
152
  "modality_tag": "S1GRD",
153
153
  "resolution_m": 10,
154
- "query_template": "Template(\"\"\" //VERSION=3 \\n function setup() { return { input: [{ bands: ${bands}, units: \"LINEAR_POWER\" }], output: { bands: ${num_bands}, sampleType: \"FLOAT32\" } }; }\nfunction evaluatePixel(sample) { return ${band_samples}; } \"\"\")",
154
+ "query_template": " //VERSION=3 \\n function setup() { return { input: [{ bands: ${bands}, units: \"LINEAR_POWER\" }], output: { bands: ${num_bands}, sampleType: \"FLOAT32\" } }; }\nfunction evaluatePixel(sample) { return ${band_samples}; } ",
155
155
  "collection_name": "s1_grd",
156
156
  "data_collection": "DataCollection.SENTINEL1_IW",
157
157
  "request_input_data": {
@@ -241,7 +241,7 @@
241
241
  "band": "SCL",
242
242
  "encoding": "sentinel2_lulc"
243
243
  },
244
- "query_template": "Template(\"\"\" //VERSION=3 \\n function setup() { return { input: [{ bands: ${bands}, units: \"DN\" }], output: { bands: ${num_bands}, sampleType: \"INT16\" } }; } function evaluatePixel(sample) { return ${band_samples}; } \"\"\")",
244
+ "query_template": " //VERSION=3 \\n function setup() { return { input: [{ bands: ${bands}, units: \"DN\" }], output: { bands: ${num_bands}, sampleType: \"INT16\" } }; } function evaluatePixel(sample) { return ${band_samples}; } ",
245
245
  "collection_name": "hls_l30",
246
246
  "data_collection": "DataCollection.HARMONIZED_LANDSAT_SENTINEL",
247
247
  "request_input_data": {
@@ -352,7 +352,7 @@
352
352
  "band": "SCL",
353
353
  "encoding": "sentinel2_lulc"
354
354
  },
355
- "query_template": "Template(\"\"\" //VERSION=3 \\n function setup() { return { input: [{ bands: ${bands}, units: \"DN\" }], output: { bands: ${num_bands}, sampleType: \"INT16\" } }; } function evaluatePixel(sample) { return ${band_samples}; } \"\"\")",
355
+ "query_template": " //VERSION=3 \\n function setup() { return { input: [{ bands: ${bands}, units: \"DN\" }], output: { bands: ${num_bands}, sampleType: \"INT16\" } }; } function evaluatePixel(sample) { return ${band_samples}; } ",
356
356
  "collection_name": "s2_l2a",
357
357
  "data_collection": "DataCollection.SENTINEL2_L2A",
358
358
  "request_input_data": {
@@ -458,7 +458,7 @@
458
458
  "band": "SCL",
459
459
  "encoding": "sentinel2_lulc"
460
460
  },
461
- "query_template": "Template(\"\"\" //VERSION=3 function setup() { return { input: [{ bands: ${bands}, units: \"DN\" }], output: { bands: ${num_bands}, sampleType: \"INT16\" } }; } function evaluatePixel(sample) { return ${band_samples}; } \"\"\")",
461
+ "query_template": " //VERSION=3 function setup() { return { input: [{ bands: ${bands}, units: \"DN\" }], output: { bands: ${num_bands}, sampleType: \"INT16\" } }; } function evaluatePixel(sample) { return ${band_samples}; } ",
462
462
  "collection_name": "hls_s30",
463
463
  "data_collection": "DataCollection.HARMONIZED_LANDSAT_SENTINEL",
464
464
  "request_input_data": {
@@ -168,25 +168,46 @@ def get_band(date_items, band, bbox, temp_creds_req, working_dir):
168
168
  # Transform the bounding box to the CRS of the first link
169
169
  hls_bbox = list(rio.warp.transform_bounds("EPSG:4326", hls_proj, *bbox))
170
170
 
171
- # Prepare the gdalbuildvrt command based on whether a NASA_EARTH_BEARER_TOKEN is available
172
- if NASA_EARTH_BEARER_TOKEN:
173
- build_vrt = f"gdalbuildvrt {working_dir}/links_{band}_{date}.vrt -separate --config GDAL_HTTP_AUTH BEARER --config GDAL_HTTP_BEARER {NASA_EARTH_BEARER_TOKEN} --config AWS_ACCESS_KEY_ID {temp_creds_req['accessKeyId']} --config AWS_SECRET_ACCESS_KEY {temp_creds_req['secretAccessKey']} --config AWS_SESSION_TOKEN {temp_creds_req['sessionToken']} --config GDAL_DISABLE_READDIR_ON_OPEN TRUE"
174
- else:
175
- build_vrt = f"gdalbuildvrt {working_dir}/links_{band}_{date}.vrt -separate --config AWS_ACCESS_KEY_ID {temp_creds_req['accessKeyId']} --config AWS_SECRET_ACCESS_KEY {temp_creds_req['secretAccessKey']} --config AWS_SESSION_TOKEN {temp_creds_req['sessionToken']} --config GDAL_DISABLE_READDIR_ON_OPEN TRUE"
171
+ # Prepare the gdalbuildvrt argument list
172
+ vrt_path = os.path.join(working_dir, f"links_{band}_{date}.vrt")
173
+ build_vrt = ["gdalbuildvrt", vrt_path, "-separate"]
176
174
 
177
- # Combine the gdalbuildvrt command with the list of links
178
- build_vrt = " ".join([build_vrt] + links)
175
+ if NASA_EARTH_BEARER_TOKEN:
176
+ build_vrt += [
177
+ "--config",
178
+ "GDAL_HTTP_AUTH",
179
+ "BEARER",
180
+ "--config",
181
+ "GDAL_HTTP_BEARER",
182
+ NASA_EARTH_BEARER_TOKEN,
183
+ ]
179
184
 
180
- # Execute the gdalbuildvrt command using subprocess
181
- subprocess.call(build_vrt, shell=True)
185
+ build_vrt += [
186
+ "--config",
187
+ "AWS_ACCESS_KEY_ID",
188
+ temp_creds_req["accessKeyId"],
189
+ "--config",
190
+ "AWS_SECRET_ACCESS_KEY",
191
+ temp_creds_req["secretAccessKey"],
192
+ "--config",
193
+ "AWS_SESSION_TOKEN",
194
+ temp_creds_req["sessionToken"],
195
+ "--config",
196
+ "GDAL_DISABLE_READDIR_ON_OPEN",
197
+ "TRUE",
198
+ ]
199
+
200
+ # Each link is a separate list element
201
+ build_vrt += links
202
+
203
+ # Execute gdalbuildvrt directly
204
+ result = subprocess.run(build_vrt, check=True)
182
205
 
183
206
  # Define chunking parameters for efficient reading of the VRT
184
207
  chunks = dict(band=1, x=512, y=512)
185
208
 
186
209
  # Open the VRT file using rioxarray
187
- data = rioxarray.open_rasterio(
188
- f"{working_dir}/links_{band}_{date}.vrt", chunks=chunks
189
- )
210
+ data = rioxarray.open_rasterio(vrt_path, chunks=chunks)
190
211
 
191
212
  # Rename the 'band' dimension to 'time' and add a new 'band' dimension
192
213
  data = data.rename({"band": "time"})
@@ -5,6 +5,7 @@
5
5
  # Assisted by watsonx Code Assistant
6
6
 
7
7
  import os
8
+ import json
8
9
  import numpy as np
9
10
  import xarray as xr
10
11
  import rioxarray
@@ -93,7 +94,12 @@ def create_request(
93
94
  data_folder="./",
94
95
  maxcc=None,
95
96
  ):
96
- evalscript = eval(data_details["query_template"]).substitute(
97
+ qt = data_details["query_template"]
98
+ if not qt.strip().startswith("//VERSION=3"):
99
+ raise TerrakitValidationError(
100
+ "Invalid query_template value: does not start with //VERSION=3"
101
+ )
102
+ evalscript = Template(qt).substitute(
97
103
  {
98
104
  "bands": str(bands),
99
105
  "num_bands": len(bands),
@@ -101,17 +107,31 @@ def create_request(
101
107
  }
102
108
  )
103
109
 
110
+ dc_name = data_details["data_collection"] # e.g. "DataCollection.SENTINEL2_L1C"
111
+ dc_prefix = "DataCollection."
112
+ if not dc_name.startswith(dc_prefix):
113
+ raise TerrakitValidationError(f"Invalid data_collection value: {dc_name!r}")
114
+ member_name = dc_name[len(dc_prefix) :]
115
+ data_collection = getattr(DataCollection, member_name)
116
+
104
117
  shr = SentinelHubRequest.input_data(
105
- data_collection=eval(data_details["data_collection"]),
118
+ data_collection=data_collection,
106
119
  time_interval=(timestamp_start, timestamp_end),
107
120
  )
108
121
 
109
122
  if maxcc is not None:
110
123
  shr["maxCloudCoverage"] = maxcc / 100.0
111
124
  if "mosaicking_order" in data_details["request_input_data"]:
112
- shr["mosaickingOrder"] = eval(
113
- data_details["request_input_data"]["mosaicking_order"]
114
- ).value
125
+ mosaicking_order_name = data_details["request_input_data"][
126
+ "mosaicking_order"
127
+ ] # i.e. "MosaickingOrder.LEAST_CC"
128
+ mosaicking_order_prefix = "MosaickingOrder."
129
+ if not mosaicking_order_name.startswith(mosaicking_order_prefix):
130
+ raise TerrakitValidationError(
131
+ f"Invalid mosaicking_order value: {mosaicking_order_name!r}"
132
+ )
133
+ member_name = mosaicking_order_name[len(mosaicking_order_prefix) :]
134
+ shr["mosaickingOrder"] = getattr(MosaickingOrder, member_name).value
115
135
 
116
136
  logger.info(shr)
117
137
 
@@ -299,7 +319,14 @@ class SentinelHub(Connector):
299
319
  raise ValueError(error_msg)
300
320
  data_connector_spec = data_connector_spec_list[0]
301
321
 
302
- data_collection = eval(data_connector_spec["data_collection"])
322
+ dc_name = data_connector_spec[
323
+ "data_collection"
324
+ ] # e.g. "DataCollection.SENTINEL2_L1C"
325
+ dc_prefix = "DataCollection."
326
+ if not dc_name.startswith(dc_prefix):
327
+ raise TerrakitValidationError(f"Invalid data_collection value: {dc_name!r}")
328
+ member_name = dc_name[len(dc_prefix) :]
329
+ data_collection = getattr(DataCollection, member_name)
303
330
 
304
331
  self.sh_config.sh_base_url = data_collection.service_url
305
332
  logger.info(self.sh_config.sh_base_url)
@@ -319,7 +346,12 @@ class SentinelHub(Connector):
319
346
  filter_string = ""
320
347
 
321
348
  if "fields" in data_connector_spec["search"]:
322
- fields_dict = eval(data_connector_spec["search"]["fields"])
349
+ fields_str = data_connector_spec["search"]["fields"]
350
+ if len(fields_str) > 1024:
351
+ raise TerrakitValidationError(
352
+ "search.fields value exceeds maximum allowed length"
353
+ )
354
+ fields_dict = json.loads(fields_str)
323
355
 
324
356
  else:
325
357
  fields_dict = {"include": ["id", "properties.datetime"], "exclude": []}
@@ -23,6 +23,7 @@ from sentinelhub import (
23
23
  bbox_to_dimensions,
24
24
  )
25
25
  from pathlib import Path
26
+ from rasterio.warp import calculate_default_transform, reproject, Resampling
26
27
  from shapely.geometry import shape
27
28
  from typing import Any, Dict, Union
28
29
 
@@ -306,44 +307,75 @@ def verify_input_image(image, standard_dimensions=224) -> typing.Tuple[int, str]
306
307
  Return:
307
308
  tuple[int, str]: [verification_status_code, verification_msg]
308
309
  """
309
- res = os.popen(f"gdalinfo {image} -json").read()
310
- res_json = json.loads(res)
311
- dims = res_json["size"]
310
+ try:
311
+ with rasterio.open(image) as src:
312
+ dims = (src.height, src.width)
312
313
 
313
- # Check if image is geotiff
314
- if res_json["driverShortName"] != "GTiff":
315
- return 1007, f"Input {image} is not a GeoTiff."
314
+ # Check if image is geotiff
315
+ if src.driver != "GTiff":
316
+ return 1007, f"Input {image} is not a GeoTiff."
316
317
 
317
- # Check image dimensions
318
- image_input_dimensions = np.min(dims)
318
+ # Check image dimensions
319
+ image_input_dimensions = np.min(dims)
319
320
 
320
- if image_input_dimensions < standard_dimensions:
321
- return (
322
- 1002,
323
- f"Input image too small for image {image} with dimensions {image_input_dimensions}. Both dimensions must be >= 224.",
324
- )
325
- else:
326
- # Log/Show dimensions of the input image
327
- logger.debug(f"Input image {image} has dimensions {image_input_dimensions}")
328
- return 200, str(image_input_dimensions)
321
+ if image_input_dimensions < standard_dimensions:
322
+ return (
323
+ 1002,
324
+ f"Input image too small for image {image} with dimensions {image_input_dimensions}. Both dimensions must be >= 224.",
325
+ )
326
+ else:
327
+ # Log/Show dimensions of the input image
328
+ logger.debug(
329
+ f"Input image {image} has dimensions {image_input_dimensions}"
330
+ )
331
+ return 200, str(image_input_dimensions)
332
+ except rasterio.errors.RasterioIOError as e:
333
+ return 1001, f"Failed to open image {image}: {e}"
329
334
 
330
335
 
331
- def check_projection(file):
336
+ def check_projection(file: Union[str, Path]) -> None:
332
337
  """
333
- Check the projection is correct, if not reproject to EPSG:4326
338
+ Check the projection is correct, if not reproject to EPSG:4326.
334
339
 
335
340
  Parameters:
336
- file (str): The path to the input file.
337
-
338
- Returns:
339
- None
341
+ file (str | Path): The path to the input file.
340
342
  """
341
- res = os.popen(f"gdalinfo {file} -proj4 -json").read()
342
- res_json = json.loads(res)
343
- # WGS84 is the same as EPSG:4326
344
- if res_json["stac"]["proj:epsg"] != 4326:
345
- os.system(f"gdalwarp {file} -t_srs EPSG:4326 {file}_reprojected.tif")
346
- os.system(f"mv {file}_reprojected.tif {file} ")
343
+ file_path = Path(file)
344
+ target_crs = CRS.from_epsg(4326)
345
+
346
+ with rasterio.open(file_path) as src:
347
+ # Check if already in EPSG:4326
348
+ if src.crs == target_crs or (src.crs and src.crs.to_epsg() == 4326):
349
+ return
350
+
351
+ transform, width, height = calculate_default_transform(
352
+ src.crs, target_crs, src.width, src.height, *src.bounds
353
+ )
354
+ kwargs = src.meta.copy()
355
+ kwargs.update(
356
+ {
357
+ "crs": target_crs,
358
+ "transform": transform,
359
+ "width": width,
360
+ "height": height,
361
+ }
362
+ )
363
+
364
+ temp_file = file_path.with_name(f"{file_path.stem}_reprojected.tif")
365
+ with rasterio.open(temp_file, "w", **kwargs) as dst:
366
+ for i in range(1, src.count + 1):
367
+ reproject(
368
+ source=rasterio.band(src, i),
369
+ destination=rasterio.band(dst, i),
370
+ src_transform=src.transform,
371
+ src_crs=src.crs,
372
+ dst_transform=transform,
373
+ dst_crs=target_crs,
374
+ resampling=Resampling.nearest,
375
+ )
376
+
377
+ # Atomically replace original file with reprojected file
378
+ temp_file.replace(file_path)
347
379
 
348
380
 
349
381
  def pad_bbox(padding_degrees, bbox):
File without changes
File without changes
File without changes
File without changes
File without changes