Add nextcloud_favorites_to_osmand_prefixed_groups.py
This commit is contained in:
parent
674f406fba
commit
8b2cfa29ed
1 changed files with 127 additions and 0 deletions
127
nextcloud_favorites_to_osmand_prefixed_groups.py
Executable file
127
nextcloud_favorites_to_osmand_prefixed_groups.py
Executable file
|
|
@ -0,0 +1,127 @@
|
|||
#!/usr/bin/env python3
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from xml.sax.saxutils import escape
|
||||
|
||||
OSMAND_NS = "https://osmand.net"
|
||||
|
||||
def epoch_to_iso_z(value):
|
||||
if value is None:
|
||||
return None
|
||||
try:
|
||||
ts = int(value)
|
||||
return datetime.fromtimestamp(ts, tz=timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def norm(s):
|
||||
if s is None:
|
||||
return None
|
||||
s = str(s).strip()
|
||||
return s if s else None
|
||||
|
||||
def main():
|
||||
if len(sys.argv) not in (3, 4):
|
||||
print(
|
||||
"Usage: nextcloud_favorites_to_osmand_prefixed_groups.py .favorites.json out.gpx [prefix]\n"
|
||||
"Example: nextcloud_favorites_to_osmand_prefixed_groups.py .favorites.json favourites.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 "Norwegen"
|
||||
|
||||
with open(in_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not isinstance(data, dict) or data.get("type") != "FeatureCollection":
|
||||
raise SystemExit("Input is not a GeoJSON FeatureCollection")
|
||||
|
||||
features = data.get("features", [])
|
||||
if not isinstance(features, list):
|
||||
raise SystemExit("GeoJSON 'features' is not a list")
|
||||
|
||||
wpts = []
|
||||
for idx, feat in enumerate(features):
|
||||
if not isinstance(feat, dict) or feat.get("type") != "Feature":
|
||||
continue
|
||||
|
||||
geom = feat.get("geometry") or {}
|
||||
if geom.get("type") != "Point":
|
||||
continue
|
||||
|
||||
coords = geom.get("coordinates")
|
||||
if not (isinstance(coords, list) and len(coords) >= 2):
|
||||
continue
|
||||
|
||||
# GeoJSON order: [lon, lat]
|
||||
try:
|
||||
lon = float(coords[0])
|
||||
lat = float(coords[1])
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
props = feat.get("properties") or {}
|
||||
title = norm(props.get("Title")) or f"Favorite {idx+1}"
|
||||
comment = norm(props.get("Comment"))
|
||||
category = norm(props.get("Category")) or "Uncategorized"
|
||||
|
||||
group = f"{prefix}/{category}"
|
||||
|
||||
# Prefer Updated, else Published (epoch seconds)
|
||||
time_iso = epoch_to_iso_z(props.get("Updated", props.get("Published")))
|
||||
|
||||
wpts.append({
|
||||
"lat": lat,
|
||||
"lon": lon,
|
||||
"name": title,
|
||||
"desc": comment,
|
||||
"time": time_iso,
|
||||
"group": group,
|
||||
"category": category
|
||||
})
|
||||
|
||||
if not wpts:
|
||||
raise SystemExit("No Point features found to convert")
|
||||
|
||||
with open(out_path, "w", encoding="utf-8") as f:
|
||||
f.write('<?xml version="1.0" encoding="UTF-8"?>\n')
|
||||
f.write('<gpx version="1.1" creator="nextcloud_favorites_to_osmand_prefixed_groups.py"\n')
|
||||
f.write(' xmlns="http://www.topografix.com/GPX/1/1"\n')
|
||||
f.write(' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n')
|
||||
f.write(f' xmlns:osmand="{OSMAND_NS}"\n')
|
||||
f.write(' xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">\n')
|
||||
|
||||
f.write(' <metadata>\n')
|
||||
f.write(f' <time>{datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00","Z")}</time>\n')
|
||||
f.write(' </metadata>\n')
|
||||
|
||||
for w in wpts:
|
||||
f.write(f' <wpt lat="{w["lat"]:.8f}" lon="{w["lon"]:.8f}">\n')
|
||||
f.write(f' <name>{escape(w["name"])}</name>\n')
|
||||
|
||||
# OsmAnd favorites group name (most important)
|
||||
f.write(f' <type>{escape(w["group"])}</type>\n')
|
||||
|
||||
if w["desc"] is not None:
|
||||
f.write(f' <desc>{escape(w["desc"])}</desc>\n')
|
||||
if w["time"] is not None:
|
||||
f.write(f' <time>{escape(w["time"])}</time>\n')
|
||||
|
||||
# Extra hints (safe if ignored)
|
||||
f.write(' <extensions>\n')
|
||||
f.write(f' <osmand:category>{escape(w["group"])}</osmand:category>\n')
|
||||
f.write(f' <osmand:tag k="nextcloud_category" v="{escape(w["category"])}"/>\n')
|
||||
f.write(' </extensions>\n')
|
||||
|
||||
f.write(' </wpt>\n')
|
||||
|
||||
f.write('</gpx>\n')
|
||||
|
||||
print(f"Wrote {len(wpts)} favorites to {out_path} with groups like '{prefix}/<Category>'")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue