# /********************************************************************** # * * # * Copyright (c) 2026 Javier Brana * # * * # * This program is free software; you can redistribute it and/or modify* # * it under the terms of the GNU Lesser General Public License (LGPL) * # * as published by the Free Software Foundation; either version 2 of * # * the License, or (at your option) any later version. * # * for detail see the LICENCE text file. * # * * # * This program is distributed in the hope that it will be useful, * # * but WITHOUT ANY WARRANTY; without even the implied warranty of * # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * # * GNU Library General Public License for more details. * # * * # * You should have received a copy of the GNU Library General Public * # * License along with this program; if not, write to the Free Software * # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307* # * USA * # * * # *********************************************************************** """ Proyecciones y transformaciones geodésicas unificadas para PVPlant. Reemplaza el uso disperso de la librería 'utm' con pyproj (PROJ), soporte multi-zona UTM y transformaciones entre datums. Uso básico: from lib.projection import latlon_to_utm, utm_to_latlon, get_utm_zone """ import FreeCAD from pyproj import CRS, Transformer from pyproj.aoi import AreaOfInterest from pyproj.database import query_utm_crs_info # WGS84 – sistema de coordenadas geográfico de referencia _WGS84 = CRS.from_epsg(4326) # Cache de transformadores UTM por zona (lazy) _utm_transformers = {} def _get_utm_transformer(lat, lon): """Obtiene (o crea en caché) un transformador UTM para la zona de las coordenadas dadas. Returns: tuple: (transformer, zone_number, zone_letter) """ # Determinar la zona UTM a partir de lat/lon zone_number = int((lon + 180) / 6) + 1 if lat >= 0: zone_letter = 'N' epsg = 32600 + zone_number else: zone_letter = 'S' epsg = 32700 + zone_number cache_key = (zone_number, zone_letter) if cache_key not in _utm_transformers: utm_crs = CRS.from_epsg(epsg) _utm_transformers[cache_key] = Transformer.from_crs( _WGS84, utm_crs, always_xy=True ) return _utm_transformers[cache_key], zone_number, zone_letter def latlon_to_utm(lat, lon): """Convierte coordenadas geográficas (WGS84) a UTM (este, norte, zona, letra). Args: lat (float): Latitud en grados. lon (float): Longitud en grados. Returns: tuple: (easting, northing, zone_number, zone_letter) easting/northing en metros. """ transformer, zone_number, zone_letter = _get_utm_transformer(lat, lon) easting, northing = transformer.transform(lon, lat) return easting, northing, zone_number, zone_letter def utm_to_latlon(easting, northing, zone_number, zone_letter='N'): """Convierte coordenadas UTM a geográficas (WGS84). Args: easting (float): Coordenada E en metros. northing (float): Coordenada N en metros. zone_number (int): Número de zona UTM (1-60). zone_letter (str): Letra de zona ('N' o 'S'). Returns: tuple: (latitude, longitude) en grados. """ if zone_letter.upper() not in ('N', 'S'): zone_letter = 'N' epsg = 32600 + zone_number if zone_letter.upper() == 'N' else 32700 + zone_number utm_crs = CRS.from_epsg(epsg) transformer = Transformer.from_crs(utm_crs, _WGS84, always_xy=True) lon, lat = transformer.transform(easting, northing) return lat, lon def get_utm_zone(lat, lon): """Obtiene la zona UTM para unas coordenadas dadas. Args: lat (float): Latitud en grados. lon (float): Longitud en grados. Returns: tuple: (zone_number, zone_letter) """ _, _, zone_number, zone_letter = latlon_to_utm(lat, lon) return zone_number, zone_letter def latlon_to_utm_vector(lat, lon, elevation=0.0): """Convierte lat/lon/elevación a un FreeCAD.Vector en UTM (mm). Args: lat (float): Latitud en grados. lon (float): Longitud en grados. elevation (float): Elevación en metros (default 0). Returns: FreeCAD.Vector: Coordenadas UTM en milímetros. """ transformer, _, _ = _get_utm_transformer(lat, lon) easting, northing = transformer.transform(lon, lat) return FreeCAD.Vector( round(easting, 0), round(northing, 0), round(elevation, 0) ) * 1000