Skip to content

Instantly share code, notes, and snippets.

@praeclarum
Created April 10, 2014 22:28
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save praeclarum/10428983 to your computer and use it in GitHub Desktop.
Save praeclarum/10428983 to your computer and use it in GitHub Desktop.
Downloads and stitches map tiles
using System;
using System.Net;
using System.IO;
using Gdk;
namespace TileSticher
{
class App
{
public static int Main (string[] args)
{
if (args.Length != 5) {
Console.WriteLine ("TileSticher.exe latitude longitude zoom numHorizontal numVertical");
return 1;
}
//
// Get the root tile
//
var latitude = double.Parse (args [0]);
var longitude = double.Parse (args [1]);
var zoom = int.Parse (args [2]);
var numHorizontal = int.Parse (args [3]);
var numVertical = int.Parse (args [4]);
var t = GetTileIndex (latitude, longitude, zoom);
//
// Download all the tiles
//
var style = SatelliteUrlTemplate;
var xo = -numHorizontal / 2;
var yo = -numVertical / 2;
for (var i = 0; i < numHorizontal; i++) {
for (var j = 0; j < numVertical; j++) {
var index = new Point (t.X + i + xo, t.Y + j + yo);
DownloadTile (style, index, zoom);
}
}
//
// Merge them
//
var rootImage = LoadTile (style, t, zoom);
var width = rootImage.Width;
var height = rootImage.Height;
var outputWidth = width * numHorizontal;
var outputHeight = height * numVertical;
var output = new Pixbuf (Colorspace.Rgb, false, 8, outputWidth, outputHeight);
for (var i = 0; i < numHorizontal; i++) {
for (var j = 0; j < numVertical; j++) {
var index = new Point (t.X + i + xo, t.Y + j + yo);
using (var image = LoadTile (style, index, zoom)) {
image.Composite (
output,
width * i, height * j,
width, height,
width * i, height * j,
1, 1,
InterpType.Bilinear, 255);
}
}
}
output.Save ("Poop.png", "png");
Console.WriteLine ("Created {0}x{1} Poop.png", outputWidth, outputHeight);
return 0;
}
static Pixbuf LoadTile (string style, Point index, int zoom)
{
return new Pixbuf (GetTileFilename (style, index, zoom));
}
static string GetTileFilename (string style, Point index, int zoom)
{
return string.Format ("{0}-{1}-{2}-{3}.png", index.X, index.Y, zoom, style.Length);
}
static void DownloadTile (string style, Point index, int zoom)
{
var filename = GetTileFilename (style, index, zoom);
if (File.Exists (filename))
return;
var url = string.Format (style, index.X, index.Y, zoom);
Console.WriteLine ("Downloading {0}", url);
var web = new WebClient ();
web.Headers.Add ("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)");
web.DownloadFile (url, filename);
}
const string MapUrlTemplate = "https://www.google.com/maps/vt/pb=!1m4!1m3!1i{2}!2i{0}!3i{1}!2m3!1e0!2sm!3i258145710";
const string SatelliteUrlTemplate = "https://khms2.google.com/kh/v=147&x={0}&y={1}&z={2}";
static Point GetTileIndex (double latitude, double longitude, int zoom)
{
var lat_rad = latitude / 180 * Math.PI;
var n = 1 << zoom;
var xtile = n * ((longitude + 180) / 360);
var ytile = n * (1 - (Math.Log (Math.Tan (lat_rad) + 1/Math.Cos (lat_rad)) / Math.PI)) / 2;
return new Point ((int)xtile, (int)ytile);
}
}
}
@understar
Copy link

Url request maybe rejected by the Google server?

@TomasHubelbauer
Copy link

I know this gist is five years old, but for anyone wondering, it doesn't work anymore. The endpoint returns 403.

Moreover, Google presumable switched to compressed/encrypted tile endpoint response bodies so the endpoint on live Google Maps, when inspected using the DevTools, returns garbage data, not actual image data. One would have to go in and reverse engineer the JavaScript of Google Maps to be able to decypher it.

@sleirsgoevy
Copy link

@TomasHubelbauer, you are wrong. The URL in question still works and returns plain images.

@TomasHubelbauer
Copy link

@sleirgoevy Can you please post 2 example URLs, one for the normal map tile and one for the satellite map tile, which when accessed with the browser serve an image correctly? Any coordinates, I just want some baseline for comparison. I must be doing something wrong on my end, but when I tried this, all I got were 404/403 responses.

@sleirsgoevy
Copy link

Normal map: https://www.google.com/maps/vt/pb=!1m4!1m3!1i0!2i0!3i0!2m3!1e0!2sm!3i0
Satellite: https://khms3.google.com/kh/v=845?x=0&y=0&z=0
The normal link works (for me) both in browser and using cURL. The second only worked in browser, however spoofing the User-Agent header seems to be enough to retrieve the image.
I suppose Google may be restricting these APIs to specific countries, so you may be able to query these URLs via a VPN or proxy.

@TomasHubelbauer
Copy link

Thank you!

@stellarpower
Copy link

I believe the v parameter may be a version number. If I check a loaded tile in the browser inspector, and bump the version of the URL in the comment above, the URL loads correctly. Otherwise, it 404s. There are also several KHMS domains. I don't know if they're based on location, and thus may have different availability for different folks, or functioning as a simple load-balancer.

@stellarpower
Copy link

I have forked into javascript in case of any use; it can be used from Google Apps Script from e.g. a spreadsheet.

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