Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A python script to download high resolution Google map images given a longitude, latitude and zoom level. (Works with Python 3)
#!/usr/bin/python
# GoogleMapDownloader.py
# Created by Hayden Eskriett [http://eskriett.com]
#
# A script which when given a longitude, latitude and zoom level downloads a
# high resolution google map
# Find the associated blog post at: http://blog.eskriett.com/2013/07/19/downloading-google-maps/
import urllib.request
from PIL import Image
import os
import math
class GoogleMapsLayers:
ROADMAP = "v"
TERRAIN = "p"
ALTERED_ROADMAP = "r"
SATELLITE = "s"
TERRAIN_ONLY = "t"
HYBRID = "y"
class GoogleMapDownloader:
"""
A class which generates high resolution google maps images given
a longitude, latitude and zoom level
"""
def __init__(self, lat, lng, zoom=12, layer=GoogleMapsLayers.ROADMAP):
"""
GoogleMapDownloader Constructor
Args:
lat: The latitude of the location required
lng: The longitude of the location required
zoom: The zoom level of the location required, ranges from 0 - 23
defaults to 12
"""
self._lat = lat
self._lng = lng
self._zoom = zoom
self._layer = layer
def getXY(self):
"""
Generates an X,Y tile coordinate based on the latitude, longitude
and zoom level
Returns: An X,Y tile coordinate
"""
tile_size = 256
# Use a left shift to get the power of 2
# i.e. a zoom level of 2 will have 2^2 = 4 tiles
numTiles = 1 << self._zoom
# Find the x_point given the longitude
point_x = (tile_size / 2 + self._lng * tile_size / 360.0) * numTiles // tile_size
# Convert the latitude to radians and take the sine
sin_y = math.sin(self._lat * (math.pi / 180.0))
# Calulate the y coorindate
point_y = ((tile_size / 2) + 0.5 * math.log((1 + sin_y) / (1 - sin_y)) * -(
tile_size / (2 * math.pi))) * numTiles // tile_size
return int(point_x), int(point_y)
def generateImage(self, **kwargs):
"""
Generates an image by stitching a number of google map tiles together.
Args:
start_x: The top-left x-tile coordinate
start_y: The top-left y-tile coordinate
tile_width: The number of tiles wide the image should be -
defaults to 5
tile_height: The number of tiles high the image should be -
defaults to 5
Returns:
A high-resolution Goole Map image.
"""
start_x = kwargs.get('start_x', None)
start_y = kwargs.get('start_y', None)
tile_width = kwargs.get('tile_width', 5)
tile_height = kwargs.get('tile_height', 5)
# Check that we have x and y tile coordinates
if start_x == None or start_y == None:
start_x, start_y = self.getXY()
# Determine the size of the image
width, height = 256 * tile_width, 256 * tile_height
# Create a new image of the size require
map_img = Image.new('RGB', (width, height))
for x in range(0, tile_width):
for y in range(0, tile_height):
url = f'https://mt0.google.com/vt?lyrs={self._layer}&x=' + str(start_x + x) + '&y=' + str(start_y + y) + '&z=' + str(
self._zoom)
current_tile = str(x) + '-' + str(y)
urllib.request.urlretrieve(url, current_tile)
im = Image.open(current_tile)
map_img.paste(im, (x * 256, y * 256))
os.remove(current_tile)
return map_img
def main():
# Create a new instance of GoogleMap Downloader
gmd = GoogleMapDownloader(51.5171, 0.1062, 13, GoogleMapsLayers.SATELLITE)
print("The tile coorindates are {}".format(gmd.getXY()))
try:
# Get the high resolution image
img = gmd.generateImage()
except IOError:
print("Could not generate the image - try adjusting the zoom level and checking your coordinates")
else:
# Save the image to disk
img.save("high_resolution_image.png")
print("The map has successfully been created")
if __name__ == '__main__': main()
@jackhirsh

This comment has been minimized.

Copy link

@jackhirsh jackhirsh commented Oct 17, 2018

The script doesn't seem to center the image at the given lat long, any idea how to make it do this?

@helicoder

This comment has been minimized.

Copy link

@helicoder helicoder commented Dec 13, 2018

+1 to that.

It would mean changing this part:

Find the x_point given the longitude

    point_x = (tile_size / 2 + self._lng * tile_size / 360.0) * numTiles // tile_size
@lzeee

This comment has been minimized.

Copy link

@lzeee lzeee commented Jan 17, 2019

The script doesn't seem to center the image at the given lat long, any idea how to make it do this?

I think it can't always center the image at a given point.
Because these data are fixed pictures from google map.
We can only calculate which picture contains the point we give but can't change the data itself.

@Scriptingunsam

This comment has been minimized.

Copy link

@Scriptingunsam Scriptingunsam commented Apr 12, 2020

@sebastianleonte Hi there, I found your adaptation awesome ! it worked for me. I have a question: do you know any way you could turn off labels with this method of getting an image of google map from a url query? do you know any documentation about flags of url query of google maps?

@sebastianleonte

This comment has been minimized.

Copy link
Owner Author

@sebastianleonte sebastianleonte commented Apr 13, 2020

@Scriptingunsam

Hi, I took a look at this and I found an answer in Stackoverflow you may like: https://stackoverflow.com/questions/29692737/customizing-google-map-tile-server-url/35266313

Basically there is a key "apistyle" you can use in this case to specify the visibility of the labels, as in this link will show no labels at all:

https://mt0.google.com/vt?x=4102&y=2726&z=13&apistyle=s.t%3A0|s.e%3Al|p.v%3Aoff

In the stackoverflow answer there is detailed what is what, you can hide all '0' in this case, roads is '3'... nothing really meaningful, but there you go.

Okay just to add to this, i found this link for a encoder / something more readable: https://github.com/julienben/gmaps-apistyle-encoder but it is not python.

@sebastianleonte

This comment has been minimized.

Copy link
Owner Author

@sebastianleonte sebastianleonte commented Apr 13, 2020

okay, I went a converted the code into python, here you go:
https://gist.github.com/sebastianleonte/69a5f62220fbf25dca7de86c3b6d23ac

You can go for example to https://mapstyle.withgoogle.com/ or https://snazzymaps.com to create a style and the apply it, like so:

https://mt0.google.com/vt?x=4102&y=2726&z=13&apistyle=s.e%3Ag%7Cp.c%3A%23ff1d2c4d%7C%2Cs.e%3Al%7Cp.v%3Aoff%7C%2Cs.e%3Al.t.f%7Cp.c%3A%23ff8ec3b9%7C%2Cs.e%3Al.t.s%7Cp.c%3A%23ff1a3646%7C%2Cs.t%3A17%7Cs.e%3Ag.s%7Cp.c%3A%23ff4b6878%7C%2Cs.t%3A21%7Cp.v%3Aoff%7C%2Cs.t%3A21%7Cs.e%3Al.t.f%7Cp.c%3A%23ff64779e%7C%2Cs.t%3A20%7Cp.v%3Aoff%7C%2Cs.t%3A18%7Cs.e%3Ag.s%7Cp.c%3A%23ff4b6878%7C%2Cs.t%3A81%7Cs.e%3Ag.s%7Cp.c%3A%23ff334e87%7C%2Cs.t%3A82%7Cs.e%3Ag%7Cp.c%3A%23ff023e58%7C%2Cs.t%3A2%7Cs.e%3Ag%7Cp.c%3A%23ff283d6a%7C%2Cs.t%3A2%7Cs.e%3Al.t.f%7Cp.c%3A%23ff6f9ba5%7C%2Cs.t%3A2%7Cs.e%3Al.t.s%7Cp.c%3A%23ff1d2c4d%7C%2Cs.t%3A40%7Cs.e%3Ag.f%7Cp.c%3A%23ff023e58%7C%2Cs.t%3A40%7Cs.e%3Al.t.f%7Cp.c%3A%23ff3C7680%7C%2Cs.t%3A3%7Cs.e%3Ag%7Cp.c%3A%23ff304a7d%7C%2Cs.t%3A3%7Cs.e%3Al.t.f%7Cp.c%3A%23ff98a5be%7C%2Cs.t%3A3%7Cs.e%3Al.t.s%7Cp.c%3A%23ff1d2c4d%7C%2Cs.t%3A49%7Cs.e%3Ag%7Cp.c%3A%23ff2c6675%7C%2Cs.t%3A49%7Cs.e%3Ag.s%7Cp.c%3A%23ff255763%7C%2Cs.t%3A49%7Cs.e%3Al.t.f%7Cp.c%3A%23ffb0d5ce%7C%2Cs.t%3A49%7Cs.e%3Al.t.s%7Cp.c%3A%23ff023e58%7C%2Cs.t%3A4%7Cs.e%3Al.t.f%7Cp.c%3A%23ff98a5be%7C%2Cs.t%3A4%7Cs.e%3Al.t.s%7Cp.c%3A%23ff1d2c4d%7C%2Cs.t%3A65%7Cs.e%3Ag.f%7Cp.c%3A%23ff283d6a%7C%2Cs.t%3A66%7Cs.e%3Ag%7Cp.c%3A%23ff3a4762%7C%2Cs.t%3A6%7Cs.e%3Ag%7Cp.c%3A%23ff0e1626%7C%2Cs.t%3A6%7Cs.e%3Al.t.f%7Cp.c%3A%23ff4e6d70%7C%2C

@Scriptingunsam

This comment has been minimized.

Copy link

@Scriptingunsam Scriptingunsam commented Apr 20, 2020

OH MY GOD! you are awesome! that absolutely made the trick! Solve! Thank you very much!!! @sebastian

okay, I went a converted the code into python, here you go:
https://gist.github.com/sebastianleonte/69a5f62220fbf25dca7de86c3b6d23ac

You can go for example to https://mapstyle.withgoogle.com/ or https://snazzymaps.com to create a style and the apply it, like so:

https://mt0.google.com/vt?x=4102&y=2726&z=13&apistyle=s.e%3Ag%7Cp.c%3A%23ff1d2c4d%7C%2Cs.e%3Al%7Cp.v%3Aoff%7C%2Cs.e%3Al.t.f%7Cp.c%3A%23ff8ec3b9%7C%2Cs.e%3Al.t.s%7Cp.c%3A%23ff1a3646%7C%2Cs.t%3A17%7Cs.e%3Ag.s%7Cp.c%3A%23ff4b6878%7C%2Cs.t%3A21%7Cp.v%3Aoff%7C%2Cs.t%3A21%7Cs.e%3Al.t.f%7Cp.c%3A%23ff64779e%7C%2Cs.t%3A20%7Cp.v%3Aoff%7C%2Cs.t%3A18%7Cs.e%3Ag.s%7Cp.c%3A%23ff4b6878%7C%2Cs.t%3A81%7Cs.e%3Ag.s%7Cp.c%3A%23ff334e87%7C%2Cs.t%3A82%7Cs.e%3Ag%7Cp.c%3A%23ff023e58%7C%2Cs.t%3A2%7Cs.e%3Ag%7Cp.c%3A%23ff283d6a%7C%2Cs.t%3A2%7Cs.e%3Al.t.f%7Cp.c%3A%23ff6f9ba5%7C%2Cs.t%3A2%7Cs.e%3Al.t.s%7Cp.c%3A%23ff1d2c4d%7C%2Cs.t%3A40%7Cs.e%3Ag.f%7Cp.c%3A%23ff023e58%7C%2Cs.t%3A40%7Cs.e%3Al.t.f%7Cp.c%3A%23ff3C7680%7C%2Cs.t%3A3%7Cs.e%3Ag%7Cp.c%3A%23ff304a7d%7C%2Cs.t%3A3%7Cs.e%3Al.t.f%7Cp.c%3A%23ff98a5be%7C%2Cs.t%3A3%7Cs.e%3Al.t.s%7Cp.c%3A%23ff1d2c4d%7C%2Cs.t%3A49%7Cs.e%3Ag%7Cp.c%3A%23ff2c6675%7C%2Cs.t%3A49%7Cs.e%3Ag.s%7Cp.c%3A%23ff255763%7C%2Cs.t%3A49%7Cs.e%3Al.t.f%7Cp.c%3A%23ffb0d5ce%7C%2Cs.t%3A49%7Cs.e%3Al.t.s%7Cp.c%3A%23ff023e58%7C%2Cs.t%3A4%7Cs.e%3Al.t.f%7Cp.c%3A%23ff98a5be%7C%2Cs.t%3A4%7Cs.e%3Al.t.s%7Cp.c%3A%23ff1d2c4d%7C%2Cs.t%3A65%7Cs.e%3Ag.f%7Cp.c%3A%23ff283d6a%7C%2Cs.t%3A66%7Cs.e%3Ag%7Cp.c%3A%23ff3a4762%7C%2Cs.t%3A6%7Cs.e%3Ag%7Cp.c%3A%23ff0e1626%7C%2Cs.t%3A6%7Cs.e%3Al.t.f%7Cp.c%3A%23ff4e6d70%7C%2C

@acrete

This comment has been minimized.

Copy link

@acrete acrete commented Apr 18, 2021

This works great but is there an easy way to change it so the screenshot is on the 'satellite' view instead of the map?

@sebastianleonte

This comment has been minimized.

Copy link
Owner Author

@sebastianleonte sebastianleonte commented Apr 18, 2021

Hi @acrete, I have added a new parameter for the class.

So you can simply do:
gmd = GoogleMapDownloader(51.5171, 0.1062, 13, GoogleMapsLayers.SATELLITE)

Or the layer you want!

@carvalhovitor

This comment has been minimized.

Copy link

@carvalhovitor carvalhovitor commented May 14, 2021

@sebastianleonte I'm trying to use the code but I keep getting this error, using the default coordinates:

The tile coorindates are (2846, 1651)
Could not generate the image - try adjusting the zoom level and checking your coordinates

Am I doing something wrong?

If I remove the try/else statement in main I get this error:

The tile coorindates are (4098, 2723)
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/urllib/request.py", line 1350, in do_open
    encode_chunked=req.has_header('Transfer-encoding'))
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/http/client.py", line 1277, in request
    self._send_request(method, url, body, headers, encode_chunked)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/http/client.py", line 1323, in _send_request
    self.endheaders(body, encode_chunked=encode_chunked)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/http/client.py", line 1272, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/http/client.py", line 1032, in _send_output
    self.send(msg)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/http/client.py", line 972, in send
    self.connect()
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/http/client.py", line 1447, in connect
    server_hostname=server_hostname)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/ssl.py", line 423, in wrap_socket
    session=session
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/ssl.py", line 870, in _create
    self.do_handshake()
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/ssl.py", line 1139, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1091)
@Militoarg

This comment has been minimized.

Copy link

@Militoarg Militoarg commented Jun 11, 2021

As a way of thank you for your help guys I want to share this version that works perfectly fine. Enjoy!

https://gist.github.com/Militoarg/e33a843ed4caed3a60561edb69d75f3d

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment