rslearn 0.0.15__py3-none-any.whl → 0.0.17__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 (50) hide show
  1. rslearn/config/__init__.py +2 -10
  2. rslearn/config/dataset.py +420 -420
  3. rslearn/data_sources/__init__.py +8 -31
  4. rslearn/data_sources/aws_landsat.py +13 -24
  5. rslearn/data_sources/aws_open_data.py +21 -46
  6. rslearn/data_sources/aws_sentinel1.py +3 -14
  7. rslearn/data_sources/climate_data_store.py +21 -40
  8. rslearn/data_sources/copernicus.py +30 -91
  9. rslearn/data_sources/data_source.py +26 -0
  10. rslearn/data_sources/earthdaily.py +13 -38
  11. rslearn/data_sources/earthdata_srtm.py +14 -32
  12. rslearn/data_sources/eurocrops.py +5 -9
  13. rslearn/data_sources/gcp_public_data.py +46 -43
  14. rslearn/data_sources/google_earth_engine.py +31 -44
  15. rslearn/data_sources/local_files.py +91 -100
  16. rslearn/data_sources/openstreetmap.py +21 -51
  17. rslearn/data_sources/planet.py +12 -30
  18. rslearn/data_sources/planet_basemap.py +4 -25
  19. rslearn/data_sources/planetary_computer.py +58 -141
  20. rslearn/data_sources/usda_cdl.py +15 -26
  21. rslearn/data_sources/usgs_landsat.py +4 -29
  22. rslearn/data_sources/utils.py +9 -0
  23. rslearn/data_sources/worldcereal.py +47 -54
  24. rslearn/data_sources/worldcover.py +16 -14
  25. rslearn/data_sources/worldpop.py +15 -18
  26. rslearn/data_sources/xyz_tiles.py +11 -30
  27. rslearn/dataset/dataset.py +6 -6
  28. rslearn/dataset/manage.py +14 -20
  29. rslearn/dataset/materialize.py +9 -45
  30. rslearn/lightning_cli.py +377 -1
  31. rslearn/main.py +3 -3
  32. rslearn/models/concatenate_features.py +93 -0
  33. rslearn/models/olmoearth_pretrain/model.py +2 -5
  34. rslearn/tile_stores/__init__.py +0 -11
  35. rslearn/train/dataset.py +4 -12
  36. rslearn/train/prediction_writer.py +16 -32
  37. rslearn/train/tasks/classification.py +2 -1
  38. rslearn/utils/fsspec.py +20 -0
  39. rslearn/utils/jsonargparse.py +79 -0
  40. rslearn/utils/raster_format.py +1 -41
  41. rslearn/utils/vector_format.py +1 -38
  42. {rslearn-0.0.15.dist-info → rslearn-0.0.17.dist-info}/METADATA +58 -25
  43. {rslearn-0.0.15.dist-info → rslearn-0.0.17.dist-info}/RECORD +48 -49
  44. rslearn/data_sources/geotiff.py +0 -1
  45. rslearn/data_sources/raster_source.py +0 -23
  46. {rslearn-0.0.15.dist-info → rslearn-0.0.17.dist-info}/WHEEL +0 -0
  47. {rslearn-0.0.15.dist-info → rslearn-0.0.17.dist-info}/entry_points.txt +0 -0
  48. {rslearn-0.0.15.dist-info → rslearn-0.0.17.dist-info}/licenses/LICENSE +0 -0
  49. {rslearn-0.0.15.dist-info → rslearn-0.0.17.dist-info}/licenses/NOTICE +0 -0
  50. {rslearn-0.0.15.dist-info → rslearn-0.0.17.dist-info}/top_level.txt +0 -0
@@ -9,7 +9,8 @@ import requests
9
9
  from fsspec.implementations.local import LocalFileSystem
10
10
  from upath import UPath
11
11
 
12
- from rslearn.config import LayerConfig
12
+ from rslearn.config import LayerType
13
+ from rslearn.data_sources import DataSourceContext
13
14
  from rslearn.data_sources.local_files import LocalFiles
14
15
  from rslearn.log_utils import get_logger
15
16
  from rslearn.utils.fsspec import get_upath_local, join_upath, open_atomic
@@ -52,8 +53,8 @@ class WorldCover(LocalFiles):
52
53
 
53
54
  def __init__(
54
55
  self,
55
- config: LayerConfig,
56
- worldcover_dir: UPath,
56
+ worldcover_dir: str,
57
+ context: DataSourceContext = DataSourceContext(),
57
58
  ) -> None:
58
59
  """Create a new WorldCover.
59
60
 
@@ -64,18 +65,19 @@ class WorldCover(LocalFiles):
64
65
  high performance, this should be a local directory; if the dataset is
65
66
  remote, prefix with a protocol ("file://") to use a local directory
66
67
  instead of a path relative to the dataset path.
68
+ context: the data source context.
67
69
  """
68
- tif_dir = self.download_worldcover_data(worldcover_dir)
69
- super().__init__(config, tif_dir)
70
-
71
- @staticmethod
72
- def from_config(config: LayerConfig, ds_path: UPath) -> "LocalFiles":
73
- """Creates a new LocalFiles instance from a configuration dictionary."""
74
- if config.data_source is None:
75
- raise ValueError("LocalFiles data source requires a data source config")
76
- d = config.data_source.config_dict
77
- return WorldCover(
78
- config=config, worldcover_dir=join_upath(ds_path, d["worldcover_dir"])
70
+ if context.ds_path is not None:
71
+ worldcover_upath = join_upath(context.ds_path, worldcover_dir)
72
+ else:
73
+ worldcover_upath = UPath(worldcover_dir)
74
+
75
+ tif_dir = self.download_worldcover_data(worldcover_upath)
76
+
77
+ super().__init__(
78
+ src_dir=tif_dir,
79
+ layer_type=LayerType.RASTER,
80
+ context=context,
79
81
  )
80
82
 
81
83
  def download_worldcover_data(self, worldcover_dir: UPath) -> UPath:
@@ -6,10 +6,10 @@ from html.parser import HTMLParser
6
6
  from urllib.parse import urljoin
7
7
 
8
8
  import requests
9
- import requests.auth
10
9
  from upath import UPath
11
10
 
12
- from rslearn.config import LayerConfig
11
+ from rslearn.config import LayerType
12
+ from rslearn.data_sources import DataSourceContext
13
13
  from rslearn.data_sources.local_files import LocalFiles
14
14
  from rslearn.log_utils import get_logger
15
15
  from rslearn.utils.fsspec import join_upath, open_atomic
@@ -59,33 +59,30 @@ class WorldPop(LocalFiles):
59
59
 
60
60
  def __init__(
61
61
  self,
62
- config: LayerConfig,
63
- worldpop_dir: UPath,
62
+ worldpop_dir: str,
64
63
  timeout: timedelta = timedelta(seconds=30),
64
+ context: DataSourceContext = DataSourceContext(),
65
65
  ):
66
66
  """Create a new WorldPop.
67
67
 
68
68
  Args:
69
- config: configuration for this layer. It should specify a single band
70
- called B1 which will contain the population counts.
71
69
  worldpop_dir: the directory to extract the WorldPop GeoTIFF files. For
72
70
  high performance, this should be a local directory; if the dataset is
73
71
  remote, prefix with a protocol ("file://") to use a local directory
74
72
  instead of a path relative to the dataset path.
75
73
  timeout: timeout for HTTP requests.
74
+ context: the data source context.
76
75
  """
77
- worldpop_dir.mkdir(parents=True, exist_ok=True)
78
- self.download_worldpop_data(worldpop_dir, timeout)
79
- super().__init__(config, worldpop_dir)
80
-
81
- @staticmethod
82
- def from_config(config: LayerConfig, ds_path: UPath) -> "LocalFiles":
83
- """Creates a new LocalFiles instance from a configuration dictionary."""
84
- if config.data_source is None:
85
- raise ValueError("LocalFiles data source requires a data source config")
86
- d = config.data_source.config_dict
87
- return WorldPop(
88
- config=config, worldpop_dir=join_upath(ds_path, d["worldpop_dir"])
76
+ if context.ds_path is not None:
77
+ worldpop_upath = join_upath(context.ds_path, worldpop_dir)
78
+ else:
79
+ worldpop_upath = UPath(worldpop_dir)
80
+ worldpop_upath.mkdir(parents=True, exist_ok=True)
81
+ self.download_worldpop_data(worldpop_upath, timeout)
82
+ super().__init__(
83
+ src_dir=worldpop_upath,
84
+ layer_type=LayerType.RASTER,
85
+ context=context,
89
86
  )
90
87
 
91
88
  def download_worldpop_data(self, worldpop_dir: UPath, timeout: timedelta) -> None:
@@ -14,9 +14,8 @@ import shapely
14
14
  from PIL import Image
15
15
  from rasterio.crs import CRS
16
16
  from rasterio.enums import Resampling
17
- from upath import UPath
18
17
 
19
- from rslearn.config import LayerConfig, QueryConfig, RasterLayerConfig
18
+ from rslearn.config import LayerConfig, QueryConfig
20
19
  from rslearn.dataset import Window
21
20
  from rslearn.dataset.materialize import RasterMaterializer
22
21
  from rslearn.tile_stores import TileStore, TileStoreWithLayer
@@ -24,7 +23,7 @@ from rslearn.utils import PixelBounds, Projection, STGeometry
24
23
  from rslearn.utils.array import copy_spatial_array
25
24
  from rslearn.utils.raster_format import get_transform_from_projection_and_bounds
26
25
 
27
- from .data_source import DataSource, Item
26
+ from .data_source import DataSource, DataSourceContext, Item
28
27
  from .utils import match_candidate_items_to_window
29
28
 
30
29
  WEB_MERCATOR_EPSG = 3857
@@ -96,11 +95,12 @@ class XyzTiles(DataSource, TileStore):
96
95
  url_templates: list[str],
97
96
  time_ranges: list[tuple[datetime, datetime]],
98
97
  zoom: int,
99
- crs: CRS = CRS.from_epsg(WEB_MERCATOR_EPSG),
98
+ crs: str | CRS = CRS.from_epsg(WEB_MERCATOR_EPSG),
100
99
  total_units: float = WEB_MERCATOR_UNITS,
101
100
  offset: float = WEB_MERCATOR_UNITS / 2,
102
101
  tile_size: int = 256,
103
102
  band_names: list[str] = ["R", "G", "B"],
103
+ context: DataSourceContext = DataSourceContext(),
104
104
  ):
105
105
  """Initialize an XyzTiles instance.
106
106
 
@@ -121,16 +121,22 @@ class XyzTiles(DataSource, TileStore):
121
121
  offset: offset added to projection units when converting to tile positions.
122
122
  tile_size: size in pixels of each tile. Tiles must be square.
123
123
  band_names: what to name the bands that we read.
124
+ context: the data source context.
124
125
  """
125
126
  self.url_templates = url_templates
126
127
  self.time_ranges = time_ranges
127
128
  self.zoom = zoom
128
- self.crs = crs
129
129
  self.total_units = total_units
130
130
  self.offset = offset
131
131
  self.tile_size = tile_size
132
132
  self.band_names = band_names
133
133
 
134
+ # Convert to CRS if needed.
135
+ if isinstance(crs, str):
136
+ self.crs = CRS.from_string(crs)
137
+ else:
138
+ self.crs = crs
139
+
134
140
  # Compute total number of pixels (a function of the zoom level and tile size).
135
141
  self.total_pixels = tile_size * (2**zoom)
136
142
  # Compute pixel size (resolution).
@@ -153,30 +159,6 @@ class XyzTiles(DataSource, TileStore):
153
159
  item = Item(url_template, geometry)
154
160
  self.items.append(item)
155
161
 
156
- @staticmethod
157
- def from_config(config: LayerConfig, ds_path: UPath) -> "XyzTiles":
158
- """Creates a new XyzTiles instance from a configuration dictionary."""
159
- if config.data_source is None:
160
- raise ValueError("data_source is required")
161
- d = config.data_source.config_dict
162
- time_ranges = []
163
- for str1, str2 in d["time_ranges"]:
164
- time1 = datetime.fromisoformat(str1)
165
- time2 = datetime.fromisoformat(str2)
166
- time_ranges.append((time1, time2))
167
- kwargs = dict(
168
- url_templates=d["url_templates"], zoom=d["zoom"], time_ranges=time_ranges
169
- )
170
- if "crs" in d:
171
- kwargs["crs"] = CRS.from_string(d["crs"])
172
- if "total_units" in d:
173
- kwargs["total_units"] = d["total_units"]
174
- if "offset" in d:
175
- kwargs["offset"] = d["offset"]
176
- if "tile_size" in d:
177
- kwargs["tile_size"] = d["tile_size"]
178
- return XyzTiles(**kwargs)
179
-
180
162
  def get_items(
181
163
  self, geometries: list[STGeometry], query_config: QueryConfig
182
164
  ) -> list[list[list[Item]]]:
@@ -381,7 +363,6 @@ class XyzTiles(DataSource, TileStore):
381
363
  layer_name: the name of this layer
382
364
  layer_cfg: the config of this layer
383
365
  """
384
- assert isinstance(layer_cfg, RasterLayerConfig)
385
366
  RasterMaterializer().materialize(
386
367
  TileStoreWithLayer(self, layer_name),
387
368
  window,
@@ -6,7 +6,7 @@ import multiprocessing
6
6
  import tqdm
7
7
  from upath import UPath
8
8
 
9
- from rslearn.config import load_layer_config
9
+ from rslearn.config import DatasetConfig
10
10
  from rslearn.log_utils import get_logger
11
11
  from rslearn.template_params import substitute_env_vars_in_string
12
12
  from rslearn.tile_stores import TileStore, load_tile_store
@@ -55,19 +55,19 @@ class Dataset:
55
55
  with (self.path / "config.json").open("r") as f:
56
56
  config_content = f.read()
57
57
  config_content = substitute_env_vars_in_string(config_content)
58
- config = json.loads(config_content)
58
+ config = DatasetConfig.model_validate(json.loads(config_content))
59
+
59
60
  self.layers = {}
60
- for layer_name, d in config["layers"].items():
61
+ for layer_name, layer_config in config.layers.items():
61
62
  # Layer names must not contain period, since we use period to
62
63
  # distinguish different materialized groups within a layer.
63
64
  assert "." not in layer_name, "layer names must not contain periods"
64
65
  if layer_name in disabled_layers:
65
66
  logger.warning(f"Layer {layer_name} is disabled")
66
67
  continue
67
- self.layers[layer_name] = load_layer_config(d)
68
+ self.layers[layer_name] = layer_config
68
69
 
69
- self.tile_store_config = config.get("tile_store", None)
70
- self.materializer_name = config.get("materialize")
70
+ self.tile_store_config = config.tile_store
71
71
 
72
72
  def _get_index(self) -> DatasetIndex | None:
73
73
  index_fname = self.path / DatasetIndex.FNAME
rslearn/dataset/manage.py CHANGED
@@ -6,11 +6,9 @@ from collections.abc import Callable
6
6
  from datetime import timedelta
7
7
  from typing import Any
8
8
 
9
- import rslearn.data_sources
10
9
  from rslearn.config import (
11
10
  LayerConfig,
12
11
  LayerType,
13
- RasterLayerConfig,
14
12
  )
15
13
  from rslearn.data_sources import DataSource, Item
16
14
  from rslearn.dataset.handler_summaries import (
@@ -24,7 +22,7 @@ from rslearn.log_utils import get_logger
24
22
  from rslearn.tile_stores import TileStore, get_tile_store_with_layer
25
23
 
26
24
  from .dataset import Dataset
27
- from .materialize import Materializers
25
+ from .materialize import Materializer, RasterMaterializer, VectorMaterializer
28
26
  from .window import Window, WindowLayerData
29
27
 
30
28
  logger = get_logger(__name__)
@@ -150,7 +148,7 @@ def prepare_dataset_windows(
150
148
  layer_summaries.append(
151
149
  LayerPrepareSummary(
152
150
  layer_name=layer_name,
153
- data_source_name=data_source_cfg.name,
151
+ data_source_name=data_source_cfg.class_path,
154
152
  duration_seconds=time.monotonic() - layer_start_time,
155
153
  windows_prepared=0,
156
154
  windows_skipped=windows_skipped,
@@ -162,9 +160,7 @@ def prepare_dataset_windows(
162
160
 
163
161
  # Create data source after checking for at least one window so it can be fast
164
162
  # if there are no windows to prepare.
165
- data_source = rslearn.data_sources.data_source_from_config(
166
- layer_cfg, dataset.path
167
- )
163
+ data_source = layer_cfg.instantiate_data_source(dataset.path)
168
164
 
169
165
  # Get STGeometry for each window.
170
166
  geometries = []
@@ -215,7 +211,7 @@ def prepare_dataset_windows(
215
211
  layer_summaries.append(
216
212
  LayerPrepareSummary(
217
213
  layer_name=layer_name,
218
- data_source_name=data_source_cfg.name,
214
+ data_source_name=data_source_cfg.class_path,
219
215
  duration_seconds=time.monotonic() - layer_start_time,
220
216
  windows_prepared=windows_prepared,
221
217
  windows_skipped=windows_skipped,
@@ -258,9 +254,7 @@ def ingest_dataset_windows(
258
254
  if not layer_cfg.data_source.ingest:
259
255
  continue
260
256
 
261
- data_source = rslearn.data_sources.data_source_from_config(
262
- layer_cfg, dataset.path
263
- )
257
+ data_source = layer_cfg.instantiate_data_source(dataset.path)
264
258
 
265
259
  geometries_by_item: dict = {}
266
260
  for window in windows:
@@ -322,8 +316,7 @@ def is_window_ingested(
322
316
  for serialized_item in group:
323
317
  item = Item.deserialize(serialized_item)
324
318
 
325
- if layer_cfg.layer_type == LayerType.RASTER:
326
- assert isinstance(layer_cfg, RasterLayerConfig)
319
+ if layer_cfg.type == LayerType.RASTER:
327
320
  for band_set in layer_cfg.band_sets:
328
321
  # Make sure that layers exist containing each configured band.
329
322
  # And that those layers are marked completed.
@@ -417,10 +410,13 @@ def materialize_window(
417
410
  f"Materializing {len(item_groups)} item groups in layer {layer_name} from tile store"
418
411
  )
419
412
 
420
- if dataset.materializer_name:
421
- materializer = Materializers[dataset.materializer_name]()
413
+ materializer: Materializer
414
+ if layer_cfg.type == LayerType.RASTER:
415
+ materializer = RasterMaterializer()
416
+ elif layer_cfg.type == LayerType.VECTOR:
417
+ materializer = VectorMaterializer()
422
418
  else:
423
- materializer = Materializers[layer_cfg.layer_type.value]()
419
+ raise ValueError(f"unknown layer type {layer_cfg.type}")
424
420
 
425
421
  retry(
426
422
  fn=lambda: materializer.materialize(
@@ -491,10 +487,8 @@ def materialize_dataset_windows(
491
487
  if not layer_cfg.data_source:
492
488
  total_skipped = len(windows)
493
489
  else:
494
- data_source_name = layer_cfg.data_source.name
495
- data_source = rslearn.data_sources.data_source_from_config(
496
- layer_cfg, dataset.path
497
- )
490
+ data_source_name = layer_cfg.data_source.class_path
491
+ data_source = layer_cfg.instantiate_data_source(dataset.path)
498
492
 
499
493
  for window in windows:
500
494
  window_summary = materialize_window(
@@ -1,7 +1,6 @@
1
1
  """Classes to implement dataset materialization."""
2
2
 
3
- from collections.abc import Callable
4
- from typing import Any, Generic, TypeVar
3
+ from typing import Any
5
4
 
6
5
  import numpy as np
7
6
  import numpy.typing as npt
@@ -11,45 +10,17 @@ from rslearn.config import (
11
10
  BandSetConfig,
12
11
  CompositingMethod,
13
12
  LayerConfig,
14
- RasterFormatConfig,
15
- RasterLayerConfig,
16
- VectorLayerConfig,
17
13
  )
18
14
  from rslearn.data_sources.data_source import ItemType
19
15
  from rslearn.tile_stores import TileStoreWithLayer
20
16
  from rslearn.utils.feature import Feature
21
17
  from rslearn.utils.geometry import PixelBounds, Projection
22
- from rslearn.utils.raster_format import load_raster_format
23
- from rslearn.utils.vector_format import load_vector_format
24
18
 
25
19
  from .remap import Remapper, load_remapper
26
20
  from .window import Window
27
21
 
28
- _MaterializerT = TypeVar("_MaterializerT", bound="Materializer")
29
22
 
30
-
31
- class _MaterializerRegistry(dict[str, type["Materializer"]]):
32
- """Registry for Materializer classes."""
33
-
34
- def register(
35
- self, name: str
36
- ) -> Callable[[type[_MaterializerT]], type[_MaterializerT]]:
37
- """Decorator to register a materializer class."""
38
-
39
- def decorator(cls: type[_MaterializerT]) -> type[_MaterializerT]:
40
- self[name] = cls
41
- return cls
42
-
43
- return decorator
44
-
45
-
46
- Materializers = _MaterializerRegistry()
47
-
48
-
49
- LayerConfigType = TypeVar("LayerConfigType", bound=LayerConfig)
50
-
51
-
52
- class Materializer(Generic[LayerConfigType]):
23
+ class Materializer:
53
24
  """An abstract class that materializes data from a tile store."""
54
25
 
55
26
  def materialize(
@@ -57,7 +28,7 @@ class Materializer(Generic[LayerConfigType]):
57
28
  tile_store: TileStoreWithLayer,
58
29
  window: Window,
59
30
  layer_name: str,
60
- layer_cfg: LayerConfigType,
31
+ layer_cfg: LayerConfig,
61
32
  item_groups: list[list[ItemType]],
62
33
  ) -> None:
63
34
  """Materialize portions of items corresponding to this window into the dataset.
@@ -473,7 +444,7 @@ def build_composite(
473
444
  group: list[ItemType],
474
445
  compositing_method: CompositingMethod,
475
446
  tile_store: TileStoreWithLayer,
476
- layer_cfg: RasterLayerConfig,
447
+ layer_cfg: LayerConfig,
477
448
  band_cfg: BandSetConfig,
478
449
  projection: Projection,
479
450
  bounds: PixelBounds,
@@ -503,13 +474,12 @@ def build_composite(
503
474
  band_dtype=band_cfg.dtype.value,
504
475
  tile_store=tile_store,
505
476
  projection=projection,
506
- resampling_method=layer_cfg.resampling_method,
477
+ resampling_method=layer_cfg.resampling_method.get_rasterio_resampling(),
507
478
  remapper=remapper,
508
479
  )
509
480
 
510
481
 
511
- @Materializers.register("raster")
512
- class RasterMaterializer(Materializer[RasterLayerConfig]):
482
+ class RasterMaterializer(Materializer):
513
483
  """A Materializer for raster data."""
514
484
 
515
485
  def materialize(
@@ -517,7 +487,7 @@ class RasterMaterializer(Materializer[RasterLayerConfig]):
517
487
  tile_store: TileStoreWithLayer,
518
488
  window: Window,
519
489
  layer_name: str,
520
- layer_cfg: RasterLayerConfig,
490
+ layer_cfg: LayerConfig,
521
491
  item_groups: list[list[ItemType]],
522
492
  ) -> None:
523
493
  """Materialize portions of items corresponding to this window into the dataset.
@@ -529,8 +499,6 @@ class RasterMaterializer(Materializer[RasterLayerConfig]):
529
499
  layer_cfg: the configuration of the layer to materialize
530
500
  item_groups: the items associated with this window and layer
531
501
  """
532
- assert isinstance(layer_cfg, RasterLayerConfig)
533
-
534
502
  for band_cfg in layer_cfg.band_sets:
535
503
  # band_cfg could specify zoom_offset and maybe other parameters that affect
536
504
  # projection/bounds, so use the corrected projection/bounds.
@@ -543,9 +511,7 @@ class RasterMaterializer(Materializer[RasterLayerConfig]):
543
511
  if band_cfg.remap:
544
512
  remapper = load_remapper(band_cfg.remap)
545
513
 
546
- raster_format = load_raster_format(
547
- RasterFormatConfig(band_cfg.format["name"], band_cfg.format)
548
- )
514
+ raster_format = band_cfg.instantiate_raster_format()
549
515
 
550
516
  for group_id, group in enumerate(item_groups):
551
517
  composite = build_composite(
@@ -569,7 +535,6 @@ class RasterMaterializer(Materializer[RasterLayerConfig]):
569
535
  window.mark_layer_completed(layer_name, group_id)
570
536
 
571
537
 
572
- @Materializers.register("vector")
573
538
  class VectorMaterializer(Materializer):
574
539
  """A Materializer for vector data."""
575
540
 
@@ -590,8 +555,7 @@ class VectorMaterializer(Materializer):
590
555
  layer_cfg: the configuration of the layer to materialize
591
556
  item_groups: the items associated with this window and layer
592
557
  """
593
- assert isinstance(layer_cfg, VectorLayerConfig)
594
- vector_format = load_vector_format(layer_cfg.format)
558
+ vector_format = layer_cfg.instantiate_vector_format()
595
559
 
596
560
  for group_id, group in enumerate(item_groups):
597
561
  features: list[Feature] = []