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.
Files changed (166) hide show
  1. rslearn/arg_parser.py +31 -0
  2. rslearn/config/__init__.py +6 -12
  3. rslearn/config/dataset.py +520 -401
  4. rslearn/const.py +9 -15
  5. rslearn/data_sources/__init__.py +8 -23
  6. rslearn/data_sources/aws_landsat.py +242 -98
  7. rslearn/data_sources/aws_open_data.py +111 -151
  8. rslearn/data_sources/aws_sentinel1.py +131 -0
  9. rslearn/data_sources/climate_data_store.py +471 -0
  10. rslearn/data_sources/copernicus.py +884 -12
  11. rslearn/data_sources/data_source.py +43 -12
  12. rslearn/data_sources/earthdaily.py +484 -0
  13. rslearn/data_sources/earthdata_srtm.py +282 -0
  14. rslearn/data_sources/eurocrops.py +242 -0
  15. rslearn/data_sources/gcp_public_data.py +578 -222
  16. rslearn/data_sources/google_earth_engine.py +461 -135
  17. rslearn/data_sources/local_files.py +219 -150
  18. rslearn/data_sources/openstreetmap.py +51 -89
  19. rslearn/data_sources/planet.py +24 -60
  20. rslearn/data_sources/planet_basemap.py +275 -0
  21. rslearn/data_sources/planetary_computer.py +798 -0
  22. rslearn/data_sources/usda_cdl.py +195 -0
  23. rslearn/data_sources/usgs_landsat.py +115 -83
  24. rslearn/data_sources/utils.py +249 -61
  25. rslearn/data_sources/vector_source.py +1 -0
  26. rslearn/data_sources/worldcereal.py +449 -0
  27. rslearn/data_sources/worldcover.py +144 -0
  28. rslearn/data_sources/worldpop.py +153 -0
  29. rslearn/data_sources/xyz_tiles.py +150 -107
  30. rslearn/dataset/__init__.py +8 -2
  31. rslearn/dataset/add_windows.py +2 -2
  32. rslearn/dataset/dataset.py +40 -51
  33. rslearn/dataset/handler_summaries.py +131 -0
  34. rslearn/dataset/manage.py +313 -74
  35. rslearn/dataset/materialize.py +431 -107
  36. rslearn/dataset/remap.py +29 -4
  37. rslearn/dataset/storage/__init__.py +1 -0
  38. rslearn/dataset/storage/file.py +202 -0
  39. rslearn/dataset/storage/storage.py +140 -0
  40. rslearn/dataset/window.py +181 -44
  41. rslearn/lightning_cli.py +454 -0
  42. rslearn/log_utils.py +24 -0
  43. rslearn/main.py +384 -181
  44. rslearn/models/anysat.py +215 -0
  45. rslearn/models/attention_pooling.py +177 -0
  46. rslearn/models/clay/clay.py +231 -0
  47. rslearn/models/clay/configs/metadata.yaml +295 -0
  48. rslearn/models/clip.py +68 -0
  49. rslearn/models/component.py +111 -0
  50. rslearn/models/concatenate_features.py +103 -0
  51. rslearn/models/conv.py +63 -0
  52. rslearn/models/croma.py +306 -0
  53. rslearn/models/detr/__init__.py +5 -0
  54. rslearn/models/detr/box_ops.py +103 -0
  55. rslearn/models/detr/detr.py +504 -0
  56. rslearn/models/detr/matcher.py +107 -0
  57. rslearn/models/detr/position_encoding.py +114 -0
  58. rslearn/models/detr/transformer.py +429 -0
  59. rslearn/models/detr/util.py +24 -0
  60. rslearn/models/dinov3.py +177 -0
  61. rslearn/models/faster_rcnn.py +30 -28
  62. rslearn/models/feature_center_crop.py +53 -0
  63. rslearn/models/fpn.py +19 -8
  64. rslearn/models/galileo/__init__.py +5 -0
  65. rslearn/models/galileo/galileo.py +595 -0
  66. rslearn/models/galileo/single_file_galileo.py +1678 -0
  67. rslearn/models/module_wrapper.py +65 -0
  68. rslearn/models/molmo.py +69 -0
  69. rslearn/models/multitask.py +384 -28
  70. rslearn/models/olmoearth_pretrain/__init__.py +1 -0
  71. rslearn/models/olmoearth_pretrain/model.py +421 -0
  72. rslearn/models/olmoearth_pretrain/norm.py +86 -0
  73. rslearn/models/panopticon.py +170 -0
  74. rslearn/models/panopticon_data/sensors/drone.yaml +32 -0
  75. rslearn/models/panopticon_data/sensors/enmap.yaml +904 -0
  76. rslearn/models/panopticon_data/sensors/goes.yaml +9 -0
  77. rslearn/models/panopticon_data/sensors/himawari.yaml +9 -0
  78. rslearn/models/panopticon_data/sensors/intuition.yaml +606 -0
  79. rslearn/models/panopticon_data/sensors/landsat8.yaml +84 -0
  80. rslearn/models/panopticon_data/sensors/modis_terra.yaml +99 -0
  81. rslearn/models/panopticon_data/sensors/qb2_ge1.yaml +34 -0
  82. rslearn/models/panopticon_data/sensors/sentinel1.yaml +85 -0
  83. rslearn/models/panopticon_data/sensors/sentinel2.yaml +97 -0
  84. rslearn/models/panopticon_data/sensors/superdove.yaml +60 -0
  85. rslearn/models/panopticon_data/sensors/wv23.yaml +63 -0
  86. rslearn/models/pick_features.py +17 -10
  87. rslearn/models/pooling_decoder.py +60 -7
  88. rslearn/models/presto/__init__.py +5 -0
  89. rslearn/models/presto/presto.py +297 -0
  90. rslearn/models/presto/single_file_presto.py +926 -0
  91. rslearn/models/prithvi.py +1147 -0
  92. rslearn/models/resize_features.py +59 -0
  93. rslearn/models/sam2_enc.py +13 -9
  94. rslearn/models/satlaspretrain.py +38 -18
  95. rslearn/models/simple_time_series.py +188 -77
  96. rslearn/models/singletask.py +24 -13
  97. rslearn/models/ssl4eo_s12.py +40 -30
  98. rslearn/models/swin.py +44 -32
  99. rslearn/models/task_embedding.py +250 -0
  100. rslearn/models/terramind.py +256 -0
  101. rslearn/models/trunk.py +139 -0
  102. rslearn/models/unet.py +68 -22
  103. rslearn/models/upsample.py +48 -0
  104. rslearn/models/use_croma.py +508 -0
  105. rslearn/template_params.py +26 -0
  106. rslearn/tile_stores/__init__.py +41 -18
  107. rslearn/tile_stores/default.py +409 -0
  108. rslearn/tile_stores/tile_store.py +236 -132
  109. rslearn/train/all_patches_dataset.py +530 -0
  110. rslearn/train/callbacks/adapters.py +53 -0
  111. rslearn/train/callbacks/freeze_unfreeze.py +348 -17
  112. rslearn/train/callbacks/gradients.py +129 -0
  113. rslearn/train/callbacks/peft.py +116 -0
  114. rslearn/train/data_module.py +444 -20
  115. rslearn/train/dataset.py +588 -235
  116. rslearn/train/lightning_module.py +192 -62
  117. rslearn/train/model_context.py +88 -0
  118. rslearn/train/optimizer.py +31 -0
  119. rslearn/train/prediction_writer.py +319 -84
  120. rslearn/train/scheduler.py +92 -0
  121. rslearn/train/tasks/classification.py +55 -28
  122. rslearn/train/tasks/detection.py +132 -76
  123. rslearn/train/tasks/embedding.py +120 -0
  124. rslearn/train/tasks/multi_task.py +28 -14
  125. rslearn/train/tasks/per_pixel_regression.py +291 -0
  126. rslearn/train/tasks/regression.py +161 -44
  127. rslearn/train/tasks/segmentation.py +428 -53
  128. rslearn/train/tasks/task.py +6 -5
  129. rslearn/train/transforms/__init__.py +1 -1
  130. rslearn/train/transforms/concatenate.py +54 -10
  131. rslearn/train/transforms/crop.py +29 -11
  132. rslearn/train/transforms/flip.py +18 -6
  133. rslearn/train/transforms/mask.py +78 -0
  134. rslearn/train/transforms/normalize.py +101 -17
  135. rslearn/train/transforms/pad.py +19 -7
  136. rslearn/train/transforms/resize.py +83 -0
  137. rslearn/train/transforms/select_bands.py +76 -0
  138. rslearn/train/transforms/sentinel1.py +75 -0
  139. rslearn/train/transforms/transform.py +89 -70
  140. rslearn/utils/__init__.py +2 -6
  141. rslearn/utils/array.py +8 -6
  142. rslearn/utils/feature.py +2 -2
  143. rslearn/utils/fsspec.py +90 -1
  144. rslearn/utils/geometry.py +347 -7
  145. rslearn/utils/get_utm_ups_crs.py +2 -3
  146. rslearn/utils/grid_index.py +5 -5
  147. rslearn/utils/jsonargparse.py +178 -0
  148. rslearn/utils/mp.py +4 -3
  149. rslearn/utils/raster_format.py +268 -116
  150. rslearn/utils/rtree_index.py +64 -17
  151. rslearn/utils/sqlite_index.py +7 -1
  152. rslearn/utils/vector_format.py +252 -97
  153. {rslearn-0.0.1.dist-info → rslearn-0.0.21.dist-info}/METADATA +532 -283
  154. rslearn-0.0.21.dist-info/RECORD +167 -0
  155. {rslearn-0.0.1.dist-info → rslearn-0.0.21.dist-info}/WHEEL +1 -1
  156. rslearn-0.0.21.dist-info/licenses/NOTICE +115 -0
  157. rslearn/data_sources/raster_source.py +0 -309
  158. rslearn/models/registry.py +0 -5
  159. rslearn/tile_stores/file.py +0 -242
  160. rslearn/utils/mgrs.py +0 -24
  161. rslearn/utils/utils.py +0 -22
  162. rslearn-0.0.1.dist-info/RECORD +0 -88
  163. /rslearn/{data_sources/geotiff.py → py.typed} +0 -0
  164. {rslearn-0.0.1.dist-info → rslearn-0.0.21.dist-info}/entry_points.txt +0 -0
  165. {rslearn-0.0.1.dist-info → rslearn-0.0.21.dist-info/licenses}/LICENSE +0 -0
  166. {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()