feat: improve D-InSAR pairing and distribution

This commit is contained in:
2026-05-09 14:59:20 +08:00
parent 69d15bffcb
commit 5c73e2c1c2
28 changed files with 1480 additions and 86 deletions
+113 -14
View File
@@ -33,7 +33,13 @@ from ..config import settings
from ..models import (
RadarDataORM, RadarData, DinsarResultORM, HazardPointORM, HazardPoint, ScanStateORM
)
from ..utils import get_parser, RADAR_PARSERS, find_xml_file, parse_xml_metadata
from ..utils import (
get_parser,
RADAR_PARSERS,
find_xml_file,
normalize_satellite_family,
parse_xml_metadata,
)
from .task_service import task_service
from .image_service import image_service
from .orbit_converter import get_source_orbit_inventory, sync_orbit_pools
@@ -50,7 +56,7 @@ def _safe_mtime(path: str) -> float:
_DATE_RE = re.compile(r"^\d{8}$")
def _valid_imaging_date(value: str) -> bool:
def _valid_imaging_date(value: Optional[str]) -> bool:
return bool(value and _DATE_RE.match(value))
@@ -63,6 +69,62 @@ def _extract_date_from_text(value: Optional[str]) -> Optional[str]:
return None
def _text_or_none(value: Any) -> Optional[str]:
if value is None:
return None
text_value = str(value).strip()
return text_value or None
def _bool_or_none(value: Any) -> Optional[bool]:
if value is None or isinstance(value, bool):
return value
text_value = str(value).strip().lower()
if text_value in {"1", "true", "yes", "y"}:
return True
if text_value in {"0", "false", "no", "n"}:
return False
return None
def _build_insar_source_readiness(
meta: Dict[str, Any],
coverage_polygon: Optional[List[Tuple[float, float]]],
) -> Tuple[bool, Optional[str]]:
reasons: List[str] = []
if not coverage_polygon or len(coverage_polygon) < 3:
reasons.append("missing_footprint")
if not _valid_imaging_date(_text_or_none(meta.get("imaging_date"))):
reasons.append("missing_date")
for field_name, reason in (
("orbit_direction", "missing_orbit_direction"),
("imaging_mode", "missing_imaging_mode"),
("polarization", "missing_polarization"),
("satellite_family", "missing_satellite_family"),
):
if not _text_or_none(meta.get(field_name)):
reasons.append(reason)
geocoded_flag = _bool_or_none(meta.get("geocoded_flag"))
if geocoded_flag is True:
reasons.append("geocoded_product")
complex_tokens = {
_text_or_none(meta.get("image_data_type")),
_text_or_none(meta.get("product_type")),
_text_or_none(meta.get("source_product_token")),
_text_or_none(meta.get("product_variant")),
}
normalized_tokens = {str(token).strip().upper() for token in complex_tokens if token}
is_complex_source = bool(normalized_tokens.intersection({"COMPLEX", "SLC", "SSC"}))
if not is_complex_source:
reasons.append("not_complex_source")
if reasons:
return False, ";".join(reasons)
return True, None
def _iter_dirs(root: str, last_mtime: float):
stack = [root]
while stack:
@@ -586,20 +648,31 @@ class DataService:
continue
merged_meta[key] = value
satellite = merged_meta.get("satellite")
imaging_date = merged_meta.get("imaging_date")
imaging_mode = merged_meta.get("imaging_mode")
polarization = merged_meta.get("polarization")
orbit_direction = merged_meta.get("orbit_direction")
satellite_mode = merged_meta.get("satellite_mode")
receiving_station = merged_meta.get("receiving_station")
orbit_circle = merged_meta.get("orbit_circle")
satellite = _text_or_none(merged_meta.get("satellite"))
imaging_date = _text_or_none(merged_meta.get("imaging_date"))
imaging_mode = _text_or_none(merged_meta.get("imaging_mode"))
polarization = _text_or_none(merged_meta.get("polarization"))
orbit_direction = _text_or_none(merged_meta.get("orbit_direction"))
satellite_mode = _text_or_none(merged_meta.get("satellite_mode"))
receiving_station = _text_or_none(merged_meta.get("receiving_station"))
orbit_circle = _text_or_none(merged_meta.get("orbit_circle"))
scene_center_lon = merged_meta.get("scene_center_lon")
scene_center_lat = merged_meta.get("scene_center_lat")
acquisition_time_utc = merged_meta.get("acquisition_time_utc")
product_type = merged_meta.get("product_type")
product_level = merged_meta.get("product_level")
product_unique_id = merged_meta.get("product_unique_id")
acquisition_time_utc = _text_or_none(merged_meta.get("acquisition_time_utc"))
product_type = _text_or_none(merged_meta.get("product_type"))
source_product_token = _text_or_none(merged_meta.get("source_product_token"))
image_data_type = _text_or_none(merged_meta.get("image_data_type"))
image_data_format = _text_or_none(merged_meta.get("image_data_format"))
product_variant = _text_or_none(merged_meta.get("product_variant"))
product_level = _text_or_none(merged_meta.get("product_level"))
product_unique_id = _text_or_none(merged_meta.get("product_unique_id"))
satellite_family = normalize_satellite_family(
_text_or_none(merged_meta.get("satellite_family")) or satellite
)
look_direction = _text_or_none(merged_meta.get("look_direction"))
if look_direction:
look_direction = look_direction.upper()
geocoded_flag = _bool_or_none(merged_meta.get("geocoded_flag"))
if not satellite:
continue
@@ -635,9 +708,27 @@ class DataService:
scene_center_lon = scene_center_lon if scene_center_lon is not None else poly.centroid.x
scene_center_lat = scene_center_lat if scene_center_lat is not None else poly.centroid.y
readiness_meta = {
"satellite_family": satellite_family,
"imaging_date": imaging_date,
"imaging_mode": imaging_mode,
"orbit_direction": orbit_direction,
"polarization": polarization,
"product_type": product_type,
"source_product_token": source_product_token,
"image_data_type": image_data_type,
"product_variant": product_variant,
"geocoded_flag": geocoded_flag,
}
insar_source_ready, insar_source_reason = _build_insar_source_readiness(
readiness_meta,
coverage_polygon,
)
data_to_upsert = {
"unique_id": unique_id,
"satellite": satellite,
"satellite_family": satellite_family,
"imaging_date": imaging_date,
"imaging_mode": imaging_mode,
"orbit_direction": orbit_direction,
@@ -649,8 +740,16 @@ class DataService:
"scene_center_lat": scene_center_lat,
"acquisition_time_utc": acquisition_time_utc,
"product_type": product_type,
"source_product_token": source_product_token,
"image_data_type": image_data_type,
"image_data_format": image_data_format,
"product_variant": product_variant,
"product_level": product_level,
"product_unique_id": product_unique_id,
"look_direction": look_direction,
"geocoded_flag": geocoded_flag,
"insar_source_ready": insar_source_ready,
"insar_source_reason": insar_source_reason,
"file_path": radar_folder_path,
"has_orbit_data": has_orbit_data,
"orbit_file_path": orbit_file_path,