Fork of Lightning_Report adding: - n8n_report_branch.json: workflow branch for storm-triggered report delivery - report_service/: FastAPI microservice wrapping create_docx_report() so n8n can produce byte-identical reports without fighting the Python Code sandbox Made-with: Cursor
262 lines
9.9 KiB
Python
262 lines
9.9 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
UTM ED50 to WGS84 Coordinate Converter
|
|
|
|
This script converts UTM coordinates in 6-degree zones from ED50 (European Datum 1950)
|
|
reference system to WGS84 format.
|
|
|
|
Requirements:
|
|
pip install pyproj pandas
|
|
|
|
Usage:
|
|
python utm_ed50_to_wgs84_converter.py input_file.csv output_file.csv
|
|
python utm_ed50_to_wgs84_converter.py --interactive
|
|
"""
|
|
|
|
import argparse
|
|
import pandas as pd
|
|
import pyproj
|
|
from typing import Tuple, List, Optional
|
|
import sys
|
|
|
|
|
|
class UTMED50ToWGS84Converter:
|
|
"""Converter for UTM ED50 coordinates to WGS84."""
|
|
|
|
def __init__(self):
|
|
"""Initialize the converter with ED50 and WGS84 projections."""
|
|
self.ed50_utm_projections = {}
|
|
self.wgs84_utm_projections = {}
|
|
|
|
def get_ed50_utm_projection(self, zone: int, northern: bool = True) -> pyproj.Proj:
|
|
"""Get ED50 UTM projection for a specific zone."""
|
|
key = (zone, northern)
|
|
if key not in self.ed50_utm_projections:
|
|
hemisphere = 'N' if northern else 'S'
|
|
proj_string = f"+proj=utm +zone={zone} +ellps=intl +towgs84=-87,-98,-121,0,0,0,0 +units=m +no_defs"
|
|
self.ed50_utm_projections[key] = pyproj.Proj(proj_string)
|
|
return self.ed50_utm_projections[key]
|
|
|
|
def get_wgs84_utm_projection(self, zone: int, northern: bool = True) -> pyproj.Proj:
|
|
"""Get WGS84 UTM projection for a specific zone."""
|
|
key = (zone, northern)
|
|
if key not in self.wgs84_utm_projections:
|
|
hemisphere = 'N' if northern else 'S'
|
|
proj_string = f"+proj=utm +zone={zone} +ellps=WGS84 +datum=WGS84 +units=m +no_defs"
|
|
self.wgs84_utm_projections[key] = pyproj.Proj(proj_string)
|
|
return self.wgs84_utm_projections[key]
|
|
|
|
def convert_single_point(self, easting: float, northing: float, zone: int,
|
|
northern: bool = True) -> Tuple[float, float]:
|
|
"""
|
|
Convert a single UTM ED50 point to WGS84 lat/lon.
|
|
|
|
Args:
|
|
easting: UTM easting coordinate in meters
|
|
northing: UTM northing coordinate in meters
|
|
zone: UTM zone number (1-60)
|
|
northern: True if in northern hemisphere, False if southern
|
|
|
|
Returns:
|
|
Tuple of (latitude, longitude) in WGS84 decimal degrees
|
|
"""
|
|
if not (1 <= zone <= 60):
|
|
raise ValueError(f"Invalid UTM zone: {zone}. Must be between 1 and 60.")
|
|
|
|
# Create ED50 UTM projection
|
|
ed50_proj = self.get_ed50_utm_projection(zone, northern)
|
|
|
|
# Create WGS84 lat/lon projection
|
|
wgs84_latlon = pyproj.Proj('+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs')
|
|
|
|
# Create transformer from ED50 UTM to WGS84 lat/lon
|
|
transformer = pyproj.Transformer.from_proj(ed50_proj, wgs84_latlon)
|
|
|
|
# Transform coordinates directly to WGS84 lat/lon
|
|
wgs84_lon, wgs84_lat = transformer.transform(easting, northing)
|
|
|
|
return wgs84_lat, wgs84_lon
|
|
|
|
def convert_dataframe(self, df: pd.DataFrame, easting_col: str, northing_col: str,
|
|
zone_col: str, northern_col: Optional[str] = None,
|
|
northern_default: bool = True) -> pd.DataFrame:
|
|
"""
|
|
Convert UTM ED50 coordinates in a DataFrame to WGS84.
|
|
|
|
Args:
|
|
df: Input DataFrame with UTM coordinates
|
|
easting_col: Column name for easting coordinates
|
|
northing_col: Column name for northing coordinates
|
|
zone_col: Column name for UTM zone
|
|
northern_col: Column name for northern hemisphere flag (optional)
|
|
northern_default: Default value for northern hemisphere if column not provided
|
|
|
|
Returns:
|
|
DataFrame with additional WGS84 lat/lon columns
|
|
"""
|
|
result_df = df.copy()
|
|
result_df['wgs84_lat'] = None
|
|
result_df['wgs84_lon'] = None
|
|
|
|
for idx, row in df.iterrows():
|
|
try:
|
|
easting = float(row[easting_col])
|
|
northing = float(row[northing_col])
|
|
zone = int(row[zone_col])
|
|
|
|
if northern_col and northern_col in row:
|
|
northern = bool(row[northern_col])
|
|
else:
|
|
northern = northern_default
|
|
|
|
lat, lon = self.convert_single_point(easting, northing, zone, northern)
|
|
result_df.at[idx, 'wgs84_lat'] = lat
|
|
result_df.at[idx, 'wgs84_lon'] = lon
|
|
|
|
except (ValueError, KeyError) as e:
|
|
print(f"Warning: Could not convert row {idx}: {e}")
|
|
result_df.at[idx, 'wgs84_lat'] = None
|
|
result_df.at[idx, 'wgs84_lon'] = None
|
|
|
|
return result_df
|
|
|
|
|
|
def interactive_mode():
|
|
"""Run the converter in interactive mode."""
|
|
converter = UTMED50ToWGS84Converter()
|
|
|
|
print("UTM ED50 to WGS84 Coordinate Converter")
|
|
print("=" * 40)
|
|
print("Enter coordinates (type 'quit' to exit)")
|
|
print()
|
|
|
|
while True:
|
|
try:
|
|
# Get input
|
|
easting_input = input("Enter easting (meters): ").strip()
|
|
if easting_input.lower() == 'quit':
|
|
break
|
|
|
|
northing_input = input("Enter northing (meters): ").strip()
|
|
if northing_input.lower() == 'quit':
|
|
break
|
|
|
|
zone_input = input("Enter UTM zone (1-60): ").strip()
|
|
if zone_input.lower() == 'quit':
|
|
break
|
|
|
|
hemisphere_input = input("Enter hemisphere (N/S) [default: N]: ").strip().upper()
|
|
if hemisphere_input == 'QUIT':
|
|
break
|
|
|
|
# Parse inputs
|
|
easting = float(easting_input)
|
|
northing = float(northing_input)
|
|
zone = int(zone_input)
|
|
northern = hemisphere_input != 'S' if hemisphere_input else True
|
|
|
|
# Convert
|
|
lat, lon = converter.convert_single_point(easting, northing, zone, northern)
|
|
|
|
print(f"\nWGS84 Coordinates:")
|
|
print(f"Latitude: {lat:.8f}°")
|
|
print(f"Longitude: {lon:.8f}°")
|
|
print("-" * 40)
|
|
|
|
except ValueError as e:
|
|
print(f"Error: {e}")
|
|
print("Please enter valid numeric values.")
|
|
except KeyboardInterrupt:
|
|
print("\nExiting...")
|
|
break
|
|
|
|
|
|
def main():
|
|
"""Main function to handle command line arguments and file processing."""
|
|
parser = argparse.ArgumentParser(
|
|
description="Convert UTM ED50 coordinates to WGS84 format",
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog="""
|
|
Examples:
|
|
# Interactive mode
|
|
python utm_ed50_to_wgs84_converter.py --interactive
|
|
|
|
# Convert CSV file
|
|
python utm_ed50_to_wgs84_converter.py input.csv output.csv
|
|
|
|
# Convert with custom column names
|
|
python utm_ed50_to_wgs84_converter.py input.csv output.csv --easting-col X --northing-col Y --zone-col ZONE
|
|
"""
|
|
)
|
|
|
|
parser.add_argument('input_file', nargs='?', help='Input CSV file with UTM coordinates')
|
|
parser.add_argument('output_file', nargs='?', help='Output CSV file for WGS84 coordinates')
|
|
parser.add_argument('--interactive', '-i', action='store_true',
|
|
help='Run in interactive mode')
|
|
parser.add_argument('--easting-col', default='easting',
|
|
help='Column name for easting coordinates (default: easting)')
|
|
parser.add_argument('--northing-col', default='northing',
|
|
help='Column name for northing coordinates (default: northing)')
|
|
parser.add_argument('--zone-col', default='zone',
|
|
help='Column name for UTM zone (default: zone)')
|
|
parser.add_argument('--northern-col',
|
|
help='Column name for northern hemisphere flag (optional)')
|
|
parser.add_argument('--northern-default', action='store_true', default=True,
|
|
help='Default value for northern hemisphere (default: True)')
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.interactive:
|
|
interactive_mode()
|
|
return
|
|
|
|
if not args.input_file or not args.output_file:
|
|
parser.error("Both input_file and output_file are required when not in interactive mode")
|
|
|
|
try:
|
|
# Read input file
|
|
print(f"Reading input file: {args.input_file}")
|
|
df = pd.read_csv(args.input_file)
|
|
|
|
# Check required columns
|
|
required_cols = [args.easting_col, args.northing_col, args.zone_col]
|
|
missing_cols = [col for col in required_cols if col not in df.columns]
|
|
if missing_cols:
|
|
print(f"Error: Missing required columns: {missing_cols}")
|
|
print(f"Available columns: {list(df.columns)}")
|
|
sys.exit(1)
|
|
|
|
# Convert coordinates
|
|
print("Converting coordinates...")
|
|
converter = UTMED50ToWGS84Converter()
|
|
result_df = converter.convert_dataframe(
|
|
df,
|
|
args.easting_col,
|
|
args.northing_col,
|
|
args.zone_col,
|
|
args.northern_col,
|
|
args.northern_default
|
|
)
|
|
|
|
# Save output
|
|
print(f"Saving output file: {args.output_file}")
|
|
result_df.to_csv(args.output_file, index=False)
|
|
|
|
# Print summary
|
|
total_rows = len(result_df)
|
|
successful_conversions = result_df['wgs84_lat'].notna().sum()
|
|
print(f"\nConversion complete!")
|
|
print(f"Total rows: {total_rows}")
|
|
print(f"Successful conversions: {successful_conversions}")
|
|
print(f"Failed conversions: {total_rows - successful_conversions}")
|
|
|
|
except FileNotFoundError:
|
|
print(f"Error: Input file '{args.input_file}' not found")
|
|
sys.exit(1)
|
|
except Exception as e:
|
|
print(f"Error: {e}")
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |