2828from tqdm import tqdm
2929
3030from stac_utils import (
31- check_geotiff_cog ,
31+ geotiff_extract_metadata ,
32+ item_create_from_cache ,
3233 date_extract_from_path ,
3334 datetime_parse_item ,
3435 encode_url_for_gdal ,
@@ -52,6 +53,9 @@ def process_item(path_item: str, collection_id: str, path_local: str,
5253 results_lookup : dict ) -> dict | None :
5354 """Process a single GeoTIFF URL to create a STAC item.
5455
56+ Uses cached metadata when available (no remote read). Falls back to
57+ rio_stac for cache misses (should not happen if validation ran first).
58+
5559 Returns dict with item_id and item object, or None if processing fails.
5660 """
5761 href_item = fix_url (path_item )
@@ -70,7 +74,6 @@ def process_item(path_item: str, collection_id: str, path_local: str,
7074 if date_str :
7175 item_time = datetime_parse_item (date_str )
7276 else :
73- # Placeholder for items where date cannot be extracted (e.g. albers10k2m)
7477 item_time = datetime (2000 , 1 , 1 , tzinfo = timezone .utc )
7578 datetime_is_unknown = True
7679
@@ -81,21 +84,35 @@ def process_item(path_item: str, collection_id: str, path_local: str,
8184 )
8285
8386 try :
84- # Encode for GDAL/vsicurl (spaces → %20), but keep original for asset href
85- gdal_path = encode_url_for_gdal (path_item )
86- item = rio_stac .stac .create_stac_item (
87- gdal_path ,
88- id = item_id ,
89- asset_media_type = media_type ,
90- asset_name = 'image' ,
91- asset_href = href_item ,
92- with_proj = True ,
93- collection = collection_id ,
94- collection_url = PATH_S3_JSON ,
95- asset_roles = ["data" ]
96- )
87+ # Cache hit: build from metadata (no remote read)
88+ if check .get ("epsg" ) is not None :
89+ item = item_create_from_cache (
90+ url = path_item ,
91+ item_id = item_id ,
92+ metadata = check ,
93+ collection_id = collection_id ,
94+ collection_url = PATH_S3_JSON ,
95+ media_type = media_type ,
96+ item_datetime = item_time ,
97+ )
98+ else :
99+ # Cache miss: fall back to rio_stac (remote read)
100+ logger .info ("Cache miss for %s, reading remote file" , href_item )
101+ gdal_path = encode_url_for_gdal (path_item )
102+ item = rio_stac .stac .create_stac_item (
103+ gdal_path ,
104+ id = item_id ,
105+ asset_media_type = media_type ,
106+ asset_name = 'image' ,
107+ asset_href = href_item ,
108+ with_proj = True ,
109+ collection = collection_id ,
110+ collection_url = PATH_S3_JSON ,
111+ asset_roles = ["data" ]
112+ )
113+ item .assets ['image' ].href = href_item
114+
97115 item .datetime = item_time
98- item .assets ['image' ].href = href_item
99116
100117 if datetime_is_unknown :
101118 item .properties ["datetime_unknown" ] = True
@@ -114,30 +131,55 @@ def process_item(path_item: str, collection_id: str, path_local: str,
114131# =============================================================================
115132
116133def load_validation_cache (urls_to_check : list [str ]) -> dict :
117- """Load cached validation results and validate new URLs as needed.
134+ """Load cached metadata and extract metadata for new URLs as needed.
118135
119- Returns lookup dict: {url: {"is_geotiff": bool, "is_cog": bool}}
136+ Returns lookup dict: {url: {is_geotiff, is_cog, epsg, height, width, transform, bounds}}
137+
138+ Old cache rows (missing spatial columns) trigger re-extraction on cache miss
139+ in process_item via the rio_stac fallback path.
120140 """
141+ all_columns = ["url" , "is_geotiff" , "is_cog" , "epsg" , "height" , "width" , "transform" , "bounds" ]
142+
121143 if os .path .exists (PATH_RESULTS_CSV ):
122144 df_existing = pd .read_csv (PATH_RESULTS_CSV )
123145 existing_urls = set (df_existing ["url" ])
124146 logger .info ("Loaded %d existing validation results" , len (df_existing ))
125147 else :
126- df_existing = pd .DataFrame (columns = [ "url" , "is_geotiff" , "is_cog" ] )
148+ df_existing = pd .DataFrame (columns = all_columns )
127149 existing_urls = set ()
128150 logger .info ("No existing validation cache found, will validate all URLs" )
129151
130- urls_to_validate = [url for url in urls_to_check if url not in existing_urls ]
131- logger .info ("%d URLs need validation (%d already cached)" ,
152+ # Detect old-format rows missing spatial metadata
153+ has_spatial = set ()
154+ needs_upgrade = set ()
155+ if "epsg" in df_existing .columns :
156+ for _ , row in df_existing .iterrows ():
157+ if pd .notna (row .get ("epsg" )):
158+ has_spatial .add (row ["url" ])
159+ elif row .get ("is_geotiff" ):
160+ needs_upgrade .add (row ["url" ])
161+ else :
162+ needs_upgrade = {row ["url" ] for _ , row in df_existing .iterrows () if row ["is_geotiff" ]}
163+
164+ urls_to_validate = [url for url in urls_to_check
165+ if url not in existing_urls or url in needs_upgrade ]
166+ if needs_upgrade :
167+ # Drop old rows that will be re-extracted with spatial metadata
168+ urls_upgrading = needs_upgrade & set (urls_to_validate )
169+ if urls_upgrading :
170+ df_existing = df_existing [~ df_existing ["url" ].isin (urls_upgrading )]
171+ logger .info ("%d cached URLs need spatial metadata upgrade" , len (urls_upgrading ))
172+
173+ logger .info ("%d URLs need metadata extraction (%d already cached with full metadata)" ,
132174 len (urls_to_validate ), len (urls_to_check ) - len (urls_to_validate ))
133175
134176 if urls_to_validate :
135- logger .info ("Validating %d GeoTIFFs..." , len (urls_to_validate ))
177+ logger .info ("Extracting metadata from %d GeoTIFFs..." , len (urls_to_validate ))
136178 with concurrent .futures .ThreadPoolExecutor () as executor :
137179 new_results = list (tqdm (
138- executor .map (check_geotiff_cog , urls_to_validate ),
180+ executor .map (geotiff_extract_metadata , urls_to_validate ),
139181 total = len (urls_to_validate ),
140- desc = "Validating GeoTIFFs "
182+ desc = "Extracting GeoTIFF metadata "
141183 ))
142184
143185 df_new = pd .DataFrame (new_results )
@@ -146,12 +188,19 @@ def load_validation_cache(urls_to_check: list[str]) -> dict:
146188 logger .info ("Saved %d validation results to %s" , len (df_all ), PATH_RESULTS_CSV )
147189 else :
148190 df_all = df_existing
149- logger .info ("No new URLs to validate, using existing cache" )
150-
151- return {
152- fix_url (row ["url" ]): {"is_geotiff" : row ["is_geotiff" ], "is_cog" : row ["is_cog" ]}
153- for _ , row in df_all .iterrows ()
154- }
191+ logger .info ("All URLs cached, no remote reads needed" )
192+
193+ # Build lookup with full metadata (NaN → None for missing spatial columns)
194+ result = {}
195+ for _ , row in df_all .iterrows ():
196+ entry = {"is_geotiff" : row ["is_geotiff" ], "is_cog" : row ["is_cog" ]}
197+ for col in ["epsg" , "height" , "width" , "transform" , "bounds" ]:
198+ if col in row and pd .notna (row [col ]):
199+ entry [col ] = int (row [col ]) if col in ("epsg" , "height" , "width" ) else row [col ]
200+ else :
201+ entry [col ] = None
202+ result [fix_url (row ["url" ])] = entry
203+ return result
155204
156205
157206# =============================================================================
0 commit comments