Drawing Google Streetview images down an entire street using python

I’ve previously written about grabbing Google Streetview images given a particular address. For a different project I sampled images running along an entire street, so figured I would share that code. It is a bit more complicated though, because when you base it off an address you do not need to worry about drawing the same image twice. So I will walk through an example.

So first we will import the necessary libraries we are using, then will globally define your user key and the download folder you want to save the streetview images into.

#Upfront stuff you need
import urllib, os, json
key = "&key=" + "!!!!!!!!!!!!!YourAPIHere!!!!!!!!!!!!!!!!"
DownLoc = r'!!!!!!!!!!!YourFileLocationHere!!!!!!!!!!!!!!'  

Second are a few functions. The first, MetaParse, grabs the date (Month and Year) and pano_id from a particular street view image. Because if you submit just a slightly different set of lat-lon, google will just download the same image again. To prevent that, we do a sort of memoization, where we grab the meta-data first, stuff it in a global list PrevImage. Then if you have already downloaded that image once, the second GetStreetLL function will not download it again, as it checks the PrevImage list. If you are doing a ton of images you may limit the size of PrevImage to a certain amount, but it is no problem doing a few thousand images as is. (With a free account you can IIRC get 25,000 images in a day, but the meta-queries count against that as well.)

def MetaParse(MetaUrl):
    response = urllib.urlopen(MetaUrl)
    jsonRaw = response.read()
    jsonData = json.loads(jsonRaw)
    #return jsonData
    if jsonData['status'] == "OK":
        if 'date' in jsonData:
            return (jsonData['date'],jsonData['pano_id']) #sometimes it does not have a date!
        else:
            return (None,jsonData['pano_id'])
    else:
        return (None,None)

PrevImage = [] #Global list that has previous images sampled, memoization kindof        
        
def GetStreetLL(Lat,Lon,Head,File,SaveLoc):
    base = r"https://maps.googleapis.com/maps/api/streetview"
    size = r"?size=1200x800&fov=60&location="
    end = str(Lat) + "," + str(Lon) + "&heading=" + str(Head) + key
    MyUrl = base + mid + end
    fi = File + ".jpg"
    MetaUrl = base + r"/metadata" + size + end
    #print MyUrl, MetaUrl #can check out image in browser to adjust size, fov to needs
    met_lis = list(MetaParse(MetaUrl))                           #does not grab image if no date
    if (met_lis[1],Head) not in PrevImage and met_lis[0] is not None:   #PrevImage is global list
        urllib.urlretrieve(MyUrl, os.path.join(SaveLoc,fi))
        met_lis.append(fi)
        PrevImage.append((met_lis[1],Head)) #append new Pano ID to list of images
    else:
        met_lis.append(None)
    return met_lis  

Now we are ready to download images running along an entire street. To get the necessary coordinates and header information I worked it out in a GIS. Using a street centerline file I regularly sampled along the streets. Based on those sample points then you can calculate a local trajectory of the street, and then based on that trajectory turn the camera how you want it. Most social science folks I imagine want it to look at the sidewalk, so then you will calculate 90 degrees to the orientation of the street.

Using trial and error I found that spacing the samples around 40 feet apart tended to get a new image. I have the pixel size and fov parameters to the streetview api hard set in the function, but you could easily amend the function to take those as arguments as well.

So next I have an example list of tuples with lat-lon’s and orientation. Then I just loop over those sample locations and draw the images. Here I also have another list image_list, that contains what I save the images too, as well as saves the pano-id and the date meta data.

DataList = [(40.7036043470179800,-74.0143908501053400,97.00),
            (40.7037139540670900,-74.0143727485309500,97.00),
            (40.7038235569946140,-74.0143546472568100,97.00),
            (40.7039329592712600,-74.0143365794219800,97.00),
            (40.7040422704154500,-74.0143185262956300,97.00),
            (40.7041517813782500,-74.0143004403322000,97.00),
            (40.7042611636045350,-74.0142823755611700,97.00),
            (40.7043707615693800,-74.0142642750708300,97.00)]

    
image_list = [] #to stuff the resulting meta-data for images
ct = 0
for i in DataList:
    ct += 1
    fi = "Image_" + str(ct)
    temp = GetStreetLL(Lat=i[0],Lon=i[1],Head=i[2],File=fi,SaveLoc=DownLoc)
    if temp[2] is not None:
        image_list.append(temp)

I have posted the entire python code snippet here. If you want to see the end result, you can check out the photo album. Below is one example image out of the 8 in that street segment, but when viewing the whole album you can see how it runs along the entire street.

Still one of the limitations of this is that there is no easy way to draw older images that I can tell — doing this approach you just get the most recent image. You need to know the pano-id to query older images. Preferably the meta data json should contain multiple entries, but that is not the case. Let me know if there is a way to amend this to grab older imagery or imagery over time. Here is a great example from Kyle Walker showing changes over time in Detroit.

Advertisements
Leave a comment

11 Comments

  1. Simon Ghislain

     /  May 12, 2018

    Hello again,

    I just left a comment on another of your post, but I guess this one is closer to the bojective i’m trying to achieve…

    Btw i have again same kind of error when i run the code (entire python code snippet):
    AttributeError: module ‘urllib’ has no attribute ‘urlopen’

    I’m completely new to Google map API (never used it), but there seem to be a bunch of parameters that can be accessed from a itineray:
    https://developers.google.com/maps/documentation/directions/intro#Waypoints

    (with MAPS Direction API, sorry it’s french version of the site)

    Do you think the process of collecting all points of the road (as well as directions –to be looking ahead on the road, not sideways–) can be done only with the API, without ArcGIS?

    Oh god, I really don’t know how to put all this together…I even don’t know yet how I can use that Google Maps API, I never used JSON i don’t know what is it yet 😀

    Anyway, thank you again, i’m very glad to discover someone has already done something similar to what i wish to do…

    Going back to analyse your code…
    Hoping to hear from you soon!

    Reply
    • It sounds like you have most of the ingredients. Using the directions API you can get JSON for the route. At that point you just need to write code to interpolate along the path at regular intervals. That will be some work though to figure that out with the spherical coordinates. It might be easier to project the coordinates and use shapely, http://shapely.readthedocs.io/en/stable/manual.html.

      To generate the route foregoing the Google directions API you might want to check out OSMnx, http://geoffboeing.com/2016/11/osmnx-python-street-networks/.

      Reply
      • Simon Ghislain

         /  May 12, 2018

        Thank you for your answer…
        I found someone doing exactly that, but in Java:
        https://stackoverflow.com/questions/47784512/plotting-coordinates-on-route-in-gmap-google-maps-android-api

        I would like to do it in Python, as I already “took in hands” your code and it’s working…
        I installed googlemaps python API…but i don’t know where to start…

        could you give me an example of like 5 lines of codes to use googlemaps API to generate itinerary from A to B, and how to access the .routes, .legs, .steps, etc…information?

        Again, thank you so much for any help, you save me

      • I don’t have code to do your exact task, so you will need to strike out some on your own. The order of tasks will be something like:

        – use begin & end locations to query a route via the google directions api.
        – take that route in spherical coordinates, project it into any suitable local projection (can use pyproj)
        – use the shapely library to interpolate points at regular intervals along the route
        – calculate the local orientation of the road from those interpolated points
        – reproject the interpolated points back to spherical coordinates
        – grab the streetview images at those interpolated points given your preferred orientation of the camera (which you can figure out via knowing the direction of the road)
        – stitch the resulting images into a GIF

        You should add a small inset map that shows a point flowing along the route where the images are for as well ;).

        Pretty sure that is doable, but will take some work.

      • Simon Ghislain

         /  May 12, 2018

        Thank you very much, I will try that, and I keep you updated on my full code once it is working!
        Hope i’ll be back soon…
        Thank you again

      • Simon Ghislain

         /  May 12, 2018

        I DID IT!!
        I F***** MANAGED TO DO IT!
        (except orientation sector)

        Here is my brute code:

        import json
        import urllib.request
        import urllib.parse
        import os
        from pyproj import Proj
        import math
        myloc = r”C:\Gmap\01″
        key = “&key=” + “”

        def distance_cart(p1, p2):
        return( math.sqrt((p1[0]-p2[0])*(p1[0]-p2[0]) + (p1[1]-p2[1])*(p1[1]-p2[1])))

        def GetStreet(Add,num, SaveLoc):
        base = “https://maps.googleapis.com/maps/api/streetview?size=1200×800&location=”
        MyUrl = base + urllib.parse.quote_plus(Add) + “&heading=” + str(00.00) + key #added url encoding
        fi = str(num) + “.jpg”
        urllib.request.urlretrieve(MyUrl, os.path.join(SaveLoc,fi))

        def decode_polyline(polyline_str):
        index, lat, lng = 0, 0, 0
        coordinates = []
        changes = {‘latitude’: 0, ‘longitude’: 0}

        while index < len(polyline_str):
        for unit in ['latitude', 'longitude']:
        shift, result = 0, 0

        while True:
        byte = ord(polyline_str[index]) – 63
        index+=1
        result |= (byte & 0x1f) <= 0x20:
        break

        if (result & 1):
        changes[unit] = ~(result >> 1)
        else:
        changes[unit] = (result >> 1)

        lat += changes[‘latitude’]
        lng += changes[‘longitude’]

        coordinates.append((lat / 100000.0, lng / 100000.0))

        return coordinates

        start = “Ottignies, Belgique”
        finish = “Namur, Belgique”

        url = ‘http://maps.googleapis.com/maps/api/directions/json?%s’ % urllib.parse.urlencode((
        (‘origin’, start),
        (‘destination’, finish)
        ))
        ur = urllib.request.urlopen(url)
        result = json.load(ur)
        coord=[]
        for i in range (0, len (result[‘routes’][0][‘legs’][0][‘steps’])):
        points1 = result[‘routes’][0][‘legs’][0][‘steps’][i][‘polyline’][‘points’]
        if (points1 is not None):
        coord.append(decode_polyline(points1))

        cc=[]
        cce=[]
        for i in range (0, len(coord)):
        for j in range(0, len(coord[i])):
        cc.append(coord[i][j])
        lat=coord[i][j][0]
        long=coord[i][j][1]
        coord_f = str(lat) + “,” + str(long)

        p = Proj(proj=’utm’, zone=10, ellps=’WGS84′) # use kwargs
        x, y = p(lat, long)
        cce.append([x,y])

        mess= ‘x=%9.3f’ % cce[i][0]

        #print(mess)

        #GetStreet(Add=coord_f,SaveLoc=myloc)

        print(len(cc))
        print(len(cce))

        res=[]
        p0=cce[0]
        res.append(p0)
        temp=0
        prev=p0
        distance_max=500
        for i in range (1, len(cce)):
        temp = temp + distance_cart(cce[i], prev)
        if (temp>distance_max):
        res.append(cce[i])
        temp=0
        prev=cce[i]

        print(len(res))

        final=[]
        for i in range (0, len(res)):
        p = Proj(proj=’utm’, zone=10, ellps=’WGS84′)
        lat, long = p(res[i][0], res[i][1], inverse=True)
        coord_f = str(lat) + “,” + str(long)
        GetStreet(Add=coord_f, num=i, SaveLoc=myloc)

        It saves all the images..it’s working perfectly! withtout shapely : a simply sqrt to compute distances…

  2. Simon Ghislain

     /  May 12, 2018

    But it seems i’ve been banned now because i didnt use the key while devugging…
    How can I know I’m banned?

    At run, I get a cascade of error starting from urlretrieve() to
    request.py”, line 650, in http_error_default
    raise HTTPError(req.full_url, code, msg, hdrs, fp)
    urllib.error.HTTPError: HTTP Error 403: Forbidden

    Error 403 forbidden?
    I worked 2min ago, i changed nothing… :-s

    Reply
    • Nice, yeah just set up a new api key, make sure it works, and then you should be good to go. Sometimes with the google APIs it will let me get ~100 requests before it bans me, but some of the api’s don’t work at all without a key.

      Reply
      • Simon Ghislain

         /  May 12, 2018

        Yeah definitely looks like i’m banned…
        I tried with a valid key (i know it’s valid because i made a request using googlemaps module and i worked)

        The line triggering the errors is in GetStreet() function:
        urllib.request.urlretrieve(MyUrl, os.path.join(SaveLoc,fi))

        It Triggers all the way to HTTP Error 403: Forbidden

        How long generally are you banned?
        Are there methods to “reset” the ~100 requests quota without key?

        I have several API keys in Google api console (credentials tab)…without restrictions and enabled for API Directions (but not for Google street view…and i can’t find it)

        Again, thank you so much for your help, I would never have been able to achieve that without you…

  3. Simon Ghislain

     /  May 12, 2018

    Oh…sorry…nevermind my last message, I just found the Street View API, i just had to get an API key for that particular API, and now it works again 🙂

    Reply
  4. Simon Ghislain

     /  May 12, 2018

    Here is my final workingg code (with correct orientation+saving all images into a .gif and a .mp5 video):

    import json
    import urllib.request
    import urllib.parse
    import googlemaps
    import os
    from pyproj import Proj
    import math
    import imageio
    import sys
    import moviepy.editor as mp

    myloc = r”C:\Gmap\01″
    key = “&key=” + “API_KEY”
    res_dir=[]
    filesn=[]

    def distance_cart(p1, p2):
    return( math.sqrt((p1[0]-p2[0])*(p1[0]-p2[0]) + (p1[1]-p2[1])*(p1[1]-p2[1])))

    def dir_cart(p1, p2):
    if p1[0]-p2[0] == 0:
    return math.degrees(math.pi/2.0)
    else:
    return( math.degrees(math.atan((p1[1]-p2[1]) / (p1[0]-p2[0]))))

    def create_gif(filenames, duration):
    images = []
    for filename in filenames:
    images.append(imageio.imread(filename))
    output_file = ‘Gif-1.gif’
    imageio.mimsave(output_file, images, duration=duration)

    def GetStreet(Add,num, SaveLoc):
    base = “https://maps.googleapis.com/maps/api/streetview?size=1200×800&location=”
    MyUrl = base + urllib.parse.quote_plus(Add) + “&heading=” + str(res_dir[num]+180.00) + key
    print(“processing ” + str(num) + “//” + str(len(res_dir)))
    fi = str(num) + “.jpg”
    filesn.append(str(os.path.join(SaveLoc, fi)))
    urllib.request.urlretrieve(MyUrl, os.path.join(SaveLoc,fi))

    def decode_polyline(polyline_str):
    index, lat, lng = 0, 0, 0
    coordinates = []
    changes = {‘latitude’: 0, ‘longitude’: 0}

    while index < len(polyline_str):
    for unit in ['latitude', 'longitude']:
    shift, result = 0, 0

    while True:
    byte = ord(polyline_str[index]) – 63
    index+=1
    result |= (byte & 0x1f) <= 0x20:
    break

    if (result & 1):
    changes[unit] = ~(result >> 1)
    else:
    changes[unit] = (result >> 1)

    lat += changes[‘latitude’]
    lng += changes[‘longitude’]

    coordinates.append((lat / 100000.0, lng / 100000.0))

    return coordinates

    itin=[]
    #create a list of checkpoints for the entire itinerary:
    itin.append(“Bruxelles, Belgique”)
    itin.append(“Ottignies, Belgique”)
    itin.append(“Namur, Belgique”)

    cc=[]
    cce=[]
    print(“len itin: ” + str(len(itin)))

    for k in range(1, len(itin)):

    url = ‘http://maps.googleapis.com/maps/api/directions/json?%s’ % urllib.parse.urlencode((
    (‘origin’, itin[k-1]),
    (‘destination’, itin[k])
    ))
    ur = urllib.request.urlopen(url)
    result = json.load(ur)
    coord=[]
    for i in range (0, len (result[‘routes’][0][‘legs’][0][‘steps’])):
    points1 = result[‘routes’][0][‘legs’][0][‘steps’][i][‘polyline’][‘points’]
    if (points1 is not None):
    coord.append(decode_polyline(points1))

    for i in range (0, len(coord)):
    for j in range(0, len(coord[i])):
    cc.append(coord[i][j])
    lat=coord[i][j][0]
    long=coord[i][j][1]

    p = Proj(proj=’utm’, zone=10, ellps=’WGS84′) # use kwargs
    x, y = p(lat, long)
    cce.append([x,y])

    print(“len cce ” + str(k) + “:” + str(len(cce)))
    print(“len cce tot: ” + str(len(cce)))
    res=[]

    p0=cce[0]
    res.append(p0)
    temp=0
    prev=p0
    distance_max=250
    for i in range (1, len(cce)):
    temp = temp + distance_cart(cce[i], prev)
    if (temp>distance_max):
    res.append(cce[i])
    res_dir.append(dir_cart(cce[i], prev))
    temp=0
    prev=cce[i]
    res.append(cce[len(cce)-1])
    res_dir.append(dir_cart(cce[len(cce)-1], prev))
    print(len(res))
    print(“resdir: ” + str(len(res_dir)))

    for i in range (0, len(res)-1):
    #for i in range(0, 10):
    p = Proj(proj=’utm’, zone=10, ellps=’WGS84′)
    lat, long = p(res[i][0], res[i][1], inverse=True)
    coord_f = str(lat) + “,” + str(long)
    GetStreet(Add=coord_f, num=i, SaveLoc=myloc)

    create_gif(filesn, .2)

    clip = mp.VideoFileClip(“Gif-1.gif”)
    clip.write_videofile(“gifvideo.mp4”)

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: