import math from abc import ABC, abstractmethod from f02_1_flight_lifecycle_manager import CameraParameters class IGSDCalculator(ABC): @abstractmethod def compute_gsd(self, altitude: float, camera_params: CameraParameters) -> float: pass @abstractmethod def altitude_to_scale(self, altitude: float, focal_length: float) -> float: pass @abstractmethod def meters_per_pixel(self, lat: float, zoom: int) -> float: pass @abstractmethod def gsd_from_camera(self, altitude: float, focal_length: float, sensor_width: float, image_width: int) -> float: pass class GSDCalculator(IGSDCalculator): """H02: Ground Sampling Distance computations for altitude and coordinate systems.""" def compute_gsd(self, altitude: float, camera_params: CameraParameters) -> float: w = camera_params.resolution.get("width", 1920) return self.gsd_from_camera(altitude, camera_params.focal_length_mm, camera_params.sensor_width_mm, w) def altitude_to_scale(self, altitude: float, focal_length: float) -> float: if focal_length <= 0: return 1.0 return altitude / focal_length def meters_per_pixel(self, lat: float, zoom: int) -> float: return 156543.03392 * math.cos(math.radians(lat)) / (2 ** zoom) def gsd_from_camera(self, altitude: float, focal_length: float, sensor_width: float, image_width: int) -> float: if focal_length <= 0 or image_width <= 0: return 0.0 return (altitude * sensor_width) / (focal_length * image_width)