Add Sentinel-1 asset management and PyINT pipeline support

This commit is contained in:
2026-05-14 03:02:56 +08:00
parent 508ca5641a
commit 729c5c2a7f
61 changed files with 7800 additions and 885 deletions
+40 -13
View File
@@ -1,4 +1,6 @@
import os
import re
from datetime import datetime
from typing import Optional, Tuple, List, Callable, Dict, Any
from lxml import etree
@@ -18,7 +20,7 @@ def _create_secure_xml_parser() -> etree.XMLParser:
recover=False,
)
# --- Sentinel-1 (S1A/S1B) Parsers ---
# --- Sentinel-1 (S1A/S1B/S1C) Parsers ---
def _radar_meta_base() -> Dict[str, Any]:
return {
@@ -83,7 +85,7 @@ def normalize_satellite_family(value: Optional[str]) -> Optional[str]:
compact = raw.replace("-", "").replace("_", "").replace(" ", "")
if compact in {"LT1", "LT1A", "LT1B", "LUTAN1", "LUTAN1A", "LUTAN1B"}:
return "LT1"
if compact in {"S1", "S1A", "S1B", "SENTINEL1", "SENTINEL1A", "SENTINEL1B"}:
if compact in {"S1", "S1A", "S1B", "S1C", "SENTINEL1", "SENTINEL1A", "SENTINEL1B", "SENTINEL1C"}:
return "S1"
if compact in {"GF3", "GAOFEN3"}:
return "GF3"
@@ -96,19 +98,44 @@ def parse_s1_radar_filename(folder_name: str) -> Optional[Dict[str, Any]]:
Example: S1A_IW_SLC__1SDV_20250101T104105_...
Returns a metadata dict.
"""
parts = folder_name.split('_')
if len(parts) < 5 or not parts[0].startswith('S1'):
name = os.path.basename(str(folder_name or "").strip())
if name.lower().endswith(".zip"):
name = name[:-4]
if name.lower().endswith(".safe"):
name = name[:-5]
match = re.match(
r"^(?P<satellite>S1[A-Z])_"
r"(?P<mode>[A-Z0-9]+)_"
r"(?P<product>[A-Z0-9]+)_+"
r"(?P<class>[0-9A-Z]{4})_"
r"(?P<start>\d{8}T\d{6}(?:\.\d+)?)_"
r"(?P<stop>\d{8}T\d{6}(?:\.\d+)?)_"
r"(?P<absolute_orbit>\d+)_"
r"(?P<datatake>[0-9A-F]+)_"
r"(?P<product_uid>[0-9A-F]+)$",
name,
flags=re.IGNORECASE,
)
if not match:
return None
meta = _radar_meta_base()
meta["satellite"] = parts[0]
meta["satellite_family"] = normalize_satellite_family(parts[0])
meta["imaging_date"] = parts[4].split('T')[0]
meta["imaging_mode"] = parts[1]
meta["source_product_token"] = parts[2]
meta["product_type"] = parts[2]
polarization = parts[3] # e.g., '1SDV' -> 'DV' is dual-pol VV/VH
meta["polarization"] = polarization[2:] if len(polarization) > 2 else polarization
meta["satellite"] = match.group("satellite").upper()
meta["satellite_family"] = normalize_satellite_family(meta["satellite"])
meta["imaging_date"] = match.group("start")[:8]
meta["imaging_mode"] = match.group("mode").upper()
meta["source_product_token"] = match.group("class").upper()
meta["product_type"] = match.group("product").upper()
meta["product_level"] = "L1"
polarization = match.group("class").upper() # e.g. 1SDV -> DV
meta["polarization"] = polarization[-2:] if len(polarization) > 2 else polarization
meta["orbit_circle"] = match.group("absolute_orbit").lstrip("0") or match.group("absolute_orbit")
meta["product_unique_id"] = name
try:
start_time = datetime.strptime(match.group("start").split(".")[0], "%Y%m%dT%H%M%S")
meta["acquisition_time_utc"] = start_time.isoformat()
except ValueError:
meta["acquisition_time_utc"] = match.group("start")
return meta