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.
xodr_transform.py
Go to the documentation of this file.
1# Copyright 2025 Leidos
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""
16This script transforms an XODR file by updating the geoReference and rotating the coordinates.
17It reads the input XODR file, extracts the original latitude and longitude from the geoReference tag,
18applies a transformation to the coordinates, and writes the modified data to a new XODR file.
19The script can be run from the command line with the following arguments:
20- input_file: The path to the input XODR file.
21- output_file: The path to the output XODR file.
22Dependency:
23- pip install pyproj argparse lxml
24Usage:
25 python3 xodr_transform.py <input_file> <output_file>
26"""
27
28from lxml import etree
29from pyproj import CRS, Transformer
30import math
31import re
32
33
34def extract_lat_lon_from_georeference(geo_text):
35 """Extracts lat_0 and lon_0 from the geoReference WKT string."""
36 lat_match = re.search(r"\+lat_0=([-+]?[0-9]*\.?[0-9]+)", geo_text)
37 lon_match = re.search(r"\+lon_0=([-+]?[0-9]*\.?[0-9]+)", geo_text)
38 if lat_match and lon_match:
39 lat = float(lat_match.group(1))
40 lon = float(lon_match.group(1))
41 return lat, lon
42 else:
43 raise ValueError("geoReference does not contain +lat_0 and +lon_0")
44
45
46def update_georeference_text(old_text, new_lat, new_lon):
47 """Updates the +lat_0 and +lon_0 values in a proj4 WKT string."""
48 new_text = re.sub(r"\+lat_0=([-+]?[0-9]*\.?[0-9]+)", f"+lat_0={new_lat}", old_text)
49 new_text = re.sub(r"\+lon_0=([-+]?[0-9]*\.?[0-9]+)", f"+lon_0={new_lon}", new_text)
50 return new_text
51
52
53def rotate(x, y, angle_deg):
54 angle_rad = math.radians(angle_deg)
55 x_rot = x * math.cos(angle_rad) - y * math.sin(angle_rad)
56 y_rot = x * math.sin(angle_rad) + y * math.cos(angle_rad)
57 return x_rot, y_rot
58
59
60def transform_latlon(lat, lon, transformer_from_orig_to_utm, transformer_from_utm_to_new, angle_deg):
61 x, y = transformer_from_orig_to_utm.transform(lon, lat)
62 x_rot, y_rot = rotate(x, y, angle_deg)
63 lon_new, lat_new = transformer_from_utm_to_new.transform(x_rot, y_rot, direction='INVERSE')
64 return lat_new, lon_new
65
66
67def transform_hdg(hdg, angle_deg):
68 return hdg + math.radians(angle_deg)
69
70
71def transform_xodr_file(input_path, output_path):
72 tree = etree.parse(input_path)
73 root = tree.getroot()
74
75
77 new_lat = None
78 new_lon = None
79 angle_deg = 0.0
80
81
82 # Get original lat/lon from geoReference tag
83 geo_ref_tag = root.find("header/geoReference")
84 if geo_ref_tag is None or not geo_ref_tag.text:
85 raise ValueError("No geoReference tag found.")
86
87 orig_lat, orig_lon = extract_lat_lon_from_georeference(geo_ref_tag.text)
88
89 if new_lat is None or new_lon is None:
90 new_lat = orig_lat
91 new_lon = orig_lon
92
93 # Update geoReference tag for new output
94 geo_ref_tag.text = etree.CDATA(update_georeference_text(geo_ref_tag.text, new_lat, new_lon))
95
96 # Define projections
97 crs_orig = CRS.from_proj4(f"+proj=tmerc +lat_0={orig_lat} +lon_0={orig_lon} +ellps=WGS84 +datum=WGS84 +units=m +no_defs")
98 crs_new = CRS.from_proj4(f"+proj=tmerc +lat_0={new_lat} +lon_0={new_lon} +ellps=WGS84 +datum=WGS84 +units=m +no_defs")
99
100 transformer_to_utm = Transformer.from_crs("EPSG:4326", crs_orig, always_xy=True)
101 transformer_from_utm = Transformer.from_crs("EPSG:4326", crs_new, always_xy=True)
102
103 for elem in root.iter():
104 lat = elem.attrib.get("lat")
105 lon = elem.attrib.get("lon")
106 x = elem.attrib.get("x")
107 y = elem.attrib.get("y")
108 hdg = elem.attrib.get("hdg")
109
110 # Transform GPS-based coordinates
111 if lat and lon:
112 lat_f = float(lat)
113 lon_f = float(lon)
114 lat_new_val, lon_new_val = transform_latlon(lat_f, lon_f, transformer_to_utm, transformer_from_utm, angle_deg)
115 elem.set("lat", f"{lat_new_val:.8f}")
116 elem.set("lon", f"{lon_new_val:.8f}")
117
118 # Transform local x, y coordinates
119 if x and y:
120 x_f = float(x)
121 y_f = float(y)
122 x_rot, y_rot = rotate(x_f, y_f, angle_deg)
123 elem.set("x", f"{x_rot:.8f}")
124 elem.set("y", f"{y_rot:.8f}")
125
126 # Adjust heading
127 if hdg:
128 hdg_f = float(hdg)
129 hdg_new = transform_hdg(hdg_f, angle_deg)
130 elem.set("hdg", f"{hdg_new:.8f}")
131
132 # Write modified XML
133 tree.write(output_path, encoding="UTF-8", xml_declaration=True)
134
135
136if __name__ == "__main__":
137 import argparse
138
139 parser = argparse.ArgumentParser(description="Transform XODR GPS-coordinates with a new geoReference and rotation.")
140 parser.add_argument("input_file", help="Path to input .xodr file")
141 parser.add_argument("output_file", help="Path to output .xodr file")
142
143 args = parser.parse_args()
144
145 transform_xodr_file(args.input_file, args.output_file)
def transform_hdg(hdg, angle_deg)
def update_georeference_text(old_text, new_lat, new_lon)
def transform_xodr_file(input_path, output_path)
def extract_lat_lon_from_georeference(geo_text)
def transform_latlon(lat, lon, transformer_from_orig_to_utm, transformer_from_utm_to_new, angle_deg)
def rotate(x, y, angle_deg)