rslearn 0.0.1__py3-none-any.whl → 0.0.21__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.
- rslearn/arg_parser.py +31 -0
- rslearn/config/__init__.py +6 -12
- rslearn/config/dataset.py +520 -401
- rslearn/const.py +9 -15
- rslearn/data_sources/__init__.py +8 -23
- rslearn/data_sources/aws_landsat.py +242 -98
- rslearn/data_sources/aws_open_data.py +111 -151
- rslearn/data_sources/aws_sentinel1.py +131 -0
- rslearn/data_sources/climate_data_store.py +471 -0
- rslearn/data_sources/copernicus.py +884 -12
- rslearn/data_sources/data_source.py +43 -12
- rslearn/data_sources/earthdaily.py +484 -0
- rslearn/data_sources/earthdata_srtm.py +282 -0
- rslearn/data_sources/eurocrops.py +242 -0
- rslearn/data_sources/gcp_public_data.py +578 -222
- rslearn/data_sources/google_earth_engine.py +461 -135
- rslearn/data_sources/local_files.py +219 -150
- rslearn/data_sources/openstreetmap.py +51 -89
- rslearn/data_sources/planet.py +24 -60
- rslearn/data_sources/planet_basemap.py +275 -0
- rslearn/data_sources/planetary_computer.py +798 -0
- rslearn/data_sources/usda_cdl.py +195 -0
- rslearn/data_sources/usgs_landsat.py +115 -83
- rslearn/data_sources/utils.py +249 -61
- rslearn/data_sources/vector_source.py +1 -0
- rslearn/data_sources/worldcereal.py +449 -0
- rslearn/data_sources/worldcover.py +144 -0
- rslearn/data_sources/worldpop.py +153 -0
- rslearn/data_sources/xyz_tiles.py +150 -107
- rslearn/dataset/__init__.py +8 -2
- rslearn/dataset/add_windows.py +2 -2
- rslearn/dataset/dataset.py +40 -51
- rslearn/dataset/handler_summaries.py +131 -0
- rslearn/dataset/manage.py +313 -74
- rslearn/dataset/materialize.py +431 -107
- rslearn/dataset/remap.py +29 -4
- rslearn/dataset/storage/__init__.py +1 -0
- rslearn/dataset/storage/file.py +202 -0
- rslearn/dataset/storage/storage.py +140 -0
- rslearn/dataset/window.py +181 -44
- rslearn/lightning_cli.py +454 -0
- rslearn/log_utils.py +24 -0
- rslearn/main.py +384 -181
- rslearn/models/anysat.py +215 -0
- rslearn/models/attention_pooling.py +177 -0
- rslearn/models/clay/clay.py +231 -0
- rslearn/models/clay/configs/metadata.yaml +295 -0
- rslearn/models/clip.py +68 -0
- rslearn/models/component.py +111 -0
- rslearn/models/concatenate_features.py +103 -0
- rslearn/models/conv.py +63 -0
- rslearn/models/croma.py +306 -0
- rslearn/models/detr/__init__.py +5 -0
- rslearn/models/detr/box_ops.py +103 -0
- rslearn/models/detr/detr.py +504 -0
- rslearn/models/detr/matcher.py +107 -0
- rslearn/models/detr/position_encoding.py +114 -0
- rslearn/models/detr/transformer.py +429 -0
- rslearn/models/detr/util.py +24 -0
- rslearn/models/dinov3.py +177 -0
- rslearn/models/faster_rcnn.py +30 -28
- rslearn/models/feature_center_crop.py +53 -0
- rslearn/models/fpn.py +19 -8
- rslearn/models/galileo/__init__.py +5 -0
- rslearn/models/galileo/galileo.py +595 -0
- rslearn/models/galileo/single_file_galileo.py +1678 -0
- rslearn/models/module_wrapper.py +65 -0
- rslearn/models/molmo.py +69 -0
- rslearn/models/multitask.py +384 -28
- rslearn/models/olmoearth_pretrain/__init__.py +1 -0
- rslearn/models/olmoearth_pretrain/model.py +421 -0
- rslearn/models/olmoearth_pretrain/norm.py +86 -0
- rslearn/models/panopticon.py +170 -0
- rslearn/models/panopticon_data/sensors/drone.yaml +32 -0
- rslearn/models/panopticon_data/sensors/enmap.yaml +904 -0
- rslearn/models/panopticon_data/sensors/goes.yaml +9 -0
- rslearn/models/panopticon_data/sensors/himawari.yaml +9 -0
- rslearn/models/panopticon_data/sensors/intuition.yaml +606 -0
- rslearn/models/panopticon_data/sensors/landsat8.yaml +84 -0
- rslearn/models/panopticon_data/sensors/modis_terra.yaml +99 -0
- rslearn/models/panopticon_data/sensors/qb2_ge1.yaml +34 -0
- rslearn/models/panopticon_data/sensors/sentinel1.yaml +85 -0
- rslearn/models/panopticon_data/sensors/sentinel2.yaml +97 -0
- rslearn/models/panopticon_data/sensors/superdove.yaml +60 -0
- rslearn/models/panopticon_data/sensors/wv23.yaml +63 -0
- rslearn/models/pick_features.py +17 -10
- rslearn/models/pooling_decoder.py +60 -7
- rslearn/models/presto/__init__.py +5 -0
- rslearn/models/presto/presto.py +297 -0
- rslearn/models/presto/single_file_presto.py +926 -0
- rslearn/models/prithvi.py +1147 -0
- rslearn/models/resize_features.py +59 -0
- rslearn/models/sam2_enc.py +13 -9
- rslearn/models/satlaspretrain.py +38 -18
- rslearn/models/simple_time_series.py +188 -77
- rslearn/models/singletask.py +24 -13
- rslearn/models/ssl4eo_s12.py +40 -30
- rslearn/models/swin.py +44 -32
- rslearn/models/task_embedding.py +250 -0
- rslearn/models/terramind.py +256 -0
- rslearn/models/trunk.py +139 -0
- rslearn/models/unet.py +68 -22
- rslearn/models/upsample.py +48 -0
- rslearn/models/use_croma.py +508 -0
- rslearn/template_params.py +26 -0
- rslearn/tile_stores/__init__.py +41 -18
- rslearn/tile_stores/default.py +409 -0
- rslearn/tile_stores/tile_store.py +236 -132
- rslearn/train/all_patches_dataset.py +530 -0
- rslearn/train/callbacks/adapters.py +53 -0
- rslearn/train/callbacks/freeze_unfreeze.py +348 -17
- rslearn/train/callbacks/gradients.py +129 -0
- rslearn/train/callbacks/peft.py +116 -0
- rslearn/train/data_module.py +444 -20
- rslearn/train/dataset.py +588 -235
- rslearn/train/lightning_module.py +192 -62
- rslearn/train/model_context.py +88 -0
- rslearn/train/optimizer.py +31 -0
- rslearn/train/prediction_writer.py +319 -84
- rslearn/train/scheduler.py +92 -0
- rslearn/train/tasks/classification.py +55 -28
- rslearn/train/tasks/detection.py +132 -76
- rslearn/train/tasks/embedding.py +120 -0
- rslearn/train/tasks/multi_task.py +28 -14
- rslearn/train/tasks/per_pixel_regression.py +291 -0
- rslearn/train/tasks/regression.py +161 -44
- rslearn/train/tasks/segmentation.py +428 -53
- rslearn/train/tasks/task.py +6 -5
- rslearn/train/transforms/__init__.py +1 -1
- rslearn/train/transforms/concatenate.py +54 -10
- rslearn/train/transforms/crop.py +29 -11
- rslearn/train/transforms/flip.py +18 -6
- rslearn/train/transforms/mask.py +78 -0
- rslearn/train/transforms/normalize.py +101 -17
- rslearn/train/transforms/pad.py +19 -7
- rslearn/train/transforms/resize.py +83 -0
- rslearn/train/transforms/select_bands.py +76 -0
- rslearn/train/transforms/sentinel1.py +75 -0
- rslearn/train/transforms/transform.py +89 -70
- rslearn/utils/__init__.py +2 -6
- rslearn/utils/array.py +8 -6
- rslearn/utils/feature.py +2 -2
- rslearn/utils/fsspec.py +90 -1
- rslearn/utils/geometry.py +347 -7
- rslearn/utils/get_utm_ups_crs.py +2 -3
- rslearn/utils/grid_index.py +5 -5
- rslearn/utils/jsonargparse.py +178 -0
- rslearn/utils/mp.py +4 -3
- rslearn/utils/raster_format.py +268 -116
- rslearn/utils/rtree_index.py +64 -17
- rslearn/utils/sqlite_index.py +7 -1
- rslearn/utils/vector_format.py +252 -97
- {rslearn-0.0.1.dist-info → rslearn-0.0.21.dist-info}/METADATA +532 -283
- rslearn-0.0.21.dist-info/RECORD +167 -0
- {rslearn-0.0.1.dist-info → rslearn-0.0.21.dist-info}/WHEEL +1 -1
- rslearn-0.0.21.dist-info/licenses/NOTICE +115 -0
- rslearn/data_sources/raster_source.py +0 -309
- rslearn/models/registry.py +0 -5
- rslearn/tile_stores/file.py +0 -242
- rslearn/utils/mgrs.py +0 -24
- rslearn/utils/utils.py +0 -22
- rslearn-0.0.1.dist-info/RECORD +0 -88
- /rslearn/{data_sources/geotiff.py → py.typed} +0 -0
- {rslearn-0.0.1.dist-info → rslearn-0.0.21.dist-info}/entry_points.txt +0 -0
- {rslearn-0.0.1.dist-info → rslearn-0.0.21.dist-info/licenses}/LICENSE +0 -0
- {rslearn-0.0.1.dist-info → rslearn-0.0.21.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
"""Default TileStore implementation."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import math
|
|
5
|
+
import shutil
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import numpy.typing as npt
|
|
9
|
+
import rasterio.transform
|
|
10
|
+
import rasterio.vrt
|
|
11
|
+
import shapely
|
|
12
|
+
from rasterio.enums import Resampling
|
|
13
|
+
from upath import UPath
|
|
14
|
+
|
|
15
|
+
from rslearn.const import WGS84_PROJECTION
|
|
16
|
+
from rslearn.utils.feature import Feature
|
|
17
|
+
from rslearn.utils.fsspec import (
|
|
18
|
+
join_upath,
|
|
19
|
+
open_atomic,
|
|
20
|
+
open_rasterio_upath_reader,
|
|
21
|
+
open_rasterio_upath_writer,
|
|
22
|
+
)
|
|
23
|
+
from rslearn.utils.geometry import PixelBounds, Projection, STGeometry
|
|
24
|
+
from rslearn.utils.get_utm_ups_crs import get_utm_ups_crs
|
|
25
|
+
from rslearn.utils.raster_format import (
|
|
26
|
+
GeotiffRasterFormat,
|
|
27
|
+
get_bandset_dirname,
|
|
28
|
+
)
|
|
29
|
+
from rslearn.utils.vector_format import (
|
|
30
|
+
GeojsonVectorFormat,
|
|
31
|
+
VectorFormat,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
from .tile_store import TileStore
|
|
35
|
+
|
|
36
|
+
# Special filename to indicate writing is done.
|
|
37
|
+
COMPLETED_FNAME = "completed"
|
|
38
|
+
|
|
39
|
+
# Special filename to store the bands that are present in a raster.
|
|
40
|
+
BANDS_FNAME = "bands.json"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class DefaultTileStore(TileStore):
|
|
44
|
+
"""Default TileStore implementation.
|
|
45
|
+
|
|
46
|
+
It stores raster and vector data under the provided UPath.
|
|
47
|
+
|
|
48
|
+
Raster data is always stored as a geo-referenced image, while vector data can use
|
|
49
|
+
any provided VectorFormat. This is because for raster data we support reading in an
|
|
50
|
+
arbitrary projection, but this is not supported in GeotiffRasterFormat, so we
|
|
51
|
+
directly use rasterio to access the file.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
path_suffix: str = "tiles",
|
|
57
|
+
convert_rasters_to_cogs: bool = True,
|
|
58
|
+
tile_size: int = 256,
|
|
59
|
+
geotiff_options: dict[str, Any] = {},
|
|
60
|
+
vector_format: VectorFormat = GeojsonVectorFormat(),
|
|
61
|
+
):
|
|
62
|
+
"""Create a new DefaultTileStore.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
path_suffix: the path suffix to store files under, which is joined with
|
|
66
|
+
the dataset path if it does not contain a protocol string. See
|
|
67
|
+
rslearn.utils.fsspec.join_upath.
|
|
68
|
+
convert_rasters_to_cogs: whether to re-encode all raster files to tiled
|
|
69
|
+
GeoTIFFs.
|
|
70
|
+
tile_size: if converting to COGs, the tile size to use.
|
|
71
|
+
geotiff_options: other options to pass to rasterio.open (for writes).
|
|
72
|
+
vector_format: format to use for storing vector data.
|
|
73
|
+
"""
|
|
74
|
+
self.path_suffix = path_suffix
|
|
75
|
+
self.convert_rasters_to_cogs = convert_rasters_to_cogs
|
|
76
|
+
self.tile_size = tile_size
|
|
77
|
+
self.geotiff_options = geotiff_options
|
|
78
|
+
self.vector_format = vector_format
|
|
79
|
+
|
|
80
|
+
self.path: UPath | None = None
|
|
81
|
+
|
|
82
|
+
def set_dataset_path(self, ds_path: UPath) -> None:
|
|
83
|
+
"""Set the dataset path.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
ds_path: the dataset path.
|
|
87
|
+
"""
|
|
88
|
+
self.path = join_upath(ds_path, self.path_suffix)
|
|
89
|
+
|
|
90
|
+
def _get_raster_dir(
|
|
91
|
+
self, layer_name: str, item_name: str, bands: list[str], write: bool = False
|
|
92
|
+
) -> UPath:
|
|
93
|
+
"""Get the directory where the specified raster is stored.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
layer_name: the name of the dataset layer.
|
|
97
|
+
item_name: the name of the item from the data source.
|
|
98
|
+
bands: list of band names that are expected to be stored together.
|
|
99
|
+
write: whether to create the directory and write the bands to a file inside
|
|
100
|
+
the directory.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
the UPath directory where the raster should be stored.
|
|
104
|
+
"""
|
|
105
|
+
assert self.path is not None
|
|
106
|
+
dir_name = self.path / layer_name / item_name / get_bandset_dirname(bands)
|
|
107
|
+
|
|
108
|
+
if write:
|
|
109
|
+
dir_name.mkdir(parents=True, exist_ok=True)
|
|
110
|
+
with (dir_name / BANDS_FNAME).open("w") as f:
|
|
111
|
+
json.dump(bands, f)
|
|
112
|
+
|
|
113
|
+
return dir_name
|
|
114
|
+
|
|
115
|
+
def _get_raster_fname(
|
|
116
|
+
self, layer_name: str, item_name: str, bands: list[str]
|
|
117
|
+
) -> UPath:
|
|
118
|
+
"""Get the filename of the specified raster.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
layer_name: the name of the dataset layer.
|
|
122
|
+
item_name: the name of the item from the data source.
|
|
123
|
+
bands: list of band names that are expected to be stored together.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
the UPath filename of the raster, which should be readable by rasterio.
|
|
127
|
+
|
|
128
|
+
Raises:
|
|
129
|
+
ValueError: if no file is found.
|
|
130
|
+
"""
|
|
131
|
+
raster_dir = self._get_raster_dir(layer_name, item_name, bands)
|
|
132
|
+
for fname in raster_dir.iterdir():
|
|
133
|
+
# Ignore completed sentinel files, bands files, as well as temporary files created by
|
|
134
|
+
# open_atomic (in case this tile store is on local filesystem).
|
|
135
|
+
if fname.name == COMPLETED_FNAME:
|
|
136
|
+
continue
|
|
137
|
+
if fname.name == BANDS_FNAME:
|
|
138
|
+
continue
|
|
139
|
+
if ".tmp." in fname.name:
|
|
140
|
+
continue
|
|
141
|
+
return fname
|
|
142
|
+
raise ValueError(f"no raster found in {raster_dir}")
|
|
143
|
+
|
|
144
|
+
def is_raster_ready(
|
|
145
|
+
self, layer_name: str, item_name: str, bands: list[str]
|
|
146
|
+
) -> bool:
|
|
147
|
+
"""Checks if this raster has been written to the store.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
layer_name: the layer name or alias.
|
|
151
|
+
item_name: the item.
|
|
152
|
+
bands: the list of bands identifying which specific raster to read.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
whether there is a raster in the store matching the source, item, and
|
|
156
|
+
bands.
|
|
157
|
+
"""
|
|
158
|
+
raster_dir = self._get_raster_dir(layer_name, item_name, bands)
|
|
159
|
+
return (raster_dir / COMPLETED_FNAME).exists()
|
|
160
|
+
|
|
161
|
+
def get_raster_bands(self, layer_name: str, item_name: str) -> list[list[str]]:
|
|
162
|
+
"""Get the sets of bands that have been stored for the specified item.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
layer_name: the layer name or alias.
|
|
166
|
+
item_name: the item.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
a list of lists of bands that are in the tile store (with one raster
|
|
170
|
+
stored corresponding to each inner list).
|
|
171
|
+
"""
|
|
172
|
+
assert isinstance(self.path, UPath)
|
|
173
|
+
item_dir = self.path / layer_name / item_name
|
|
174
|
+
if not item_dir.exists():
|
|
175
|
+
return []
|
|
176
|
+
|
|
177
|
+
bands: list[list[str]] = []
|
|
178
|
+
for raster_dir in item_dir.iterdir():
|
|
179
|
+
if not (raster_dir / BANDS_FNAME).exists():
|
|
180
|
+
# This is likely a legacy directory where the bands are only encoded in
|
|
181
|
+
# the directory name, so we have to rely on that.
|
|
182
|
+
parts = raster_dir.name.split("_")
|
|
183
|
+
bands.append(parts)
|
|
184
|
+
continue
|
|
185
|
+
|
|
186
|
+
# We use the BANDS_FNAME here -- although it is slower to read the file, it
|
|
187
|
+
# is more reliable since sometimes the directory name is a hash of the
|
|
188
|
+
# bands in case there are too many bands (filename too long) or some bands
|
|
189
|
+
# contain the underscore character.
|
|
190
|
+
with (raster_dir / BANDS_FNAME).open() as f:
|
|
191
|
+
bands.append(json.load(f))
|
|
192
|
+
|
|
193
|
+
return bands
|
|
194
|
+
|
|
195
|
+
def get_raster_bounds(
|
|
196
|
+
self, layer_name: str, item_name: str, bands: list[str], projection: Projection
|
|
197
|
+
) -> PixelBounds:
|
|
198
|
+
"""Get the bounds of the raster in the specified projection.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
layer_name: the layer name or alias.
|
|
202
|
+
item_name: the item to check.
|
|
203
|
+
bands: the list of bands identifying which specific raster to read. These
|
|
204
|
+
bands must match the bands of a stored raster.
|
|
205
|
+
projection: the projection to get the raster's bounds in.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
the bounds of the raster in the projection.
|
|
209
|
+
"""
|
|
210
|
+
raster_fname = self._get_raster_fname(layer_name, item_name, bands)
|
|
211
|
+
|
|
212
|
+
with open_rasterio_upath_reader(raster_fname) as src:
|
|
213
|
+
with rasterio.vrt.WarpedVRT(src, crs=projection.crs) as vrt:
|
|
214
|
+
bounds = (
|
|
215
|
+
vrt.bounds[0] / projection.x_resolution,
|
|
216
|
+
vrt.bounds[1] / projection.y_resolution,
|
|
217
|
+
vrt.bounds[2] / projection.x_resolution,
|
|
218
|
+
vrt.bounds[3] / projection.y_resolution,
|
|
219
|
+
)
|
|
220
|
+
return (
|
|
221
|
+
math.floor(min(bounds[0], bounds[2])),
|
|
222
|
+
math.floor(min(bounds[1], bounds[3])),
|
|
223
|
+
math.ceil(max(bounds[0], bounds[2])),
|
|
224
|
+
math.ceil(max(bounds[1], bounds[3])),
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
def read_raster(
|
|
228
|
+
self,
|
|
229
|
+
layer_name: str,
|
|
230
|
+
item_name: str,
|
|
231
|
+
bands: list[str],
|
|
232
|
+
projection: Projection,
|
|
233
|
+
bounds: PixelBounds,
|
|
234
|
+
resampling: Resampling = Resampling.bilinear,
|
|
235
|
+
) -> npt.NDArray[Any]:
|
|
236
|
+
"""Read raster data from the store.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
layer_name: the layer name or alias.
|
|
240
|
+
item_name: the item to read.
|
|
241
|
+
bands: the list of bands identifying which specific raster to read. These
|
|
242
|
+
bands must match the bands of a stored raster.
|
|
243
|
+
projection: the projection to read in.
|
|
244
|
+
bounds: the bounds to read.
|
|
245
|
+
resampling: resampling method to use in case resampling is needed.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
the raster data
|
|
249
|
+
"""
|
|
250
|
+
raster_fname = self._get_raster_fname(layer_name, item_name, bands)
|
|
251
|
+
return GeotiffRasterFormat().decode_raster(
|
|
252
|
+
path=raster_fname.parent,
|
|
253
|
+
fname=raster_fname.name,
|
|
254
|
+
projection=projection,
|
|
255
|
+
bounds=bounds,
|
|
256
|
+
resampling=resampling,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
def write_raster(
|
|
260
|
+
self,
|
|
261
|
+
layer_name: str,
|
|
262
|
+
item_name: str,
|
|
263
|
+
bands: list[str],
|
|
264
|
+
projection: Projection,
|
|
265
|
+
bounds: PixelBounds,
|
|
266
|
+
array: npt.NDArray[Any],
|
|
267
|
+
) -> None:
|
|
268
|
+
"""Write raster data to the store.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
layer_name: the layer name or alias.
|
|
272
|
+
item_name: the item to write.
|
|
273
|
+
bands: the list of bands in the array.
|
|
274
|
+
projection: the projection of the array.
|
|
275
|
+
bounds: the bounds of the array.
|
|
276
|
+
array: the raster data.
|
|
277
|
+
"""
|
|
278
|
+
raster_dir = self._get_raster_dir(layer_name, item_name, bands, write=True)
|
|
279
|
+
raster_format = GeotiffRasterFormat(geotiff_options=self.geotiff_options)
|
|
280
|
+
raster_format.encode_raster(raster_dir, projection, bounds, array)
|
|
281
|
+
(raster_dir / COMPLETED_FNAME).touch()
|
|
282
|
+
|
|
283
|
+
def write_raster_file(
|
|
284
|
+
self, layer_name: str, item_name: str, bands: list[str], fname: UPath
|
|
285
|
+
) -> None:
|
|
286
|
+
"""Write raster data to the store.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
layer_name: the layer name or alias.
|
|
290
|
+
item_name: the item to write.
|
|
291
|
+
bands: the list of bands in the array.
|
|
292
|
+
fname: the raster file, which must be readable by rasterio.
|
|
293
|
+
"""
|
|
294
|
+
raster_dir = self._get_raster_dir(layer_name, item_name, bands, write=True)
|
|
295
|
+
raster_dir.mkdir(parents=True, exist_ok=True)
|
|
296
|
+
|
|
297
|
+
if self.convert_rasters_to_cogs:
|
|
298
|
+
with open_rasterio_upath_reader(fname) as src:
|
|
299
|
+
profile = src.profile
|
|
300
|
+
array = src.read()
|
|
301
|
+
|
|
302
|
+
# If raster specifies ground control points, use WarpedVRT to get it in
|
|
303
|
+
# an appropriate projection.
|
|
304
|
+
# Previously we used rasterio.transform.from_gcps(gcps) but I think the
|
|
305
|
+
# problem is that it computes one transform for the entire raster but
|
|
306
|
+
# the raster might actually need warping.
|
|
307
|
+
if profile["crs"] is None and src.gcps:
|
|
308
|
+
gcps, gcp_crs = src.gcps
|
|
309
|
+
# Use the first ground control point to pick a UTM/UPS projection.
|
|
310
|
+
first_gcp_orig = STGeometry(
|
|
311
|
+
Projection(gcp_crs, 1, 1),
|
|
312
|
+
shapely.Point(gcps[0].x, gcps[0].y),
|
|
313
|
+
None,
|
|
314
|
+
)
|
|
315
|
+
first_gcp_wgs84 = first_gcp_orig.to_projection(WGS84_PROJECTION)
|
|
316
|
+
crs = get_utm_ups_crs(first_gcp_wgs84.shp.x, first_gcp_wgs84.shp.y)
|
|
317
|
+
with rasterio.vrt.WarpedVRT(
|
|
318
|
+
src, crs=crs, resampling=Resampling.cubic
|
|
319
|
+
) as vrt:
|
|
320
|
+
array = vrt.read()
|
|
321
|
+
transform = vrt.transform
|
|
322
|
+
|
|
323
|
+
else:
|
|
324
|
+
crs = profile["crs"]
|
|
325
|
+
transform = profile["transform"]
|
|
326
|
+
|
|
327
|
+
output_profile = {
|
|
328
|
+
"driver": "GTiff",
|
|
329
|
+
"compress": "lzw",
|
|
330
|
+
"width": array.shape[2],
|
|
331
|
+
"height": array.shape[1],
|
|
332
|
+
"count": array.shape[0],
|
|
333
|
+
"dtype": array.dtype.name,
|
|
334
|
+
"crs": crs,
|
|
335
|
+
"transform": transform,
|
|
336
|
+
"BIGTIFF": "IF_SAFER",
|
|
337
|
+
"tiled": True,
|
|
338
|
+
"blockxsize": self.tile_size,
|
|
339
|
+
"blockysize": self.tile_size,
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
output_profile.update(self.geotiff_options)
|
|
343
|
+
|
|
344
|
+
with open_rasterio_upath_writer(
|
|
345
|
+
raster_dir / "geotiff.tif", **output_profile
|
|
346
|
+
) as dst:
|
|
347
|
+
dst.write(array)
|
|
348
|
+
|
|
349
|
+
else:
|
|
350
|
+
# Just copy the file directly.
|
|
351
|
+
dst_fname = raster_dir / fname.name
|
|
352
|
+
with fname.open("rb") as src:
|
|
353
|
+
with open_atomic(dst_fname, "wb") as dst:
|
|
354
|
+
shutil.copyfileobj(src, dst)
|
|
355
|
+
|
|
356
|
+
(raster_dir / COMPLETED_FNAME).touch()
|
|
357
|
+
|
|
358
|
+
def _get_vector_dir(self, layer_name: str, item_name: str) -> UPath:
|
|
359
|
+
assert self.path is not None
|
|
360
|
+
return self.path / layer_name / item_name
|
|
361
|
+
|
|
362
|
+
def is_vector_ready(self, layer_name: str, item_name: str) -> bool:
|
|
363
|
+
"""Checks if this vector item has been written to the store.
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
layer_name: the layer name or alias.
|
|
367
|
+
item_name: the item.
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
whether the vector data from the item has been stored.
|
|
371
|
+
"""
|
|
372
|
+
vector_dir = self._get_vector_dir(layer_name, item_name)
|
|
373
|
+
return (vector_dir / COMPLETED_FNAME).exists()
|
|
374
|
+
|
|
375
|
+
def read_vector(
|
|
376
|
+
self,
|
|
377
|
+
layer_name: str,
|
|
378
|
+
item_name: str,
|
|
379
|
+
projection: Projection,
|
|
380
|
+
bounds: PixelBounds,
|
|
381
|
+
) -> list[Feature]:
|
|
382
|
+
"""Read vector data from the store.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
layer_name: the layer name or alias.
|
|
386
|
+
item_name: the item to read.
|
|
387
|
+
projection: the projection to read in.
|
|
388
|
+
bounds: the bounds within which to read.
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
the vector data
|
|
392
|
+
"""
|
|
393
|
+
vector_dir = self._get_vector_dir(layer_name, item_name)
|
|
394
|
+
return self.vector_format.decode_vector(vector_dir, projection, bounds)
|
|
395
|
+
|
|
396
|
+
def write_vector(
|
|
397
|
+
self, layer_name: str, item_name: str, features: list[Feature]
|
|
398
|
+
) -> None:
|
|
399
|
+
"""Write vector data to the store.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
layer_name: the layer name or alias.
|
|
403
|
+
item_name: the item to write.
|
|
404
|
+
features: the vector data.
|
|
405
|
+
"""
|
|
406
|
+
vector_dir = self._get_vector_dir(layer_name, item_name)
|
|
407
|
+
vector_dir.mkdir(parents=True, exist_ok=True)
|
|
408
|
+
self.vector_format.encode_vector(vector_dir, features)
|
|
409
|
+
(vector_dir / COMPLETED_FNAME).touch()
|