diff --git a/gpx_to_osmand_favorites.py b/gpx_to_osmand_favorites.py new file mode 100755 index 0000000..51a7a1f --- /dev/null +++ b/gpx_to_osmand_favorites.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +import os +import sys +import xml.etree.ElementTree as ET +from datetime import datetime, timezone + +GPX_NS = "http://www.topografix.com/GPX/1/1" +OSMAND_NS = "https://osmand.net" + +ET.register_namespace("", GPX_NS) +ET.register_namespace("osmand", OSMAND_NS) + +def iso_now_z(): + return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") + +def localname(tag): + return tag.split("}", 1)[-1] if "}" in tag else tag + +def find_child_by_localname(parent, wanted): + for ch in list(parent): + if localname(ch.tag) == wanted: + return ch + return None + +def get_metadata_name_anyns(root): + md = find_child_by_localname(root, "metadata") + if md is None: + return None + nm = find_child_by_localname(md, "name") + if nm is None: + return None + txt = (nm.text or "").strip() + return txt or None + +def guess_group(in_path, root, prefix): + base = get_metadata_name_anyns(root) + if not base: + base = os.path.splitext(os.path.basename(in_path))[0] + if prefix: + return f"{prefix.rstrip('/')}/{base}" + return base + +def main(): + if len(sys.argv) not in (3, 4): + print( + "Usage: gpx_to_osmand_favorites_autogroup_fixed.py input.gpx output.gpx [prefix]\n" + "Example: gpx_to_osmand_favorites_autogroup_fixed.py 4x4.gpx 4x4_osmand.gpx Norwegen", + file=sys.stderr + ) + sys.exit(2) + + in_path = sys.argv[1] + out_path = sys.argv[2] + prefix = sys.argv[3] if len(sys.argv) == 4 else "" + + tree = ET.parse(in_path) + root = tree.getroot() + + group = guess_group(in_path, root, prefix) + + in_wpts = [el for el in root.iter() if localname(el.tag) == "wpt"] + if not in_wpts: + raise SystemExit(f"No elements found in {in_path}") + + out_root = ET.Element(f"{{{GPX_NS}}}gpx", { + "version": "1.1", + "creator": "gpx_to_osmand_favorites_autogroup_fixed.py", + }) + + md = ET.SubElement(out_root, f"{{{GPX_NS}}}metadata") + ET.SubElement(md, f"{{{GPX_NS}}}name").text = f"OsmAnd favorites: {group}" + ET.SubElement(md, f"{{{GPX_NS}}}time").text = iso_now_z() + + written = 0 + for w in in_wpts: + lat = w.attrib.get("lat") + lon = w.attrib.get("lon") + if lat is None or lon is None: + continue + + out_wpt = ET.SubElement(out_root, f"{{{GPX_NS}}}wpt", {"lat": str(lat), "lon": str(lon)}) + + # Copy common fields if present + for ch in list(w): + ln = localname(ch.tag) + if ln in ("name", "cmt", "desc", "ele", "time") and ch.text not in (None, ""): + ET.SubElement(out_wpt, f"{{{GPX_NS}}}{ln}").text = ch.text + + # Ensure we have a name + if find_child_by_localname(out_wpt, "name") is None: + ET.SubElement(out_wpt, f"{{{GPX_NS}}}name").text = f"Favorite {written+1}" + + # OsmAnd favorites group (key field) + ET.SubElement(out_wpt, f"{{{GPX_NS}}}type").text = group + + # Optional OsmAnd hint (safe if ignored) + ext = ET.SubElement(out_wpt, f"{{{GPX_NS}}}extensions") + ET.SubElement(ext, f"{{{OSMAND_NS}}}category").text = group + + written += 1 + + ET.ElementTree(out_root).write(out_path, encoding="UTF-8", xml_declaration=True) + print(f"Wrote {written} waypoint(s) to {out_path} with OsmAnd favorites group '{group}'") + +if __name__ == "__main__": + main() \ No newline at end of file