forked from robolyst/streetview
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path__init__.py
249 lines (190 loc) · 8 KB
/
__init__.py
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# -*- coding: utf-8 -*-
"""
:Module: ``streetview``
:Author: `Adrian Letchford <http://www.dradrian.com>`_
:Organisation: `Warwick Business School <http://www.wbs.ac.uk/>`_, `University of Warwick <http://www.warwick.ac.uk/>`_.
This is a light module for downloading photos from Google street view. The
functions allow you to retrieve current and **old** photos.
The photos on Google street view are panoramas and are refered to as such.
However, you have the option of downloading flat photos, or panoramas.
Retrieving photos is a two step process. First, you must translate GPS
coordinates into panorama ids. The following code retrieves a list of
the closest panoramas giving you their id and date:
>>> import streetview
>>> panoids = streetview.panoids(lat, lon)
You can then use the panorama ids to download photos with the following
function:
>>> streetview.api_download(panoid, heading, flat_dir, key)
"""
import re
from datetime import datetime
import requests
import time
import shutil
import itertools
from PIL import Image
from io import BytesIO
import os
def _panoids_url(lat, lon):
"""
Builds the URL of the script on Google's servers that returns the closest
panoramas (ids) to a give GPS coordinate.
"""
url = "https://maps.googleapis.com/maps/api/js/GeoPhotoService.SingleImageSearch?pb=!1m5!1sapiv3!5sUS!11m2!1m1!1b0!2m4!1m2!3d{0:}!4d{1:}!2d50!3m10!2m2!1sen!2sGB!9m1!1e2!11m4!1m3!1e2!2b1!3e2!4m10!1e1!1e2!1e3!1e4!1e8!1e6!5m1!1e2!6m1!1e2&callback=_xdc_._v2mub5"
return url.format(lat, lon)
def _panoids_data(lat, lon):
"""
Gets the response of the script on Google's servers that returns the
closest panoramas (ids) to a give GPS coordinate.
"""
url = _panoids_url(lat, lon)
return requests.get(url)
def panoids(lat, lon, closest=False, disp=False):
"""
Gets the closest panoramas (ids) to the GPS coordinates.
If the 'closest' boolean parameter is set to true, only the closest panorama
will be gotten (at all the available dates)
"""
resp = _panoids_data(lat, lon)
# Get all the panorama ids and coordinates
# I think the latest panorama should be the first one. And the previous
# successive ones ought to be in reverse order from bottom to top. The final
# images don't seem to correspond to a particular year. So if there is one
# image per year I expect them to be orded like:
# 2015
# XXXX
# XXXX
# 2012
# 2013
# 2014
pans = re.findall('\[[0-9]+,"(.+?)"\].+?\[\[null,null,(-?[0-9]+.[0-9]+),(-?[0-9]+.[0-9]+)', resp.text)
pans = [{
"panoid": p[0],
"lat": float(p[1]),
"lon": float(p[2])} for p in pans] # Convert to floats
if disp:
for pan in pans:
print(pan)
# Get all the dates
# The dates seem to be at the end of the file. They have a strange format but
# are in the same order as the panoids except that the latest date is last
# instead of first. They also appear to have the index of the panorama before
# them. However, the last date (which corresponds to the first/main panorama
# doesn't have an index before it. The following regex just picks out all
# values that looks like dates and the preceeding index.
dates = re.findall('([0-9]?[0-9]?[0-9])?,?\[(20[0-9][0-9]),([0-9]+)\]', resp.text)
dates = [list(d) for d in dates]
# Make sure the month value is between 1-12
dates = [d for d in dates if int(d[2]) <= 12 and int(d[2]) >= 1]
# Make the first value of the dates the index
if len(dates) > 0 and dates[-1][0] == '':
dates[-1][0] = '0'
dates = [[int(v) for v in d] for d in dates] # Convert all values to integers
# Merge the dates into the panorama dictionaries
for i, year, month in dates:
pans[i].update({'year': year, "month": month})
# Sort the pans array
def func(x):
if 'year'in x:
return datetime(year=x['year'], month=x['month'], day=1)
else:
return datetime(year=3000, month=1, day=1)
pans.sort(key=func)
if closest:
return [pans[i] for i in range(len(dates))]
else:
return pans
def tiles_info(panoid):
"""
Generate a list of a panorama's tiles and their position.
The format is (x, y, filename, fileurl)
"""
image_url = "http://cbk0.google.com/cbk?output=tile&panoid={0:}&zoom=5&x={1:}&y={2:}"
# The tiles positions
coord = list(itertools.product(range(26), range(13)))
tiles = [(x, y, "%s_%dx%d.jpg" % (panoid, x, y), image_url.format(panoid, x, y)) for x, y in coord]
return tiles
def download_tiles(tiles, directory, disp=False):
"""
Downloads all the tiles in a Google Stree View panorama into a directory.
Params:
tiles - the list of tiles. This is generated by tiles_info(panoid).
directory - the directory to dump the tiles to.
"""
for i, (x, y, fname, url) in enumerate(tiles):
if disp and i % 20 == 0:
print("Image %d (%d)" % (i, len(tiles)))
# Try to download the image file
while True:
try:
response = requests.get(url, stream=True)
break
except requests.ConnectionError:
print("Connection error. Trying again in 2 seconds.")
time.sleep(2)
with open(directory + '/' + fname, 'wb') as out_file:
shutil.copyfileobj(response.raw, out_file)
del response
def stich_tiles(panoid, tiles, directory, final_directory):
"""
Stiches all the tiles of a panorama together. The tiles are located in
`directory'.
"""
tile_width = 512
tile_height = 512
panorama = Image.new('RGB', (26*tile_width, 13*tile_height))
for x, y, fname, url in tiles:
fname = directory + "/" + fname
tile = Image.open(fname)
panorama.paste(im=tile, box=(x*tile_width, y*tile_height))
del tile
# print fname
panorama.save(final_directory + ("/%s.jpg" % panoid))
del panorama
def delete_tiles(tiles, directory):
for x, y, fname, url in tiles:
os.remove(directory + "/" + fname)
def api_download(panoid, heading, flat_dir, key, width=640, height=640,
fov=120, pitch=0, extension='jpg', year=2017):
"""
Download an image using the official API. These are not panoramas.
Params:
:panoid: the panorama id
:heading: the heading of the photo. Each photo is taken with a 360
camera. You need to specify a direction in degrees as the photo
will only cover a partial region of the panorama. The recommended
headings to use are 0, 90, 180, or 270.
:flat_dir: the direction to save the image to.
:key: your API key.
:width: downloaded image width (max 640 for non-premium downloads).
:height: downloaded image height (max 640 for non-premium downloads).
:fov: image field-of-view.
:image_format: desired image format.
You can find instructions to obtain an API key here: https://developers.google.com/maps/documentation/streetview/
"""
fname = "%s_%s_%s" % (year, panoid, str(heading))
image_format = extension if extension != 'jpg' else 'jpeg'
url = "https://maps.googleapis.com/maps/api/streetview"
params = {
# maximum permitted size for free calls
"size": "%dx%d" % (width, height),
"fov": fov,
"pitch": pitch,
"heading": heading,
"pano": panoid,
"key": key
}
response = requests.get(url, params=params, stream=True)
try:
img = Image.open(BytesIO(response.content))
filename = '%s/%s.%s' % (flat_dir, fname, extension)
img.save(filename, image_format)
except:
print("Image not found")
filename = None
del response
return filename
def download_flats(panoid, flat_dir, key, width=400, height=300,
fov=120, pitch=0, extension='jpg', year=2017):
for heading in [0, 90, 180, 270]:
api_download(panoid, heading, flat_dir, key, width, height, fov, pitch, extension, year)