4
4
from dataclasses import dataclass
5
5
from datetime import date , datetime
6
6
from pathlib import Path
7
- from typing import Dict , Iterable , List , Optional , TypeAlias
7
+ from typing import Any , Dict , Iterable , List , Optional , TypeAlias
8
8
9
9
import fiona
10
10
import structlog
15
15
from datacube .model import Dataset , Field , MetadataType , Product
16
16
from geoalchemy2 import Geometry , WKBElement
17
17
from geoalchemy2 .shape import to_shape
18
+ from odc .geo import CRS
18
19
from psycopg2 ._range import Range as PgRange
19
20
from shapely .geometry import shape
20
21
from sqlalchemy import (
@@ -287,12 +288,9 @@ def _select_dataset_extent_columns(
287
288
# Some time-series-derived products have seemingly-rectangular but *huge* footprints
288
289
# (because they union many almost-indistinguishable footprints)
289
290
# If they specify a resolution, we can simplify the geometry based on it.
290
- if (
291
- footprint_expression is not None
292
- and product .grid_spec
293
- and product .grid_spec .resolution
294
- ):
295
- resolution = min (abs (r ) for r in product .grid_spec .resolution )
291
+ info = _product_info (product )
292
+ if footprint_expression is not None and info is not None :
293
+ resolution = min (abs (r ) for r in info .resolution )
296
294
footprint_expression = func .ST_SimplifyPreserveTopology (
297
295
footprint_expression , resolution / 4
298
296
)
@@ -442,15 +440,15 @@ def for_product(
442
440
"region_code"
443
441
)
444
442
445
- grid_spec = product .grid_spec
446
443
# Ingested grids trump the "region_code" field because they've probably sliced it up smaller.
447
444
#
448
445
# hltc has a grid spec, but most attributes are missing, so grid_spec functions fail.
449
446
# Therefore: only assume there's a grid if tile_size is specified.
450
447
if region_code_field is not None :
451
448
# Generic region info
452
449
return RegionInfo (product , known_regions )
453
- elif grid_spec is not None and grid_spec .tile_size :
450
+ info = _product_info (product )
451
+ if info is not None and info .get ("tile_size" ) is not None :
454
452
return GridRegionInfo (product , known_regions )
455
453
elif "sat_path" in product .metadata_type .dataset_fields :
456
454
return SceneRegionInfo (product , known_regions )
@@ -523,7 +521,6 @@ def alchemy_expression(self):
523
521
524
522
"""
525
523
product = self .product
526
- grid_spec = product .grid_spec
527
524
528
525
doc = jsonb_doc_expression (product .metadata_type )
529
526
projection_offset = _projection_doc_offset (product .metadata_type )
@@ -536,9 +533,10 @@ def alchemy_expression(self):
536
533
)
537
534
)
538
535
539
- # todo: look at grid_spec crs. Use it for defaults, conversion.
540
- size_x , size_y = grid_spec .tile_size or (1000.0 , 1000.0 )
541
- origin_x , origin_y = grid_spec .origin
536
+ info = _product_info (product ) or {}
537
+ # todo: use the CRS for defaults, conversion.
538
+ size_x , size_y = info .get ("tile_size" ) or (1000.0 , 1000.0 )
539
+ origin_x , origin_y = info .get ("origin" ) or (0.0 , 0.0 )
542
540
return func .concat (
543
541
func .floor ((func .ST_X (center_point ) - origin_x ) / size_x ).cast (String ),
544
542
"_" ,
@@ -561,6 +559,24 @@ def dataset_region_code(self, dataset: Dataset) -> Optional[str]:
561
559
return f"{ x } _{ y } "
562
560
563
561
562
+ def _product_info (product : Product ) -> dict [str , Any ] | None :
563
+ storage = product .definition .get ("storage" )
564
+ if storage is None :
565
+ return None
566
+ storage_crs = storage .get ("crs" )
567
+ if storage_crs is None :
568
+ return None
569
+ crs = CRS (str (storage_crs ).strip ())
570
+
571
+ def extract_point (name : str ) -> tuple [str ] | None :
572
+ xx = storage .get (name )
573
+ return None if xx is None else tuple (xx [dim ] for dim in crs .dimensions )
574
+
575
+ info = {name : extract_point (name ) for name in ("tile_size" , "resolution" , "origin" )}
576
+ complete = all (info [k ] is not None for k in ("tile_size" , "resolution" ))
577
+ return info if complete else None
578
+
579
+
564
580
def _from_xy_region_code (region_code : str ):
565
581
"""
566
582
>>> _from_xy_region_code('95_3')
0 commit comments