spatial-asset-mgmt / app /services /geo_processor.py
nayanpr620's picture
Upload folder using huggingface_hub
2cb1fe9 verified
Raw
History Blame Contribute Delete
2.89 kB
"""
CRS-aware GeoTIFF metadata extraction.
"""
from __future__ import annotations
from typing import Any, Dict, Optional
from pyproj import Geod, Transformer
from app.utils.geo_utils import close_ring
def _estimate_gsd_meters(src) -> Optional[float]:
"""Estimate average meters-per-pixel using geodesic edge lengths."""
try:
transformer = Transformer.from_crs(src.crs, "EPSG:4326", always_xy=True)
geod = Geod(ellps="WGS84")
top_left = src.transform * (0, 0)
top_right = src.transform * (1, 0)
bottom_left = src.transform * (0, 1)
lon1, lat1 = transformer.transform(*top_left)
lon2, lat2 = transformer.transform(*top_right)
lon3, lat3 = transformer.transform(*bottom_left)
width_m = geod.line_length([lon1, lon2], [lat1, lat2])
height_m = geod.line_length([lon1, lon3], [lat1, lat3])
if width_m <= 0 or height_m <= 0:
return None
return round((width_m + height_m) / 2, 4)
except Exception:
return None
def extract_geo_reference(image_path: str) -> Optional[Dict[str, Any]]:
"""
Extract a full CRS-aware georeference description from a raster file.
Returns WGS84-facing metadata suitable for API responses and geometry projection.
"""
try:
import rasterio
with rasterio.open(image_path) as src:
if src.crs is None:
return None
transformer = Transformer.from_crs(src.crs, "EPSG:4326", always_xy=True)
bounds = src.bounds
bounds_corners = close_ring([
[bounds.left, bounds.top],
[bounds.right, bounds.top],
[bounds.right, bounds.bottom],
[bounds.left, bounds.bottom],
])
bounds_wgs84_ring = [[*transformer.transform(x, y)] for x, y in bounds_corners]
lon_values = [point[0] for point in bounds_wgs84_ring]
lat_values = [point[1] for point in bounds_wgs84_ring]
return {
"source_crs": src.crs.to_string(),
"epsg": src.crs.to_epsg(),
"transform": list(src.transform)[:6],
"width": src.width,
"height": src.height,
"bounds_source": [bounds.left, bounds.bottom, bounds.right, bounds.top],
"bounds_wgs84": [min(lon_values), min(lat_values), max(lon_values), max(lat_values)],
"bounds_wgs84_ring": bounds_wgs84_ring,
"source_resolution": [float(src.res[0]), float(src.res[1])],
"approx_gsd_m": _estimate_gsd_meters(src),
}
except Exception:
return None
def extract_geo_transform(image_path: str) -> Optional[Dict[str, Any]]:
"""
Backward-compatible alias retained for existing imports.
"""
return extract_geo_reference(image_path)