gpx-update.py (Source)

#!/usr/bin/python
"""
Export KDE Marble KML bookmarks to OsmAnd GPX favourites.
Requires: python-lxml, gpsbabel

Usage: $0 <EXPORT_FOLDER> ... < bookmarks.kml > favourites.gpx
       $0 -i favourites.gpx

EXPORT_FOLDER are the bookmark folders (in Marble) to export. The argument can
also be in the following extended syntax:

  EXPORT_FOLDER ::= (folder | gpx_file) "|" colour

gpx_file    is the path to an actual file to splice into the favourites as-is.
colour      is what colour to display those places in, in OsmAnd.

Use the -i option to install the output file into Android.

TODO: write this up in the main droid-hacks doc. Roughly:
- add this script to a crontab
- expose $(dirname favourites.gpx) via a stealth hidden service
- periodically download it to your phone
- su -c 'ln -sf ../Downloads/favourites.gpx /data/media/0/osmand/'

"""

from __future__ import print_function

from lxml import etree

import copy
import os.path
import subprocess
import sys

path = os.path.expanduser

NS = {
    "k":"http://www.opengis.net/kml/2.2",
    "g":"http://www.topografix.com/GPX/1/0",
}

def export(*args):
    kml_in = etree.parse(sys.stdin)
    fav = None
    fav_time = None
    fav_bounds = None

    for export in args:
        parts = export.split("|")
        if len(parts) == 2:
            folder, colour = parts
        elif len(parts) == 1:
            folder, colour = parts[0], None
        else:
            raise ValueError(export)

        if folder.endswith(".gpx"):
            fp = open(path(folder))
            folder = os.path.basename(folder)
        else:
            p = subprocess.Popen(["/usr/bin/gpsbabel", "-i", "kml", "-o", "gpx", "-f", "-", "-F", "-"],
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE)

            # filter only this folder
            r = copy.deepcopy(kml_in)
            for e in r.findall("//k:Document/k:Folder", namespaces=NS):
                if e.getchildren()[0].text != folder:
                    r.getroot().getchildren()[0].remove(e)
            r.write(p.stdin, encoding="utf-8", xml_declaration=True)
            p.stdin.close()
            fp = p.stdout

        # write the folder name into the GPX output of gpsbabel, otherwise it's lost
        g = etree.parse(fp)
        for e in g.findall("//g:wpt", namespaces=NS):
            t = e.makeelement('type')
            t.tail = e.text                                                     # i'm OCD, maintain indentation
            t.text = folder
            e.insert(0, t)
            i2, i1 = e.getchildren()[-2:]
            if colour:
                x = e.makeelement("extensions")
                c = e.makeelement("color")
                c.text = colour
                x.append(c)
                e.append(x)
                x.tail = i1.tail                                                # i'm OCD, maintain indentation
                i1.tail = i2.tail

        # merge the output into "fav"
        if fav is None:
            fav = g
            fav_time = fav.findall("//g:time", namespaces=NS)[0]
            fav_bounds = fav.findall("//g:bounds", namespaces=NS)[0]
        else:
            g_time = g.findall("//g:time", namespaces=NS)
            if g_time:
                g_time = g_time[0]
                if g_time.text > fav_time.text:
                    fav_time.text = g_time.text

            g_bounds = g.findall("//g:bounds", namespaces=NS)
            if g_bounds:
                g_bounds = g_bounds[0]
                if float(g_bounds.get("minlon")) < float(fav_bounds.get("minlon")):
                    fav_bounds.set("minlon", g_bounds.get("minlon"))
                if float(g_bounds.get("minlat")) < float(fav_bounds.get("minlat")):
                    fav_bounds.set("minlat", g_bounds.get("minlat"))
                if float(g_bounds.get("maxlon")) > float(fav_bounds.get("maxlon")):
                    fav_bounds.set("maxlon", g_bounds.get("maxlon"))
                if float(g_bounds.get("maxlat")) > float(fav_bounds.get("maxlat")):
                    fav_bounds.set("maxlat", g_bounds.get("maxlat"))

            for e in g.findall("//g:wpt", namespaces=NS):
                fav.getroot().getchildren()[-1].tail = g.getroot().text         # i'm OCD, maintain indentation
                fav.getroot().append(e)

    fav.write(sys.stdout, encoding="utf-8", xml_declaration=True)
    return 0

def install(f):
    subprocess.Popen(["sh"], stdin=subprocess.PIPE).communicate(input="""
pkg=net.osmand.plus
adb shell am force-stop $pkg
adb shell su -c "find /data/data/$pkg/ /sdcard/Android/data/$pkg/ -name 'favourites_*.gpx' -delete"
adb push "%s" /sdcard/Android/data/$pkg/files/favourites.gpx
""" % f)
    return 0

if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] == "-i":
        sys.exit(install(sys.argv[2]))
    else:
        sys.exit(export(*sys.argv[1:]))