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. 
   23- pip install pyproj argparse lxml 
   25    python3 xodr_transform.py <input_file> <output_file> 
   29from pyproj import CRS, Transformer 
   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))
 
   43        raise ValueError(
"geoReference does not contain +lat_0 and +lon_0")
 
   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)
 
   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)
 
   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
 
   68    return hdg + math.radians(angle_deg)
 
   72    tree = etree.parse(input_path)
 
   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.")
 
   89    if new_lat 
is None or new_lon 
is None:
 
   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")
 
  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)
 
  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")
 
  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}")
 
  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}")
 
  130            elem.set(
"hdg", f
"{hdg_new:.8f}")
 
  133    tree.write(output_path, encoding=
"UTF-8", xml_declaration=
True)
 
  136if __name__ == 
"__main__":
 
  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")
 
  143    args = parser.parse_args()