Wednesday, October 14, 2009

Encoded Polylines for Static Google Maps


The Google Static Map has a size limitation of 2400 characters which is because of IE's url size limit. Since the static map is created by sending the polyline endpoints through the URL, it becomes easy to reach that limit with a curvey path on the map...for example...gpx paths for hiking paths. Using the ecoded polyline feature, which calculates the path segments by their relative changes, and converting to a more efficient binary representation, the data set can be greatly compressed in the URL.


The Urban Sensing Wiki at UCLA about "How-to use Google Maps polyline encoding to compact data size" and gives the following Python functions to use.




# Encode a signed number into the google maps polyline encode format.
# from: http://wiki.urban.cens.ucla.edu
def encodeSignedNumber(num):
sgn_num = num << sgn_num =" ~(sgn_num)" encodestring = "">= 0x20:
encodeString += chr((0x20 | (num & 0x1f)) + 63)
num >>= 5
encodeString += chr(num + 63)
return encodeString

# Create the encoded polyline and level strings.
# @points: [{'lat':100, 'lng':50}, {'lat':102, 'lng':49.8}]
# This function takes a list of dictionaries (each dictionary contains a
# single 'lat','lng' pair) and returns a dictionary:
# {'encoded_points': string, 'encoded_levels': string}.
# from: http://wiki.urban.cens.ucla.edu
def encodePolyPts(points):
i = 0
plat = 0
plng = 0
encoded_points = ""
encoded_levels = ""
for i in range(0, len(points)):
point = points[i]
lat = point['lat']
lng = point['lng']
level = 3 - i%4
late5 = int(math.floor(lat * 1e5))
lnge5 = int(math.floor(lng * 1e5))
dlat = late5 - plat
dlng = lnge5 - plng
plat = late5
plng = lnge5

encoded_points += encodeSignedNumber(dlat) + encodeSignedNumber(dlng)
encoded_levels += encodeNumber(level)

return {'encoded_points': encoded_points, 'encoded_levels': encoded_levels}


To use this encoding capability to draw a static Google map, the (points) list needs to be built. The variable is
expected to be a list of dictionaries. The documentation shows us it is expecting the format to be as follows:


@points: [{'lat':100, 'lng':50}, {'lat':102, 'lng':49.8}]


The following function retrieves the datastore item and parses the lat and lng values
to create a single dictionary which is then appended to the list.


Note: in this example the "newStr" variable is a JSON string from the datastore which is loaded into a python object using the
simplejson utility. It can then be iterated to find all the stored lat and lng points.



def mapShow(request, route_key):
"""creates a static google map of the entire route """
"""written by Thea Ganoe, October 2009"""
#http://maps.google.com/maps/api/staticmap?size=400x400&path=weight:3|color:orange|enc:polyline_data
user = users.get_current_user()
route = models.Route.get(route_key)
pathList = []
newStr = route.way
#'{"description":"'+ route.routeName +'", "img":"'+route.img+'", "getPic": "' + route.img +'", "zoom":"'+ zoomstring +'" , "longitude":"'+ route.longitude +'", "latitude":"'+ route.latitude +'", "position": "0", "heading": "" }'
ways_obj = simplejson.loads(newStr)
sizeofWay = len(ways_obj['way'])
for way in ways_obj['way']:
pathDic = { 'lat': float(way['latitude']), 'lng': float( way['longitude']) }
pathList.append(pathDic)
# points: [{'lat':100, 'lng':50}, {'lat':102, 'lng':49.8}]
polyPts = encodePolyPts(pathList)
return respond(request, user, 'staticMap', {'enc':polyPts } )


Since the Django template system is used by this Google App Engine application, the return value will merge the staticMap.html template with the server response. To use the enc value, it is necessary to specify which list is needed, enc.encoded_points or enc.encoded_levels. This template looks like this:


<image src="http://maps.google.com/maps/api/staticmap?size=512x512&path=weight:2|color:red|enc:{{ enc.encoded_points }}&sensor=false&key=myveryownbiglongkeyfromGoogle"/>



To create a static map, the levels data is not needed. If a live Google Map was being displayed, the levels values would be used by it, because the enc list contains dictionaries of . The above function, Showmap() will return the data needed for either type of map. This is because the return value enc actually contains list of points and a list of levels.

'encoded_points': encoded_points, 'encoded_levels': encoded_levels