Compare commits
No commits in common. "674f406fbabc7165106ecb1c24405d8250fdb51a" and "3c138f3820bedb0f40c9a393fa0721159bc444df" have entirely different histories.
674f406fba
...
3c138f3820
2 changed files with 4 additions and 122 deletions
|
|
@ -6,18 +6,6 @@ import os
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from xml.sax.saxutils import escape as _xml_escape
|
|
||||||
|
|
||||||
def xml_escape(text):
|
|
||||||
"""
|
|
||||||
Escape text for use inside XML element text nodes.
|
|
||||||
|
|
||||||
Important: At least '&', '<', '>' must be escaped to keep GPX well-formed.
|
|
||||||
We also escape quotes defensively.
|
|
||||||
"""
|
|
||||||
if text is None:
|
|
||||||
return ''
|
|
||||||
return _xml_escape(str(text), entities={'"': '"', "'": '''})
|
|
||||||
|
|
||||||
def create_gpx(places, folder_name, output_file='places.gpx'):
|
def create_gpx(places, folder_name, output_file='places.gpx'):
|
||||||
"""Create a GPX file from the collected places."""
|
"""Create a GPX file from the collected places."""
|
||||||
|
|
@ -41,12 +29,12 @@ def create_gpx(places, folder_name, output_file='places.gpx'):
|
||||||
waypoints.append(waypoint_template.format(
|
waypoints.append(waypoint_template.format(
|
||||||
lat=place['coordinates']['lat'],
|
lat=place['coordinates']['lat'],
|
||||||
lon=place['coordinates']['lng'],
|
lon=place['coordinates']['lng'],
|
||||||
name=xml_escape(place['name']),
|
name=place['name'],
|
||||||
desc=xml_escape(place['description'] or '')
|
desc=place['description'] or ''
|
||||||
))
|
))
|
||||||
|
|
||||||
gpx_content = gpx_template.format(
|
gpx_content = gpx_template.format(
|
||||||
folder_name=xml_escape(folder_name),
|
folder_name=folder_name,
|
||||||
timestamp=datetime.utcnow().isoformat(),
|
timestamp=datetime.utcnow().isoformat(),
|
||||||
waypoints='\n'.join(waypoints)
|
waypoints='\n'.join(waypoints)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,106 +0,0 @@
|
||||||
#!/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 <wpt> 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()
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue