Files
project_archives/goompy_testing/goompy/__init__.py
2017-11-29 12:31:43 -08:00

201 lines
6.7 KiB
Python

'''
GooMPy: Google Maps for Python
Copyright (C) 2015 Alec Singer and Simon D. Levy
This code is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this code. If not, see <http://www.gnu.org/licenses/>.
'''
import math
import PIL.Image
from io import StringIO, BytesIO
import urllib.request
import os
import time
_KEY = '&key=AIzaSyAFjAWMwQauf2Dy5KteOuT8KexfMjOCIS8' # This is Corwin's temp API key
_EARTHPIX = 268435456 # Number of pixels in half the earth's circumference at zoom = 21
_DEGREE_PRECISION = 4 # Number of decimal places for rounding coordinates
_TILESIZE = 640 # Larget tile we can grab without paying
_GRABRATE = 4 # Fastest rate at which we can download tiles without paying
_pixrad = _EARTHPIX / math.pi
def _new_image(width, height):
return PIL.Image.new('RGB', (width, height))
def _roundto(value, digits):
return int(value * 10 ** digits) / 10. ** digits
def _pixels_to_degrees(pixels, zoom):
return pixels * 2 ** (21 - zoom)
def _grab_tile(lat, lon, zoom, maptype, _TILESIZE, sleeptime):
urlbase = 'https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=%d&maptype=%s&size=%dx%d&format=jpg'
urlbase += _KEY
specs = lat, lon, zoom, maptype, _TILESIZE, _TILESIZE
filename = 'mapscache/' + ('%f_%f_%d_%s_%d_%d' % specs) + '.jpg'
tile = None
if os.path.isfile(filename):
tile = PIL.Image.open(filename)
else:
url = urlbase % specs
result = urllib.request.urlopen(url).read()
tile = PIL.Image.open(BytesIO(result))
if not os.path.exists('mapscache'):
os.mkdir('mapscache')
tile.save(filename)
time.sleep(sleeptime) # Choke back speed to avoid maxing out limit
return tile
def _pix_to_lon(j, lonpix, ntiles, _TILESIZE, zoom):
return math.degrees((lonpix + _pixels_to_degrees(((j) - ntiles / 2) * _TILESIZE, zoom) - _EARTHPIX) / _pixrad)
def _pix_to_lat(k, latpix, ntiles, _TILESIZE, zoom):
return math.degrees(math.pi / 2 - 2 * math.atan(
math.exp(((latpix + _pixels_to_degrees((k - ntiles / 2) * _TILESIZE, zoom)) - _EARTHPIX) / _pixrad)))
def fetchTiles(latitude, longitude, zoom, maptype, radius_meters=None, default_ntiles=4):
'''
Fetches tiles from GoogleMaps at the specified coordinates, zoom level (0-22), and map type ('roadmap',
'terrain', 'satellite', or 'hybrid'). The value of radius_meters deteremines the number of tiles that will be
fetched; if it is unspecified, the number defaults to default_ntiles. Tiles are stored as JPEG images
in the mapscache folder.
'''
latitude = _roundto(latitude, _DEGREE_PRECISION)
longitude = _roundto(longitude, _DEGREE_PRECISION)
# https://groups.google.com/forum/#!topic/google-maps-js-api-v3/hDRO4oHVSeM
pixels_per_meter = 2 ** zoom / (156543.03392 * math.cos(math.radians(latitude)))
# number of tiles required to go from center latitude to desired radius in meters
ntiles = default_ntiles if radius_meters is None else int(
round(2 * pixels_per_meter / (_TILESIZE / 2. / radius_meters)))
lonpix = _EARTHPIX + longitude * math.radians(_pixrad)
sinlat = math.sin(math.radians(latitude))
latpix = _EARTHPIX - _pixrad * math.log((1 + sinlat) / (1 - sinlat)) / 2
bigsize = ntiles * _TILESIZE
bigimage = _new_image(bigsize, bigsize)
for j in range(ntiles):
lon = _pix_to_lon(j, lonpix, ntiles, _TILESIZE, zoom)
for k in range(ntiles):
lat = _pix_to_lat(k, latpix, ntiles, _TILESIZE, zoom)
tile = _grab_tile(lat, lon, zoom, maptype, _TILESIZE, 1. / _GRABRATE)
bigimage.paste(tile, (j * _TILESIZE, k * _TILESIZE))
west = _pix_to_lon(0, lonpix, ntiles, _TILESIZE, zoom)
east = _pix_to_lon(ntiles - 1, lonpix, ntiles, _TILESIZE, zoom)
north = _pix_to_lat(0, latpix, ntiles, _TILESIZE, zoom)
south = _pix_to_lat(ntiles - 1, latpix, ntiles, _TILESIZE, zoom)
return bigimage, (north, west), (south, east)
class GooMPy(object):
def __init__(self, width, height, latitude, longitude, zoom, maptype, radius_meters=None, default_ntiles=4):
'''
Creates a GooMPy object for specified display widthan and height at the specified coordinates,
zoom level (0-22), and map type ('roadmap', 'terrain', 'satellite', or 'hybrid').
The value of radius_meters deteremines the number of tiles that will be used to create
the map image; if it is unspecified, the number defaults to default_ntiles.
'''
self.lat = latitude
self.lon = longitude
self.width = width
self.height = height
self.zoom = zoom
self.maptype = maptype
self.radius_meters = radius_meters
self.winimage = _new_image(self.width, self.height)
self._fetch()
halfsize = self.bigimage.size[0] // 2
self.leftx = halfsize
self.uppery = halfsize
self._update()
def getImage(self):
'''
Returns the current image as a PIL.Image object.
'''
return self.winimage
def move(self, dx, dy):
'''
Moves the view by the specified pixels dx, dy.
'''
self.leftx = self._constrain(self.leftx, dx, self.width)
self.uppery = self._constrain(self.uppery, dy, self.height)
self._update()
def useMaptype(self, maptype):
'''
Uses the specified map type 'roadmap', 'terrain', 'satellite', or 'hybrid'.
Map tiles are fetched as needed.
'''
self.maptype = maptype
self._fetch_and_update()
def useZoom(self, zoom):
'''
Uses the specified zoom level 0 through 22.
Map tiles are fetched as needed.
'''
self.zoom = zoom
self._fetch_and_update()
def _fetch_and_update(self):
self._fetch()
self._update()
def _fetch(self):
self.bigimage, self.northwest, self.southeast = fetchTiles(self.lat, self.lon, self.zoom, self.maptype,
self.radius_meters)
def _update(self):
self.winimage.paste(self.bigimage, (-self.leftx, -self.uppery))
def _constrain(self, oldval, diff, dimsize):
newval = oldval + diff
return newval if newval > 0 and newval < self.bigimage.size[0] - dimsize else oldval