Carma-platform v4.2.0
CARMA Platform is built on robot operating system (ROS) and utilizes open source software (OSS) that enables Cooperative Driving Automation (CDA) features to allow Automated Driving Systems to interact and cooperate with infrastructure and other vehicles through communication.
osm_transform.py
Go to the documentation of this file.
1"""
2This script relocates and optionally rotates an OpenStreetMap (OSM) file that uses a custom geoReference. It's useful when you want to:
3- Move a local map to a new geographic location.
4- Preserve geometry, scale, and layout.
5- Rotate the map around its origin (for alignment or testing).
6---
7
8Steps:
9
101. Read the input OSM XML file.
112. Extract the original geoReference (a Transverse Mercator projection centered at some latitude/longitude).
123. Convert each nodes lat/lon to projected (X, Y) coordinates using the original projection.
134. Apply a 2D rotation (optional) around the local origin (0, 0).
145. Transform the rotated (X, Y) into new lat/lon coordinates using a new projection centered at a new location.
156. Update the <geoReference> tag to reflect the new center.
167. Save the updated OSM XML file with transformed coordinates.
17
18---
19
20Inputs:
21
221- input_file.osm: An OSM XML file that:
23- Contains a <geoReference> string using +proj=tmerc
24- Has <node> elements with lat and lon attributes
25
262- output_file.osm: The transformed OSM map name that:
27- All node positions have been relocated and optionally rotated
28- The <geoReference> tag is updated to match the new map center
29- The map geometry is preserved in relative terms but relocated globally
30
31---
32
33Parameters to adjust to transform:
34
35- rotation_deg: Rotation angle in degrees (positive = counter-clockwise)
36- new_lat_0, new_lon_0: New center location in geographic coordinates (latitude, longitude)
37
38--
39
40Dependency:
41pip install lxml pyproj
42
43--
44how to run the script:
45
46python osm_transform.py suntrax.osm suntrax_transformed.osm
47"""
48
49import argparse
50from lxml import etree
51from pyproj import CRS, Transformer
52import math
53import numpy as np
54
55# === Fixed reference and rotation config ===
56# TODO Add the desired geoReference and rotation reference for the output map here (They can both be the same or different based on the application)
57# New Georeference
58new_lat_0 = 0.0
59new_lon_0 = 0.0
60# New Rotation Reference
61rotate_lat = 0.0
62rotate_lon = 0.0
63# Rotation Reference
64# TODO Add the desired rotation of the output map here
65rotation_deg = 0.0 # Counter-clockwise
66
67theta_rad = math.radians(rotation_deg)
68
69# === Parse command-line arguments ===
70parser = argparse.ArgumentParser(description="Relocate and rotate OSM map geometry around a new reference point.")
71parser.add_argument("input_file", help="Path to the input .osm file")
72parser.add_argument("output_file", help="Path to the output .osm file")
73args = parser.parse_args()
74
75# === Parse XML ===
76tree = etree.parse(args.input_file)
77root = tree.getroot()
78
79# === Read original geoReference string from map file ===
80geo_ref_elem = root.find("geoReference")
81if geo_ref_elem is None or (not geo_ref_elem.text and not geo_ref_elem.attrib.get("v")):
82 raise ValueError("āŒ geoReference tag not found or is empty in the OSM file.")
83
84if geo_ref_elem.text:
85 old_proj_str = geo_ref_elem.text.strip()
86else:
87 old_proj_str = geo_ref_elem.attrib.get("v").strip()
88print(f"šŸ“Œ Extracted old geoReference:\n{old_proj_str}\n")
89
90new_proj_str = f"+proj=tmerc +lat_0={new_lat_0} +lon_0={new_lon_0} +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +geoidgrids=egm96_15.gtx +vunits=m +no_defs"
91print(f"šŸ“Œ Updated new geoReference:\n{new_proj_str}\n")
92
93# === Coordinate Systems ===
94crs_wgs84 = CRS.from_epsg(4326)
95crs_old = CRS.from_proj4(old_proj_str)
96crs_new = CRS.from_proj4(new_proj_str)
97
98# === Transformers ===
99to_old_xy = Transformer.from_crs(crs_wgs84, crs_old, always_xy=True)
100to_new_latlon = Transformer.from_crs(crs_new, crs_wgs84, always_xy=True)
101
102# === Update <geoReference> to new projection ===
103if geo_ref_elem.text:
104 geo_ref_elem.text = new_proj_str
105else:
106 geo_ref_elem.set("v", new_proj_str)
107
108# === Step 1: Convert all nodes to old projected coordinates and compute centroid ===
109xs_old, ys_old = [], []
110for node in root.findall("node"):
111 lat = float(node.get("lat"))
112 lon = float(node.get("lon"))
113 x_old, y_old = to_old_xy.transform(lon, lat)
114 xs_old.append(x_old)
115 ys_old.append(y_old)
116
117cx_old = np.mean(xs_old)
118cy_old = np.mean(ys_old)
119
120# === Step 2: Compute shift offset to move centroid to new reference point ===
121to_new_xy = Transformer.from_crs(crs_wgs84, crs_new, always_xy=True)
122rotate_x, rotate_y = to_new_xy.transform(rotate_lon, rotate_lat)
123
124# Offset from old centroid to new rotation center
125offset_x = rotate_x - cx_old
126offset_y = rotate_y - cy_old
127
128# === Step 3: Apply shift + rotation ===
129for i, node in enumerate(root.findall("node")):
130 x = xs_old[i] + offset_x
131 y = ys_old[i] + offset_y
132
133 # Rotate around new center
134 x_rel, y_rel = x - rotate_x, y - rotate_y
135 x_rot = x_rel * math.cos(theta_rad) - y_rel * math.sin(theta_rad)
136 y_rot = x_rel * math.sin(theta_rad) + y_rel * math.cos(theta_rad)
137 x_rot += rotate_x
138 y_rot += rotate_y
139
140 # Convert to final lat/lon using new projection
141 new_lon, new_lat = to_new_latlon.transform(x_rot, y_rot)
142 node.set("lat", f"{new_lat:.10f}")
143 node.set("lon", f"{new_lon:.10f}")
144 # Update tag values for lat and lon
145 for tag in node.findall('tag'):
146 if tag.get('k') == 'lat':
147 tag.set('v', f"{new_lat:.10f}")
148 elif tag.get('k') == 'lon':
149 tag.set('v', f"{new_lon:.10f}")
150
151# === Save transformed file ===
152tree.write(args.output_file, pretty_print=True, xml_declaration=True, encoding="UTF-8")
153print(f"\nāœ… Map shifted and rotated. Output saved to: {args.output_file}")