-
Notifications
You must be signed in to change notification settings - Fork 0
/
gmaps-image-fetcher.py
176 lines (146 loc) · 6.51 KB
/
gmaps-image-fetcher.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
#!/usr/bin/env python
"""
Stitch together Google Maps images from lat, long coordinates
Based on work by enigmaticPhysicist, heltonbiker and BenElgar on StackExchange
https://stackoverflow.com/questions/7490491/capture-embedded-google-map-image-with-python-without-using-a-browser
Changes:
* Ability to set GOOGLE_MAPS_API_KEY as environment variable
* Added command-line options for NW and SE Lat/Lons and zoom level
* Added option to quit/continue (if the amoutn of images to be downloaded was too great)
* Ability to save images
* Added logging
* Dramatically simplified the maths.
* Set a more reasonable default logo cutoff.
* Added global constants for logo cutoff and max image size.
* Translated a couple presumably Portuguese variable names to English.
* Updated for Python 3
* Added Google Maps API key (compliance with T&C, although can set to None)
* Handle http request exceptions
"""
import argparse
import logging
import sys
from io import BytesIO
from os import environ
import requests
from PIL import Image
from math import log, exp, tan, atan, ceil
from datetime import datetime
__version__ = "0.3.2"
# circumference/radius
tau = 6.283185307179586
# One degree in radians, i.e. in the units the machine uses to store angle,
# which is always radians. For converting to and from degrees. See code for
# usage demonstration.
DEGREE = tau / 360
ZOOM_OFFSET = 8
# Max width or height of a single image grabbed from Google.
MAXSIZE = 600
# For cutting off the logos at the bottom of each of the grabbed images. The
# logo height in pixels is assumed to be less than this amount.
LOGO_CUTOFF = 32
def latlon_to_pixels(lat, lon, zoom):
mx = lon
my = log(tan((lat + tau / 4) / 2))
res = 2 ** (zoom + ZOOM_OFFSET) / tau
px = mx * res
py = my * res
return px, py
def pixels_to_latlon(px, py, zoom):
res = 2 ** (zoom + ZOOM_OFFSET) / tau
mx = px / res
my = py / res
lon = mx
lat = 2 * atan(exp(my)) - tau / 4
return lat, lon
def get_maps_image(nw_lat_long, se_lat_long, zoom=18):
try:
GOOGLE_MAPS_API_KEY = environ['GOOGLE_MAPS_API_KEY'] # set to 'your_API_key'
except KeyError as e:
logger.error("Please set your GOOGLE_MAPS_API_KEY environment variable")
sys.exit(1)
# Unpack lats/lons
ullat, ullon = nw_lat_long
lrlat, lrlon = se_lat_long
# convert all these coordinates to pixels
ulx, uly = latlon_to_pixels(ullat, ullon, zoom)
lrx, lry = latlon_to_pixels(lrlat, lrlon, zoom)
dx, dy = lrx - ulx, uly - lry # Calculate total pixel dimensions of final image
cols, rows = ceil(dx / MAXSIZE), ceil(dy / MAXSIZE) # Calculate rows and columns
logger.debug("GOOGLE_MAPS_API_KEY: ".format(GOOGLE_MAPS_API_KEY))
print("Retrieve {} image tiles from Google static-maps API".format(cols * rows))
user_input = input("Do you want to continue y/n: ")
if user_input == 'n':
sys.exit(0)
else:
# calculate pixel dimensions of each small image
width = ceil(dx / cols)
height = ceil(dy / rows)
heightplus = height + LOGO_CUTOFF
# assemble the image from stitched
final = Image.new('RGB', (int(dx), int(dy)))
for x in range(cols):
for y in range(rows):
dxn = width * (0.5 + x)
dyn = height * (0.5 + y)
latn, lonn = pixels_to_latlon(
ulx + dxn, uly - dyn - LOGO_CUTOFF / 2, zoom)
position = ','.join((str(latn / DEGREE), str(lonn / DEGREE)))
logger.info("Getting image tile from column {0} row {1} for position {2}".format(x, y, position))
urlparams = {
'center': position,
'zoom': str(zoom),
'size': '%dx%d' % (width, heightplus),
'maptype': 'satellite',
'sensor': 'false',
'scale': 1
}
logger.debug("urlparams: {}".format(urlparams))
if GOOGLE_MAPS_API_KEY is not None:
urlparams['key'] = GOOGLE_MAPS_API_KEY
url = 'http://maps.google.com/maps/api/staticmap'
try:
response = requests.get(url, params=urlparams)
response.raise_for_status()
except requests.exceptions.RequestException as e:
print(e)
sys.exit(1)
im = Image.open(BytesIO(response.content))
final.paste(im, (int(x * width), int(y * height)))
return final
def get_logger(name):
logger = logging.getLogger(name)
if not logger.handlers:
# Prevent logging from propagating to the root logger
logger.propagate = 0
console = logging.StreamHandler()
logger.addHandler(console)
formatter = logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
console.setFormatter(formatter)
return logger
if __name__ == "__main__":
# Configure command-line options
parser = argparse.ArgumentParser()
parser.add_argument('--version', action='version',
version='%(prog)s {version}'.format(version=__version__))
parser.add_argument('-d', '--debug', help='Enable debug output', action='store_true')
parser.add_argument('-l', '--logfile', help='Logfile to write', nargs=1)
parser.add_argument('-nw', '--northwest', help='NW Lat/Lon', nargs=2)
parser.add_argument('-se', '--southeast', help='SE Lat/Lon', nargs=2)
parser.add_argument('-z', '--zoom', help='Zoom level from 1 (world) to 20+ (buildings), default is 18', nargs=1)
options = parser.parse_args()
# Configure logging
logger = get_logger(__name__)
logger.setLevel(logging.INFO)
if options.logfile:
fh = logging.FileHandler(str(options.logfile))
logger.addHandler(fh)
if options.debug:
logger.setLevel(logging.DEBUG)
logger.debug("Command-line options: {}".format(options))
nw_lat_long = (float(options.northwest[0]) * DEGREE, float(options.northwest[1]) * DEGREE)
se_lat_long = (float(options.southeast[0]) * DEGREE, float(options.southeast[1]) * DEGREE)
# Get satellite image
result = get_maps_image(nw_lat_long, se_lat_long, zoom=int(options.zoom[0]))
result.show() # Show onscreen
result.save("satellite_" + datetime.now().strftime('%Y%m%d_%H%M%S') + ".bmp") # Save image