Unwrapped spherical projection panorama image from Google Street View

Ever wanted to download Google Street View panoramas programmatically?

I reverse-engineered their URL query scheme using the Live HTTP Headers plug-in for Firefox. From there, I just parsed through the XML returned to download each jpeg tile and then stitch them together in order.

I used a variation of this code in combination with a few other programs to parse a GPS log, download the street view panorama for each position in the log, and then stitch each frame together into a video documenting my trip on the M5 bus.

import processing.core.*;
import processing.net.*;
import processing.xml.*;

// get a hold of this for the xml library
// better way to do this part?
PApplet main = this;

void setup() {
// we usually end up with 3328 x 1664 pixel images
// let's undersample considerably for a more sane window size
size(832, 416);

// your google maps api key goes here
// sign up: http://code.google.com/apis/maps/signup.html
String apiKey = "REPLACE ME WITH YOUR VERY OWN API KEY";

// pick a location
// takes an address or a lat / lon
String location = "721 broadway, new york, ny";

// generate the panorama
Panorama pano = new Panorama(location, apiKey);

// show the image
image(pano.fullPano, 0, 0, width, height);

// save the full-res image to the sketch folder
pano.fullPano.save(location + ".jpg");

// skip the draw loop
noLoop();
}

class Panorama {
float lat;
float lon;
String panoId;
int imageWidth;
int imageHeight;
int tileWidth;
int tileHeight;
int xTileCount;
int yTileCount;
PImage[][] tiles;
PImage fullPano;
String apiKey;

// constructor
Panorama(String address, String _apiKey) {
apiKey = _apiKey;
String[] location = geocode(address);
lon = float(location[0]);
lat = float(location[1]);
fetchInfo(lat, lon);
buildImage();
}

void fetchInfo(float lat, float lon) {
String url = "http://maps.google.com/cbk?output=xml&ll=" + lat + "," + lon;

XMLElement response = new XMLElement(main, url);

XMLElement kid = response.getChild(0);
imageWidth = kid.getIntAttribute("image_width");
imageHeight = kid.getIntAttribute("image_height");
tileWidth = kid.getIntAttribute("tile_width");
tileHeight = kid.getIntAttribute("tile_height");
panoId = kid.getStringAttribute("pano_id");

xTileCount = imageWidth / tileWidth;
yTileCount = imageHeight / tileHeight;

println("Panorama ID: " + panoId);
println("X Tile Count: " + xTileCount);
println("Y Tile Count: " + yTileCount);
}

void buildImage() {
fullPano = createImage(imageWidth, imageHeight, RGB);
fullPano.loadPixels();

for (int xPos = 0; xPos<= xTileCount; xPos++) {
for (int yPos = 0; yPos<= yTileCount; yPos++) {
// &.jpg fools processing into handling it
String imageUrl = "http://cbk0.google.com/cbk?output=tile&panoid=" + panoId + "&zoom=3&x=" + xPos + "&y=" + yPos + "&.jpg";
fullPano.set(xPos * tileWidth, yPos * tileHeight, loadImage(imageUrl));
println("Loaded tile " + xPos + "::" + yPos);
}
}

fullPano.updatePixels();
}

String[] geocode(String address) {
String cleanAddress = address.replace(' ', '+');
String url = "http://maps.google.com/maps/geo?q=" + cleanAddress + "&output=xml&oe=utf8&sensor=false&key=" + apiKey;
println(url);
XMLElement location = new XMLElement(main, url);
XMLElement kid = location.getChild("Response/Placemark/Point/coordinates");
String rawCoordinates = kid.getContent();
String[] latlon = shorten(split(rawCoordinates, ',')); // ditch the 0
println(latlon);
return latlon;
}
}