diff --git a/nextcloud_favorites_to_osmand_prefixed_groups.py b/nextcloud_favorites_to_osmand_prefixed_groups.py
new file mode 100755
index 0000000..7c084ad
--- /dev/null
+++ b/nextcloud_favorites_to_osmand_prefixed_groups.py
@@ -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('\n')
+ f.write('\n')
+
+ f.write(' \n')
+ f.write(f' \n')
+ f.write(' \n')
+
+ for w in wpts:
+ f.write(f' \n')
+ f.write(f' {escape(w["name"])}\n')
+
+ # OsmAnd favorites group name (most important)
+ f.write(f' {escape(w["group"])}\n')
+
+ if w["desc"] is not None:
+ f.write(f' {escape(w["desc"])}\n')
+ if w["time"] is not None:
+ f.write(f' \n')
+
+ # Extra hints (safe if ignored)
+ f.write(' \n')
+ f.write(f' {escape(w["group"])}\n')
+ f.write(f' \n')
+ f.write(' \n')
+
+ f.write(' \n')
+
+ f.write('\n')
+
+ print(f"Wrote {len(wpts)} favorites to {out_path} with groups like '{prefix}/'")
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file