Google Maps provides several useful APIs for accessing data: a geocoding API to convert addresses to latitude and longitude, a search API to provide locations matching a term, and a details API for retrieving location metadata.

For many mapping tasks it is valuable to get a large list of locations (restaurants, churches, etc) – since this is valuable, Google places a rate limiter on the information, and encourages caching query results.

You can load a specific area of a map – the best way to find the starting point for the latitude and longitude is to enter an address in a geocoding API:

map = new google.maps.Map(document.getElementById('map-canvas'), { mapTypeId: google.maps.MapTypeId.ROADMAP, center: new google.maps.LatLng(curLat, curLong), zoom: 15, styles: [ { stylers: [ { visibility: 'simplified' } ] }, { elementType: 'labels', stylers: [ { visibility: 'off' } ] } ] });

To run a search, you can use the radarSearch API, which appears to return up to 200 results. However, this only returns latitudes and longitudes – not place names or anything you’d really want to a full application.

google.maps.event.addListenerOnce(map, 'bounds_changed', performSearch); function performSearch() { var request = { bounds: map.getBounds(), keyword: 'church' }; service.radarSearch(request, callback); }

Once that finishes, it runs a callback – in this we save off the results so far, and set up a timer to get the full address of each entity. I determined experimentally that the Maps API won’t let you run a query more than once every two seconds – this adds a little extra lag because I’d rather the script continue than risk an error being slightly too soon.

function callback(results, status) { for (var i = 0, place; place = results[i]; i++) { createMarker(place); setTimeout(loadPlace, 2200 * i); }

Each “place” is hydrated using the getDetails function on the maps API, then saved back to a server:

function loadPlace() { place = places[placeIdx++]; service.getDetails(place, function(result, status) { if (status != google.maps.places.PlacesServiceStatus.OK) { return; } $.post( "save.php", {text: JSON.stringify(result)}, function() { next(); }); }); }

This requires a simple PHP file- the results can be extracted later or used as a cache.

$text = $_POST['text']; $json = json_decode($text, true); $id = md5($text); file_put_contents('db/' . $id, $text);

Up to this point, we only have the ability to script a specific segment of a map – in reality we likely want to loop back and forth across an area. I found a bounding box that encompasses Philadelphia and the surrounding counties relatively well experimentally, by loading the map in several areas until I found good edges.

Interestingly, Google Maps does not seem to have the same scale for latitude and longitude, as I found about one map unit area to be about 20x longitude as latitude (ideally this is slightly smaller than one box – this gives a little overlap and record a few entries twice)

var minLat = 39.873; var minLong = -75.483; var maxLat = 40.453; var maxLong = -75.163; var dLat = 0.01; var dLong = 0.2;

Finally, we need to define a function which moves the current map location over to the right or down, back and forth, until we read the entire area we want:

function next() { if (placeIdx >= places.length) { curLat += dLat; if (curLat > maxLat) { curLong += dLong; curLat = minLat; } if (curLong <= maxLong) { setTimeout(initialize, Math.max( 2100, 2100 * (places.length - placeIdx))); } } }

This function must be called in a few places- anywhere there could be an error or a finished task which would otherwise stop the script. If we don't do this, it will stop partway through:

if (status != google.maps.places.PlacesServiceStatus.OK) { placeIdx = 1000000; next(); return; } places = results; if (!results) { next(); return; } if (results.length == 0) { next(); return; }