I do a lot of trip planning on google maps, creating a custom my map for each trip with tracks/lines and waypoints. I've always exported the google map as a KML, done a conversion to GPX using one of the many utilities available for that purpose and finally take the GPX file and drop it into the appropriate OSMAnd tracks directory on my android phone.
The problem I've always had with the KML to GP conversion is all the track colors and the icon symbols and colors are lost in translation, everything ends up a default color and symbol in OSMAnd. I finally sat down and wrote a quick python utility to do the KML to GPX conversion directly using a table to translate a subset of KML icons to approximate OSMAnd equivalents. I'm sharing the code in case it's useful to anyone else. -- You received this message because you are subscribed to the Google Groups "OsmAnd" group. To unsubscribe from this group and stop receiving emails from it, send an email to osmand+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/osmand/9183838c-128b-40d0-b1e9-2c132b0b4b46n%40googlegroups.com.
# 8/11/2020 Tom Musolf - based on some code I found on the internet from Tim Abell # # Quick hack (no error checking & I'm not a python guy) to convert Google My Maps KML export files to # GPX files for OSMAnd mapping program on Android phones. # # There are lots of KML to GPX converters out there, like GPSVisualizer.com, but they all lose the KML icon and color info # during the translation. In addition, OSMAnd has it's own GPX extensions for it's custom icons. # What this program does is convert KML waypoints and tracks/lines into their OSMAnd GPX equivalents. # # KML Layers # Layers are ignored, but all tracks and points found in the KML file are translated into their OSMAnd GPX file equivalent. # # Tracks # Track name and description are translated. # Tracks will carry the color specified in the KML file into OSMAnd. # Line width is not translated because it is not supported in OSMAnd. # A default transparency value for the track is specified using the TrackTransparency variable. # # Points # Name and description are translated. # The KML icon is translated to an OSMAnd equivalent using the iconDictionary translation table. # This table contains # OSMAnd equivalent icon # A color for the icon or you can specify using the icon color from the KML file # Which of the 3 icon shapes that OSMAnd supports. # If the KML icon is not found in the table then a default/unknown icon is used. # Other # All other KML structures/tags are ignored # # Once you have your GPX file copy it to this location on your android phone: .../Android/data/net.osmand.plus/files/tracks # Note: The tracks folder supports nested folders so you can create folders such as: .../tracks/hikes and .../tracks/BikeRoutes # These folders and their GPX files will then show up in OSMAnd MyPlaces>Tracks # # I use OSMAnd configure map>GPX files>Appearance>Bold for the tracks I display with my GPX files. With track transparency set to # 55 (via this program) it lets me read street names and still see the track. # # Make sure you have python installed on your PC # # Cmd format: py KMLToOSMAndGPX.py "input file" > "output file" # # If no output file is specified output goes to standard out. # # Again, NO ERROR CHECKING is done for presence of input file, valid KML file structure, etc. #=========================================================================== # #!/usr/bin/python # https://timwise.co.uk/2014/02/03/converting-kml-to-gpx-with-python/ # https://gist.github.com/timabell/8791116 import argparse import xml.sax parser = argparse.ArgumentParser(description='Convert annoying google android my tracks kml data to sensible gpx files') parser.add_argument('input_file') args = parser.parse_args() input = args.input_file #iconDictionary describes the mapping between a KML icon number and an OSMAnd icon name. #It also contains a default OSMAnd color and shape to use for each OSMAnd icon type. # #iconDictionary format: # "KML icon number":["OSMAnd Icon name","HTML hex color code or flag to use KMLCOLOR","OSMAnd shape"] # #Color code is a standard HTML hex color code. This is what OSMAnd uses #As of 8/2020 OSMAnd icons do not support transparent colors. #As of 8/2020 OSMAnd supports 3 icon shapes: circle, octagon, square # #Adding additional KML icons to the dictionary. # # For each icon you want to translate you need to add a new entry/line into the iconDictionary table. # To determine what the KML and OSMAnd icons are you want go through the following steps: # # KML icon number # 1) Create a google my maps test file with the icons you want to use. # 2) Export this map as a KML file. # 3) Open up the file in a text editor and look for your points. You can ignore all the <style> & <StyleMap> tags at the # beginning of the KML file. The points/waypoints/Placemarks will look like this: # # <Placemark> # <name>Mileage Marker dot</name> # <styleUrl>#icon-1739-0288D1-nodesc</styleUrl> # <Point> # <coordinates>-120.8427259,38.8170119,0</coordinates> # </Point> # </Placemark> #The <styleUrl> tag has the icon number. In the preceeding example it's "1739". # # OSMAnd Icon name # 1) Create some favorites using the icons you want. # 2) Goto .../Android/data/net.osmand.plus/files/favourites.gpx !!! yes, it's spelled the british way. # 3) Open the favorites file in a text editor and look for the waypoints. I # in the following example the icon name is: "special_trekking" # # <wpt lat="39.2906659" lon="-121.4965106"> # <name>hiker, pale yellow</name> # <extensions> # <color>#eeee10</color> # <icon>special_trekking</icon> # <background>circle</background> # </extensions> # </wpt> # # Put the string "KMLCOLOR", without the double quotes, in for a color value if you want to use the color specified in the KML file # for a particular icon. KMLCOLOR = "KMLCOLOR" iconDictionary ={ "unknown":["special_symbol_question_mark","e044bb","octagon"], #unknown KML icon code - this entry will be used if the KML icon is not found the iconDictionary. "1765":["building_type_pyramid","785735","circle"], #campsite "1525":["water_transport","a71de1","octagon"], #river access "1739":["special_symbol_plus","1010a0","circle"], #Mileage marker plus-KML dot "1596":["special_trekking","9e963a","cirlce"], #hiking trailhead "1723":["tourism_viewpoint","d90000","octagon"], #rapid "1602":["tourism_hotel","10c0f0","circle"], #hotel, lodge "1528":["historic_castle","10c0f0","circle"], #bridge "1577":["restaurants","10c0f0","circle"], #retaurant, diner, dining "1650":["tourism_picnic_site","eecc22","circle"], #picnic site "1644":["amenity_parking","10c0f0","circle"], #parking area "1578":["shop_supermarket","10c0f0","circle"], #grocery store, supermarket "1504":["air_transport","10c0f0","circle"], #airport, airstrip "1581":["fuel","1010a0","circle"], #gas station "1733":["amenity_toilets","10c0f0","circle"], #toilet, restroom "1624":["amenity_hospital","d00d0d","circle"], #hospital, doctor, emergency room "1608":["tourism_information","1010a0","circle"], #tourism information "1535":["special_photo_camera","eecc22","circle"], #POI #1, camera "1574":["special_flag_stroke","eecc22","circle"], #POI #2, flag "1899":["special_marker","eecc22","circle"], #POI #3, pin "1502":["special_star",KMLCOLOR,"circle"], #POI #4, star "1501":["special_bookmark","eecc22","circle"], #POI #5, ribbon/diamond "1603":["special_house","eecc22","circle"], #house "1879":["amenity_biergarten","10c0f0","circle"], #brewery, brew pub "1541":["special_symbol_exclamation_mark","ff0000","octagon"], #danger #1 exclamation "1564":["special_symbol_exclamation_mark","ff0000","octagon"], #danger #2 explosion "1710":["special_arrow_up_and_down","10c0f0","circle"], #river gauge, up/down arrow or thermometer "1655":["amenity_police","1010a0","circle"], #ranger/police station #1 "1657":["amenity_police","1010a0","circle"] #ranger/police station #2 } # TrackTransparency: This value specifies the amount of transparency that a track will have when displayed in OSMAnd. # this is a 2 digit hex value from 00 (fully transparent) to FF (fully opaque) # This value is concatinated to the front of the track color value. # For example: Bright pink is: F700FF if you want it very transparent, say only 25%, you would set TrackTransparency to "40". # A 2 digit hex value goes from 0x00=0 to 0xFF=255 so 25% of 255 is 64 decimal which is 40 hex. # So the <color> value for a 25% bright pink line would end up being "40F700FF" in the GPX file. # # As of 8/2020 transparency in the color value is only supported for OSMAnd tracks, not icons TrackTransparency = "55" #There are certain characters that can't be in HTML/XML name or description strings. #This function converts them to the HTML escaped version html_escape_table = { "&": "&", '"': """, "'": "'", ">": ">", "<": "<", } def html_escape(text): """Produce entities within text.""" return "".join(html_escape_table.get(c,c) for c in text) class KmlParser(xml.sax.ContentHandler): def __init__(self): self.in_tag=0 self.chars="" self.inPlacemark=0 self.inDocument=0 self.inLine=0 self.name ="" self.description="" self.style="" self.styleIcon="" self.styleColor="" def startElement(self, name, attrs): if self.inDocument==0: #we're entering document section looking for <name> element only if name == "Document": #print("Start Document") self.inDocument = 1 elif self.inPlacemark==0: # we're not in a placemark, ignore other elements till we get a placemark if name =="Placemark": self.inPlacemark = 1 else: # we're in a placemark if name == "LineString": self.chars="" self.inLine=1 if name == "name": self.in_tag =1 self.chars="" elif name == "styleUrl": self.in_tag=1 self.chars="" elif name =="coordinates": self.in_tag =1 self.chars="" elif name =="description": self.in_tag = 1 self.chars="" else: # we're in a docment looking only for <name> element if name == "name": self.in_tag =1 self.chars="" def characters(self, char): if self.in_tag: self.chars += char def endElement(self, name): if self.inDocument==1: #we're in a document and looking only for the </name> element if name == "name": self.inDocument = 0 print("<metadata>") print("\t<name>"+self.chars+"</name>") print("</metadata>") self.chars="" self.in_tag = 0 elif self.inPlacemark == 1: if name == "Placemark": if self.inLine==1: #we're doing a line/track placemark print("<trk>") print("\t<name>"+self.name+"</name>") if self.description != "": print("\t<desc>"+self.description+"</desc>") print("\t<trkseg>") i=0 while i < len(self.coordinates): print("\t\t<trkpt lat=\""+self.coordinates[i+1]+"\" lon=\""+self.coordinates[i]+"\"/>") i=i+3 print("\t</trkseg>") print("\t<extensions>") style = self.style.split("-") self.styleWidth=float(style[2])/1000 self.styleColor=style[1] print("\t\t<color>#"+TrackTransparency+self.styleColor+"</color>") # 8/2020 it appears that OSMAnd ignores width and opacity tags print("\t\t<width>"+str(self.styleWidth)+"</width>") print("\t\t<opacity>1</opacity>") print("\t</extensions>") print("</trk>") else: #it's waypoint print("<wpt lat=\""+self.coordinates[1]+"\" lon=\""+self.coordinates[0]+"\">") print("\t<name>"+self.name+"</name>") if self.description != "": print("\t<desc>"+self.description+"</desc>") print("\t<extensions>") style = self.style.split("-") self.styleIcon=style[1] self.styleColor=style[2] #it's a KML icon that we haven't put in the dictionary so use a default one. if not self.styleIcon in iconDictionary: self.styleIcon="unknown" if iconDictionary[self.styleIcon][1] == KMLCOLOR: #use the icon color from the KML file print("\t\t<color>#"+self.styleColor+"</color>") else: #use the icon color from the dictionary table print("\t\t<color>#"+iconDictionary[self.styleIcon][1]+"</color>") print("\t\t<icon>"+iconDictionary[self.styleIcon][0]+"</icon>") print("\t\t<background>"+iconDictionary[self.styleIcon][2]+"</background>") print("\t</extensions>") print("</wpt>") self.name ="" self.description="" self.styleIcon="" self.styleColor="" self.style="" self.inLine=0 self.inPlacemark = 0 #closing out a placemark elif name == "name": #ampersands and other special characters cause a problem in the XML/GPX file so replace them #with an XML escaped version. self.name = html_escape(self.chars.strip()) self.chars="" self.in_tag = 0 elif name == "styleUrl": #style string is a different format for a track/line vs waypoint in KML self.style=self.chars self.chars="" self.in_tag = 0 elif name == "coordinates": #end up with a list of coordinates lon, lat, alt self.coordinates=self.chars.strip().replace(" ","").replace("\n",",").split(",") self.chars="" self.in_tag = 0 elif name == "description": # Make sure to escape special characters and convert <br> in KML descriptions into carriage return/line feed # characters that OSMAnd likes. self.description = html_escape(self.chars.replace("<br>","\r\n")) self.chars="" self.in_tag = 0 #GPX header print ("""<?xml version='1.0' encoding='UTF-8' standalone='yes' ?> <gpx version="1.1" creator="OsmAnd+ 3.7.4" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"> """) parser = xml.sax.make_parser() parser.setContentHandler(KmlParser()) parser.parse(open(input,"r")) #GPX footer print ("</gpx>") #***end***