chore: initialize insar management system v2
This commit is contained in:
@@ -0,0 +1,551 @@
|
||||
import os
|
||||
from typing import Optional, Tuple, List, Callable, Dict, Any
|
||||
from lxml import etree
|
||||
|
||||
|
||||
def _create_secure_xml_parser() -> etree.XMLParser:
|
||||
"""
|
||||
Build a hardened XML parser:
|
||||
- disable entity resolution / DTD loading
|
||||
- block network access
|
||||
- keep strict parsing (no recovery mode)
|
||||
"""
|
||||
return etree.XMLParser(
|
||||
resolve_entities=False,
|
||||
load_dtd=False,
|
||||
no_network=True,
|
||||
huge_tree=False,
|
||||
recover=False,
|
||||
)
|
||||
|
||||
# --- Sentinel-1 (S1A/S1B) Parsers ---
|
||||
|
||||
def _radar_meta_base() -> Dict[str, Any]:
|
||||
return {
|
||||
"satellite": None,
|
||||
"imaging_date": None,
|
||||
"imaging_mode": None,
|
||||
"polarization": None,
|
||||
"satellite_mode": None,
|
||||
"receiving_station": None,
|
||||
"orbit_circle": None,
|
||||
"scene_center_lon": None,
|
||||
"scene_center_lat": None,
|
||||
"acquisition_time_utc": None,
|
||||
"product_type": None,
|
||||
"product_level": None,
|
||||
"product_unique_id": None,
|
||||
}
|
||||
|
||||
|
||||
def _extract_date_yyyymmdd(value: Optional[str]) -> Optional[str]:
|
||||
if not value:
|
||||
return None
|
||||
value = value.strip()
|
||||
if len(value) < 8:
|
||||
return None
|
||||
# Find first 8-digit sequence
|
||||
for idx in range(0, len(value) - 7):
|
||||
chunk = value[idx:idx + 8]
|
||||
if chunk.isdigit():
|
||||
return chunk
|
||||
return None
|
||||
|
||||
|
||||
def _parse_coord_token(value: Optional[str]) -> Optional[float]:
|
||||
if not value:
|
||||
return None
|
||||
token = value.strip()
|
||||
if not token:
|
||||
return None
|
||||
sign = 1.0
|
||||
head = token[0].upper()
|
||||
if head in ("E", "W", "N", "S"):
|
||||
sign = -1.0 if head in ("W", "S") else 1.0
|
||||
token = token[1:]
|
||||
try:
|
||||
return float(token) * sign
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def parse_s1_radar_filename(folder_name: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Parses key info from a Sentinel-1 radar data folder name.
|
||||
Example: S1A_IW_SLC__1SDV_20250101T104105_...
|
||||
Returns a metadata dict.
|
||||
"""
|
||||
parts = folder_name.split('_')
|
||||
if len(parts) < 5 or not parts[0].startswith('S1'):
|
||||
return None
|
||||
|
||||
meta = _radar_meta_base()
|
||||
meta["satellite"] = parts[0]
|
||||
meta["imaging_date"] = parts[4].split('T')[0]
|
||||
meta["imaging_mode"] = parts[1]
|
||||
polarization = parts[3] # e.g., '1SDV' -> 'DV' is dual-pol VV/VH
|
||||
meta["polarization"] = polarization[2:] if len(polarization) > 2 else polarization
|
||||
return meta
|
||||
|
||||
|
||||
def parse_s1_orbit_filename(file_name: str) -> Optional[Tuple[str, str]]:
|
||||
"""
|
||||
Parses key info from a Sentinel-1 orbit file name.
|
||||
Example: S1A_OPER_AUX_POEORB_OPOD_20250121T120000_..._V20250101_...EOF
|
||||
Returns: (satellite, date)
|
||||
"""
|
||||
parts = file_name.split('_')
|
||||
if len(parts) < 8 or not parts[0].startswith('S1'):
|
||||
return None
|
||||
|
||||
satellite = parts[0]
|
||||
# Find the validation date part, e.g., V20250101
|
||||
date_part = next((p for p in parts if p.startswith('V20')), None)
|
||||
if not date_part or len(date_part) < 9:
|
||||
return None
|
||||
|
||||
return satellite, date_part[1:9]
|
||||
|
||||
|
||||
# --- Land-Viewer (LT1) Parsers ---
|
||||
|
||||
def parse_lt1_radar_filename(folder_name: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Parses key info from a Land-Viewer (LT1) radar data folder name.
|
||||
Example: LT1B_MONO_SYC_STRIP1_018153_E135.4_N48.3_20250701_SLC_HH_S2A_0000790171
|
||||
Returns a metadata dict.
|
||||
"""
|
||||
parts = folder_name.split('_')
|
||||
if len(parts) < 10 or not parts[0].startswith('LT1'):
|
||||
return None
|
||||
|
||||
meta = _radar_meta_base()
|
||||
meta["satellite"] = parts[0]
|
||||
if len(parts) > 1:
|
||||
meta["satellite_mode"] = parts[1]
|
||||
if len(parts) > 2:
|
||||
meta["receiving_station"] = parts[2]
|
||||
if len(parts) > 3:
|
||||
meta["imaging_mode"] = parts[3]
|
||||
if len(parts) > 4:
|
||||
meta["orbit_circle"] = parts[4]
|
||||
if len(parts) > 5:
|
||||
meta["scene_center_lon"] = _parse_coord_token(parts[5])
|
||||
if len(parts) > 6:
|
||||
meta["scene_center_lat"] = _parse_coord_token(parts[6])
|
||||
if len(parts) > 7:
|
||||
meta["imaging_date"] = _extract_date_yyyymmdd(parts[7])
|
||||
meta["acquisition_time_utc"] = parts[7]
|
||||
if len(parts) > 8:
|
||||
meta["product_type"] = parts[8]
|
||||
if len(parts) > 9:
|
||||
meta["polarization"] = parts[9]
|
||||
if len(parts) > 10:
|
||||
meta["product_level"] = parts[10]
|
||||
if len(parts) > 11:
|
||||
meta["product_unique_id"] = parts[11]
|
||||
|
||||
return meta
|
||||
|
||||
|
||||
def parse_lt1_orbit_filename(file_name: str) -> Optional[Tuple[str, str]]:
|
||||
"""
|
||||
Parses key info from a Land-Viewer (LT1) GPS data file name.
|
||||
Example: LT1B_GpsData_GAS_C_20250701.txt
|
||||
Returns: (satellite, date)
|
||||
"""
|
||||
parts = file_name.split('_')
|
||||
if len(parts) < 4 or not file_name.endswith('.txt') or not parts[0].startswith('LT1'):
|
||||
return None
|
||||
|
||||
satellite = parts[0]
|
||||
date_str = parts[-1].split('.')[0]
|
||||
return satellite, date_str
|
||||
|
||||
|
||||
# --- GF3 (GaoFen-3) Parsers ---
|
||||
|
||||
def parse_gf3_l2_dirname(folder_name: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Parses key info from a GF3 L2 output directory name.
|
||||
Supports two naming patterns:
|
||||
1. Our pipeline output: gf3_GF3_SAR_<...>_<date>_<...> (prefixed with gf3_)
|
||||
2. Raw GF3 product name: GF3_SAR_<mode>_<pol>_<orbit>_<date>_<...>
|
||||
Example: GF3_SAR_UFS_HH_011234_E120.5_N31.2_20230615_L1A_HH_L10003456789
|
||||
"""
|
||||
name = folder_name
|
||||
# Strip our pipeline prefix
|
||||
if name.startswith("gf3_"):
|
||||
name = name[4:]
|
||||
if not name.startswith("GF3"):
|
||||
return None
|
||||
|
||||
meta = _radar_meta_base()
|
||||
meta["satellite"] = "GF3"
|
||||
|
||||
parts = name.split("_")
|
||||
# Try to extract date: first 8-digit segment
|
||||
for part in parts:
|
||||
date = _extract_date_yyyymmdd(part)
|
||||
if date:
|
||||
meta["imaging_date"] = date
|
||||
break
|
||||
|
||||
# Try to extract polarization
|
||||
for pol in ("HH", "HV", "VH", "VV"):
|
||||
if pol in parts:
|
||||
meta["polarization"] = pol
|
||||
break
|
||||
|
||||
# Try to extract imaging mode (3rd part for standard GF3 naming)
|
||||
if len(parts) >= 3:
|
||||
mode = parts[2] # e.g. UFS, FSI, QPSI
|
||||
if mode.isalpha() and len(mode) <= 6:
|
||||
meta["imaging_mode"] = mode
|
||||
|
||||
# Try to extract coordinates
|
||||
for part in parts:
|
||||
if part.startswith("E") or part.startswith("W"):
|
||||
meta["scene_center_lon"] = _parse_coord_token(part)
|
||||
elif part.startswith("N") or part.startswith("S"):
|
||||
meta["scene_center_lat"] = _parse_coord_token(part)
|
||||
|
||||
return meta
|
||||
|
||||
|
||||
# --- Generic Parsers Dispatcher ---
|
||||
|
||||
# List of available radar and orbit parsers
|
||||
RADAR_PARSERS: List[Callable[[str], Optional[Dict[str, Any]]]] = [
|
||||
parse_lt1_radar_filename,
|
||||
parse_s1_radar_filename,
|
||||
parse_gf3_l2_dirname,
|
||||
]
|
||||
|
||||
ORBIT_PARSERS: List[Callable[[str], Optional[Tuple[str, str]]]] = [
|
||||
parse_lt1_orbit_filename,
|
||||
parse_s1_orbit_filename,
|
||||
]
|
||||
|
||||
def get_parser(filename: str, parsers: list) -> Optional[tuple]:
|
||||
"""Tries a list of parsers on a filename and returns the first success."""
|
||||
for parser in parsers:
|
||||
result = parser(filename)
|
||||
if result:
|
||||
return result
|
||||
return None
|
||||
|
||||
|
||||
# --- XML and File Utilities ---
|
||||
|
||||
def find_xml_file(directory: str) -> Optional[str]:
|
||||
"""
|
||||
Finds the most relevant XML file in a given directory.
|
||||
It prioritizes files ending with '.meta.xml'.
|
||||
"""
|
||||
try:
|
||||
xml_files = [f for f in os.listdir(directory) if f.lower().endswith('.xml')]
|
||||
except FileNotFoundError:
|
||||
return None # Directory might not exist if parsing fails early
|
||||
|
||||
if not xml_files:
|
||||
return None
|
||||
|
||||
# Prioritize '.meta.xml'
|
||||
for file in xml_files:
|
||||
if file.lower().endswith('.meta.xml'):
|
||||
return os.path.join(directory, file)
|
||||
|
||||
# Fallback: if only one XML file exists, use it.
|
||||
if len(xml_files) == 1:
|
||||
return os.path.join(directory, xml_files[0])
|
||||
|
||||
# Ambiguous case: multiple XMLs, none are '.meta.xml'.
|
||||
print(f"Warning: Multiple XML files found in {directory}, none is '.meta.xml'. Using first one: {xml_files[0]}")
|
||||
return os.path.join(directory, xml_files[0])
|
||||
|
||||
|
||||
def parse_xml_metadata(
|
||||
xml_file_path: str
|
||||
) -> Optional[Tuple[Optional[List[Tuple[float, float]]], Optional[Dict[str, Any]]]]:
|
||||
"""
|
||||
Parses metadata from a radar data XML file.
|
||||
Returns a tuple containing:
|
||||
- A list of corner coordinates (polygon).
|
||||
- A metadata dict (orbit direction, imaging mode, polarization, etc).
|
||||
"""
|
||||
xml_name = os.path.basename(xml_file_path or "")
|
||||
try:
|
||||
parser = _create_secure_xml_parser()
|
||||
tree = etree.parse(xml_file_path, parser=parser)
|
||||
root = tree.getroot()
|
||||
|
||||
# Standard way to get namespace map. The key is the prefix, value is the URI.
|
||||
# If there's a default namespace, the key is None.
|
||||
ns = root.nsmap
|
||||
|
||||
# If there is a default namespace, lxml requires a prefix for it in xpath.
|
||||
# We can make one up, e.g., 'def'.
|
||||
ns_prefix = ''
|
||||
if None in ns:
|
||||
ns['def'] = ns.pop(None)
|
||||
ns_prefix = 'def:'
|
||||
|
||||
def _get_first_text(paths: List[str]) -> Optional[str]:
|
||||
for path in paths:
|
||||
elements = root.xpath(path, namespaces=ns)
|
||||
for element in elements:
|
||||
if element is not None and element.text:
|
||||
value = element.text.strip()
|
||||
if value:
|
||||
return value
|
||||
return None
|
||||
|
||||
def _to_float(value: Optional[str]) -> Optional[float]:
|
||||
try:
|
||||
if value is None or value == "":
|
||||
return None
|
||||
return float(value)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
def _to_int(value: Optional[str]) -> Optional[int]:
|
||||
try:
|
||||
if value is None or value == "":
|
||||
return None
|
||||
return int(float(value))
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
def _build_corner_pixel_mapping(corner_details: Dict[str, Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
||||
if len(corner_details) < 4:
|
||||
return None
|
||||
|
||||
ref_rows = []
|
||||
ref_cols = []
|
||||
for info in corner_details.values():
|
||||
if info.get("ref_row") is None or info.get("ref_col") is None:
|
||||
return None
|
||||
ref_rows.append(int(info["ref_row"]))
|
||||
ref_cols.append(int(info["ref_col"]))
|
||||
|
||||
min_row, max_row = min(ref_rows), max(ref_rows)
|
||||
min_col, max_col = min(ref_cols), max(ref_cols)
|
||||
remaining = set(corner_details.keys())
|
||||
|
||||
def pick(target_row: int, target_col: int) -> Optional[Tuple[float, float]]:
|
||||
if not remaining:
|
||||
return None
|
||||
ranked = []
|
||||
for name in remaining:
|
||||
info = corner_details[name]
|
||||
row = int(info["ref_row"])
|
||||
col = int(info["ref_col"])
|
||||
score = abs(row - target_row) + abs(col - target_col)
|
||||
ranked.append((score, abs(row - target_row), abs(col - target_col), name))
|
||||
ranked.sort()
|
||||
chosen_name = ranked[0][3]
|
||||
remaining.remove(chosen_name)
|
||||
chosen = corner_details[chosen_name]
|
||||
return float(chosen["lon"]), float(chosen["lat"])
|
||||
|
||||
top_left = pick(min_row, min_col)
|
||||
top_right = pick(min_row, max_col)
|
||||
bottom_left = pick(max_row, min_col)
|
||||
bottom_right = pick(max_row, max_col)
|
||||
|
||||
if not all([top_left, top_right, bottom_left, bottom_right]):
|
||||
return None
|
||||
|
||||
return {
|
||||
"top_left": [top_left[0], top_left[1]],
|
||||
"top_right": [top_right[0], top_right[1]],
|
||||
"bottom_left": [bottom_left[0], bottom_left[1]],
|
||||
"bottom_right": [bottom_right[0], bottom_right[1]],
|
||||
"source": "xml_ref_row_col",
|
||||
}
|
||||
|
||||
corners = ['bottomLeft', 'bottomRight', 'topRight', 'topLeft']
|
||||
polygon = []
|
||||
|
||||
# --- Parse Orbit Direction ---
|
||||
orbit_direction = _get_first_text([
|
||||
f".//{ns_prefix}pass",
|
||||
f".//{ns_prefix}orbitDirection",
|
||||
"//*[local-name()='pass']",
|
||||
"//*[local-name()='orbitDirection']",
|
||||
])
|
||||
if orbit_direction:
|
||||
orbit_direction = orbit_direction.strip().upper()
|
||||
|
||||
# --- Parse Imaging Mode ---
|
||||
imaging_mode = _get_first_text([
|
||||
f".//{ns_prefix}acquisitionInfo/{ns_prefix}imagingMode",
|
||||
f".//{ns_prefix}orderInfo/{ns_prefix}imagingMode",
|
||||
"//*[local-name()='acquisitionInfo']/*[local-name()='imagingMode']",
|
||||
"//*[local-name()='orderInfo']/*[local-name()='imagingMode']",
|
||||
])
|
||||
|
||||
# --- Parse Polarization ---
|
||||
polarization = _get_first_text([
|
||||
f".//{ns_prefix}acquisitionInfo/{ns_prefix}polarisationMode",
|
||||
f".//{ns_prefix}polarisationList/{ns_prefix}polLayer",
|
||||
f".//{ns_prefix}polList/{ns_prefix}polLayer",
|
||||
"//*[local-name()='acquisitionInfo']/*[local-name()='polarisationMode']",
|
||||
"//*[local-name()='polarisationList']/*[local-name()='polLayer']",
|
||||
"//*[local-name()='polList']/*[local-name()='polLayer']",
|
||||
])
|
||||
|
||||
# --- Parse Receiving Station ---
|
||||
receiving_station = _get_first_text([
|
||||
f".//{ns_prefix}generationInfo/{ns_prefix}receivingStation",
|
||||
"//*[local-name()='generationInfo']/*[local-name()='receivingStation']",
|
||||
])
|
||||
|
||||
# --- Parse Satellite Mode ---
|
||||
satellite_mode = _get_first_text([
|
||||
f".//{ns_prefix}generalHeader/{ns_prefix}satelliteMode",
|
||||
f".//{ns_prefix}satelliteMode",
|
||||
"//*[local-name()='generalHeader']/*[local-name()='satelliteMode']",
|
||||
"//*[local-name()='satelliteMode']",
|
||||
])
|
||||
|
||||
# --- Parse Orbit Circle (Abs Orbit) ---
|
||||
orbit_circle = _get_first_text([
|
||||
f".//{ns_prefix}missionInfo/{ns_prefix}absOrbit",
|
||||
f".//{ns_prefix}absOrbit",
|
||||
"//*[local-name()='missionInfo']/*[local-name()='absOrbit']",
|
||||
"//*[local-name()='absOrbit']",
|
||||
])
|
||||
|
||||
# --- Scene Center Coordinates ---
|
||||
scene_center_lon = _to_float(_get_first_text([
|
||||
f".//{ns_prefix}sceneCenterCoord/{ns_prefix}lon",
|
||||
"//*[local-name()='sceneCenterCoord']/*[local-name()='lon']",
|
||||
]))
|
||||
scene_center_lat = _to_float(_get_first_text([
|
||||
f".//{ns_prefix}sceneCenterCoord/{ns_prefix}lat",
|
||||
"//*[local-name()='sceneCenterCoord']/*[local-name()='lat']",
|
||||
]))
|
||||
|
||||
# --- Acquisition Time ---
|
||||
acquisition_time_utc = _get_first_text([
|
||||
f".//{ns_prefix}sceneCenterCoord/{ns_prefix}azimuthTimeUTC",
|
||||
f".//{ns_prefix}sceneInfo/{ns_prefix}start/{ns_prefix}timeUTC",
|
||||
"//*[local-name()='sceneCenterCoord']/*[local-name()='azimuthTimeUTC']",
|
||||
"//*[local-name()='sceneInfo']/*[local-name()='start']/*[local-name()='timeUTC']",
|
||||
])
|
||||
|
||||
# --- Product Type / Level / Unique ID ---
|
||||
product_type = _get_first_text([
|
||||
f".//{ns_prefix}imageDataInfo/{ns_prefix}imageDataType",
|
||||
f".//{ns_prefix}orderInfo/{ns_prefix}productVariant",
|
||||
f".//{ns_prefix}imageDataInfo/{ns_prefix}imageDataFormat",
|
||||
"//*[local-name()='imageDataInfo']/*[local-name()='imageDataType']",
|
||||
"//*[local-name()='orderInfo']/*[local-name()='productVariant']",
|
||||
"//*[local-name()='imageDataInfo']/*[local-name()='imageDataFormat']",
|
||||
])
|
||||
product_level = _get_first_text([
|
||||
f".//{ns_prefix}generalHeader/{ns_prefix}itemName",
|
||||
"//*[local-name()='generalHeader']/*[local-name()='itemName']",
|
||||
])
|
||||
product_unique_id = _get_first_text([
|
||||
f".//{ns_prefix}logicalProductID",
|
||||
f".//{ns_prefix}sceneID",
|
||||
"//*[local-name()='logicalProductID']",
|
||||
"//*[local-name()='sceneID']",
|
||||
])
|
||||
|
||||
# --- Direction / Start-Layout related fields ---
|
||||
look_direction = _get_first_text([
|
||||
f".//{ns_prefix}acquisitionInfo/{ns_prefix}lookDirection",
|
||||
f".//{ns_prefix}orderInfo/{ns_prefix}lookDirection",
|
||||
"//*[local-name()='acquisitionInfo']/*[local-name()='lookDirection']",
|
||||
"//*[local-name()='orderInfo']/*[local-name()='lookDirection']",
|
||||
])
|
||||
if look_direction:
|
||||
look_direction = look_direction.strip().upper()
|
||||
|
||||
image_data_start_with = _get_first_text([
|
||||
f".//{ns_prefix}imageDataStartWith",
|
||||
"//*[local-name()='imageDataStartWith']",
|
||||
])
|
||||
quicklook_data_start_with = _get_first_text([
|
||||
f".//{ns_prefix}quicklookDataStartWith",
|
||||
"//*[local-name()='quicklookDataStartWith']",
|
||||
])
|
||||
geocoded_flag_text = _get_first_text([
|
||||
f".//{ns_prefix}geocodedFlag",
|
||||
"//*[local-name()='geocodedFlag']",
|
||||
])
|
||||
geocoded_flag = None
|
||||
if geocoded_flag_text is not None:
|
||||
geocoded_flag = geocoded_flag_text.strip().lower() in ("1", "true", "yes", "y")
|
||||
|
||||
# --- Parse Corner Coordinates ---
|
||||
corner_details: Dict[str, Dict[str, Any]] = {}
|
||||
for corner_name in corners:
|
||||
lon_path = f".//{ns_prefix}sceneCornerCoord[@name='{corner_name}']/{ns_prefix}lon"
|
||||
lat_path = f".//{ns_prefix}sceneCornerCoord[@name='{corner_name}']/{ns_prefix}lat"
|
||||
row_path = f".//{ns_prefix}sceneCornerCoord[@name='{corner_name}']/{ns_prefix}refRow"
|
||||
col_path = f".//{ns_prefix}sceneCornerCoord[@name='{corner_name}']/{ns_prefix}refColumn"
|
||||
|
||||
lon_elements = root.xpath(lon_path, namespaces=ns)
|
||||
lat_elements = root.xpath(lat_path, namespaces=ns)
|
||||
row_elements = root.xpath(row_path, namespaces=ns)
|
||||
col_elements = root.xpath(col_path, namespaces=ns)
|
||||
|
||||
if lon_elements and lat_elements and lon_elements[0].text and lat_elements[0].text:
|
||||
lon = float(lon_elements[0].text)
|
||||
lat = float(lat_elements[0].text)
|
||||
polygon.append((lon, lat))
|
||||
ref_row = _to_int(row_elements[0].text if row_elements and row_elements[0].text else None)
|
||||
ref_col = _to_int(col_elements[0].text if col_elements and col_elements[0].text else None)
|
||||
corner_details[corner_name] = {
|
||||
"lon": lon,
|
||||
"lat": lat,
|
||||
"ref_row": ref_row,
|
||||
"ref_col": ref_col,
|
||||
}
|
||||
else:
|
||||
# This might not be a critical error if we just want the orbit direction
|
||||
# but for now, we'll keep it strict.
|
||||
print(f"Warning: Could not find or parse corner '{corner_name}' in XML '{xml_name}'")
|
||||
return None, None
|
||||
|
||||
if len(polygon) == 4:
|
||||
polygon.append(polygon[0])
|
||||
corner_pixel_mapping = _build_corner_pixel_mapping(corner_details)
|
||||
meta = {
|
||||
"orbit_direction": orbit_direction,
|
||||
"imaging_mode": imaging_mode,
|
||||
"polarization": polarization,
|
||||
"receiving_station": receiving_station,
|
||||
"satellite_mode": satellite_mode,
|
||||
"orbit_circle": orbit_circle,
|
||||
"scene_center_lon": scene_center_lon,
|
||||
"scene_center_lat": scene_center_lat,
|
||||
"acquisition_time_utc": acquisition_time_utc,
|
||||
"product_type": product_type,
|
||||
"product_level": product_level,
|
||||
"product_unique_id": product_unique_id,
|
||||
"look_direction": look_direction,
|
||||
"image_data_start_with": image_data_start_with,
|
||||
"quicklook_data_start_with": quicklook_data_start_with,
|
||||
"geocoded_flag": geocoded_flag,
|
||||
"corner_ref_pixels": {
|
||||
key: {
|
||||
"ref_row": value.get("ref_row"),
|
||||
"ref_col": value.get("ref_col"),
|
||||
}
|
||||
for key, value in corner_details.items()
|
||||
},
|
||||
"corner_pixel_mapping": corner_pixel_mapping,
|
||||
}
|
||||
return polygon, meta
|
||||
|
||||
return None, None
|
||||
|
||||
except Exception as exc:
|
||||
print(f"Error parsing XML metadata in '{xml_name}': {exc.__class__.__name__}")
|
||||
return None, None
|
||||
Reference in New Issue
Block a user