Getting an image from Google static maps + showing it via TkInter

I found out, more by coincidence, that it's possible to request a google map image from their static maps server. The documentation for the static maps API is here: http://code.google.com/apis/maps/documentation/staticmaps/  (BTW, it's called static because you only get a image back, as opposed to embedding an interactive google map).

Static maps are somewhat limited, e.g. they can only be up to 640x640 pixels in size, but they provide support for locations as lat/long coordinates, zip-codes and search terms, draw markers, lines and polygons and do not need a developers key. You need to dig into the the documentation to find out the specifics but I found it pretty easy do put together a (rather long) URL with query parameters in python,  retrieve the image from google and either save the image to disk or show it via PIL and/or TKinter.



1) To get a hybrid street+satellite map around the Empire State Building  (note the + instead of spaces inside the search term part), you'd have to assemble a request like this:

http://maps.google.com/maps/api/staticmap?center=Empire+State+Building&zoom=18&size=500x500&format=jpeg&maptype=hybrid&sensor=false&


2) For a a terrain map around latitude = 42.950827 (North) and Longitude = -122.108974 (East). Note that the actual extent around the center (the viewport) is a function of the zoom-level and the size of your image, zoom=0 shows all of the globe, zoom=20 is usually the limit for zooming in:

http://maps.google.com/maps/api/staticmap?center=42.950827,-122.108974&zoom=12&size=500x500&format=jpg&maptype=terrain&sensor=false&


3) You can also "plot" a bunch of locations as Markers or connect them into lines or polygons within this viewport. Instead of specifying a center and zoom factor, you could also specify a bunch of locations (again either as lat/long, search term or even as zip-code) and let google figure out what "envelope" is needed to show them all within a map. Here's a couple of marker at such locations:

http://maps.google.com/maps/api/staticmap?size=640x640&format=png&maptype=roadmap&markers=color:blue|label:S|11211|11206|11222&markers=size:tiny|label:B|color:0xFFFF00|40.702147,-74.015794|&markers=size:mid|color:red|label:6|Brooklyn+Bridge,New+York,NY&sensor=false&


Again, this is just a quick overview, the documentation actually walks you nicely through creating such URLs. To start you can just assemble the URL by hand (in a text editor), then copy and paste them into your browser and look at the image but why not use python to do the work for you?

Here's some code to create such a URL programmatically and, in the simplest case, save the image it gets back from google as local file. You can use PIL to make a image directly from google's image data, i.e. without saving it to disk first.

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import urllib
import cStringIO 
import Image


def get_static_google_map(filename_wo_extension, center=None, zoom=None, imgsize="500x500", imgformat="jpeg",
                          maptype="roadmap", markers=None ):  
    """retrieve a map (image) from the static google maps server 
    
     See: http://code.google.com/apis/maps/documentation/staticmaps/
        
        Creates a request string with a URL like this:
        http://maps.google.com/maps/api/staticmap?center=Brooklyn+Bridge,New+York,NY&zoom=14&size=512x512&maptype=roadmap
&markers=color:blue|label:S|40.702147,-74.015794&sensor=false"""
   
    
    # assemble the URL
    request =  "http://maps.google.com/maps/api/staticmap?" # base URL, append query params, separated by &
   
    # if center and zoom  are not given, the map will show all marker locations
    if center != None:
        request += "center=%s&" % center
        #request += "center=%s&" % "40.714728, -73.998672"   # latitude and longitude (up to 6-digits)
        #request += "center=%s&" % "50011" # could also be a zipcode,
        #request += "center=%s&" % "Brooklyn+Bridge,New+York,NY"  # or a search term 
    if center != None:
        request += "zoom=%i&" % zoom  # zoom 0 (all of the world scale ) to 22 (single buildings scale)


    request += "size=%ix%i&" % (imgsize)  # tuple of ints, up to 640 by 640
    request += "format=%s&" % imgformat
    request += "maptype=%s&" % maptype  # roadmap, satellite, hybrid, terrain


    # add markers (location and style)
    if markers != None:
        for marker in markers:
                request += "%s&" % marker


    #request += "mobile=false&"  # optional: mobile=true will assume the image is shown on a small screen (mobile device)
    request += "sensor=false&"   # must be given, deals with getting loction from mobile device 
    print request
    
    urllib.urlretrieve(request, filename_wo_extension+"."+imgformat) # Option 1: save image directly to disk
    
    # Option 2: read into PIL 
    web_sock = urllib.urlopen(request)
    imgdata = cStringIO.StringIO(web_sock.read()) # constructs a StringIO holding the image
    try:
        PIL_img = Image.open(imgdata)
    
    # if this cannot be read as image that, it's probably an error from the server,
    except IOError:
        print "IOError:", imgdata.read() # print error (or it may return a image showing the error"
     
    # show image 
    else:
        PIL_img.show()
        #PIL_img.save(filename_wo_extension+".jpg", "JPEG") # save as jpeg


if __name__ == '__main__':


    # define a series of location markers and their styles
    # syntax:  markers=markerStyles|markerLocation1|markerLocation2|... etc.
    marker_list = []
    marker_list.append("markers=color:blue|label:S|11211|11206|11222") # blue S at several zip code's centers
    marker_list.append("markers=size:tiny|label:B|color:0xFFFF00|40.702147,-74.015794|") # tiny yellow B at lat/long
    marker_list.append("markers=size:mid|color:red|label:6|Brooklyn+Bridge,New+York,NY") # mid-sized red 6 at search location
    # see http://code.google.com/apis/maps/documentation/staticmaps/#Markers


    # make a map around a center
    get_static_google_map("google_map_example1", center="42.950827,-122.108974", zoom=12, imgsize=(500,500),
                          imgformat="jpg", maptype="terrain" )


    get_static_google_map("google_map_example2", center="Empire+State+Building", zoom=18, imgsize=(500,500), maptype="hybrid")


    # make map that shows all the markers
    get_static_google_map("google_map_example3", imgsize=(640,640), imgformat="png", markers=marker_list )

So far, so good - but how would you show such a map (image) in a GUI? TkInter's canvas and label widgets can draw an image as a "background", however, I did run into a couple of strange issues when trying to do this inside a class. Luckily I did find this link: http://www.daniweb.com/forums/thread65288.html which let me put together a short and hopefully working example of reading a jpg image from disk and showing it inside a TkInter Frame. 

This example uses a (pretty large) label to draw the image but it could be done with a canvas widget as well, in case you want to draw something "over" the map:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# thanks to: http://www.daniweb.com/forums/thread65288.html
from Tkinter import *  # order seems to matter: import Tkinter first
import Image, ImageTk  # then import ImageTk

class MyFrame(Frame):
    def __init__(self, master, im):
        Frame.__init__(self, master)
        self.caption = Label(self, text="Some text about the map")
        self.caption.grid()
        self.image = ImageTk.PhotoImage(im) # <--- results of PhotoImage() must be stored
        self.image_label = Label(self, image=self.image, bd=0) # <--- will not work if 'image = ImageTk.PhotoImage(im)'
        self.image_label.grid()
        self.grid()

im = Image.open("mymap.jpg") # read map from disk

# or you could use the PIL image you created directly via option 2 from the URL request ...
mainw = Tk()
mainw.frame = MyFrame(mainw, im)
mainw.mainloop()

The google static map inside a TkInter Window

4 comments:

Anonymous said...

This is great. Thanks for the example.

Unknown said...

Congratulations for this post!!
It's amazing!! You helped me a lot!!
AMAZINNNGGGGG!!!
Thank for the examples and documentation!!
I'm new in Python but I understand everything because your explanation!
You deserve all the best!

Anonymous said...

Thank you very very very ... much
That was really helpful.
Fardo Campalo

Laurel said...

Thank you so SO much! I have been trying to save static maps for far too long. Your post saved me :)

Search This Blog