Source code for layout.geo_coordinates

"""Definition of geospatial coordinate systems."""

import logging

import astropy.units as u
import numpy as np
import pyproj


[docs] class GeoCoordinates: """ Geospatial Coordinate systems. Defines UTM, WGS84 and ground (sim_telarray) coordinate systems. """ def __init__(self): """Initialize GeoCoordinates.""" self._logger = logging.getLogger(__name__)
[docs] def crs_utm(self, epsg): """ UTM coordinate system definition. Parameters ---------- epsg: int EPSG code for UTM zone. Returns ------- pyproj.CRS UTM coordinate system. """ if epsg: crs_utm = pyproj.CRS.from_user_input(epsg) self._logger.debug(f"UTM coordinate system: {crs_utm}") return crs_utm return None
[docs] @staticmethod def crs_wgs84(): """ WGS84 coordinate system definition. Returns ------- pyproj.CRS WGS84 coordinate system. """ return pyproj.CRS("EPSG:4326")
[docs] def crs_local(self, reference_point): """ Local coordinate system definition. This is a cartesian coordinate system with the origin at the array center. X-axis points towards geographic North, y-axis towards geographic West. Parameters ---------- reference_point: simtools.layout.telescope_position Reference coordinate. Returns ------- pyproj.CRS local coordinate system. """ try: if self._valid_reference_point(reference_point): _center_lat, _center_lon, _ = reference_point.get_coordinates("mercator") _scale_factor_k_0 = self._coordinate_scale_factor(reference_point) proj4_string = ( "+proj=tmerc +ellps=WGS84 +datum=WGS84" f" +lon_0={_center_lon} +lat_0={_center_lat}" f" +axis=nwu +units=m +k_0={_scale_factor_k_0}" ) crs_local = pyproj.CRS.from_proj4(proj4_string) self._logger.debug(f"Ground (sim_telarray) coordinate system: {crs_local}") return crs_local except AttributeError: self._logger.error("Failed to derive local coordinate system. Missing reference point") raise return None
def _valid_reference_point(self, reference_point): """ Check if reference point has valid long/lat coordinates (including altitude). This is required to derive the local coordinate system. Try if a conversion from UTM coordinates to long/lat is possible. Parameters ---------- reference_point: simtools.layout.telescope_position Reference coordinate. Returns ------- bool True if reference point has valid coordinates. """ _center_lat, _center_lon, _center_alt = reference_point.get_coordinates("mercator") if np.isnan(_center_alt.value): self._logger.debug("Missing array center altitude") return False if np.isnan(_center_lat.value) or np.isnan(_center_lon.value): self._logger.debug( "Invalid array center coordinates " f"(lat={_center_lat}, lon={_center_lon}, alt={_center_alt})" ) return False return True def _coordinate_scale_factor(self, reference_point): """ Derive coordinate scale factor for transformation into local coordinate system. Depends on latitude and altitude of array center. Ignores transformation of geodetic height to geocentric height (this introduces an error on the sub-mm scale). Parameters ---------- reference_point: simtools.layout.telescope_position Reference coordinate. Returns ------- k_0: float Scale factor for local coordinate system. Raises ------ AttributeError If reference_point does not have a valid center or UTM system is not defined. """ try: _center_lat, _, _centre_altitude = reference_point.get_coordinates("mercator") except AttributeError: self._logger.debug("Missing array center, cannot derive coordinate scale factor") raise crs_utm = self.crs_wgs84() _semi_major_axis = crs_utm.geodetic_crs.ellipsoid.semi_major_metre * u.m _semi_minor_axis = crs_utm.geodetic_crs.ellipsoid.semi_minor_metre * u.m _local_geo_radius = self._geocentric_radius(_center_lat, _semi_major_axis, _semi_minor_axis) return (_local_geo_radius.value + _centre_altitude.value) / _local_geo_radius.value def _geocentric_radius(self, latitude, semi_major_axis, semi_minor_axis): """ Calculate geocentric radius from WGS84 ellipsoid parameters. derivation: https://gis.stackexchange.com/questions/20200/how-do-you-compute-the-earths-radius-at-a-given-geodetic-latitude Parameters ---------- latitude: astropy.Quantity Latitude in degrees. semi_major_axis: astropy.Quantity Semi-major axis of ellipsoid in meters. semi_minor_axis: astropy.Quantity Semi-minor axis of ellipsoid in meters. Returns ------- float Ellipsoid radius at given latitude. """ _lat_rad = np.deg2rad(latitude) _numerator = (semi_major_axis**2 * np.cos(_lat_rad)) ** 2 + ( semi_minor_axis**2 * np.sin(_lat_rad) ) ** 2 _denominator = ( semi_major_axis**2 * np.cos(_lat_rad) ** 2 + semi_minor_axis**2 * np.sin(_lat_rad) ** 2 ) return np.sqrt(_numerator / _denominator)