Public transport probability
This program uses OpenStreetMap data to calculate the chance of a hashpoint lying within X kilometres of a train station or bus stop. Bear in mind that it uses OSM data which is often incomplete.
[edit] Usage
First, get the OpenStreetMap extract for your graticule (replace 52,13 below with your own graticule).
$ wget 'http://www.informationfreeway.org/api/0.6/node[bbox=13,52,14,53][railway=station|stop|halt|tram_stop]' $ wget 'http://www.informationfreeway.org/api/0.6/node[bbox=13,52,14,53][highway=bus_stop]'
The first command retrieves all stations and on-request halts. Usually it includes surface rail, underground services and trams. The second retrieves all bus stops.
Next, create a PHP file with the code below, editing the first few lines as appropriate. Run it to create an image file and to output the probabilities. For the first run, I suggest making the $distance very small so you can check whether the OSM data looks complete.
NB: Untested on negative graticules, let me know if you have problems!
[edit] Example
For the Berlin, Germany (52,13) graticule:
david@netman1:~/geohashing/berlin$ php process.php Graticule size: E-W: 67.6534534537 km, N-S: 111.13296 km Image resolution: 608, 1000 2km ellipse is: 35.9479062169, 35.9929223517 Total pixels: 608000 Train pixels: 167803 Bus pixels: 120821 Chance of train within 2km: 27.60% Chance of train or bus within 2km; 47.47%
[edit] Code
<?php /* -*- C++ -*- */
// Calculates the probability of a hashpoint landing within X km of public transport
// See http://wiki.xkcd.com/geohashing/Public_transport_probability
//
// by David Croft, http://wiki.xkcd.com/geohashing/User:Davidc
// no copyright, released into the public domain
// Your downloaded OSM files
$bus_file = 'node[bbox=13,52,14,53][highway=bus_stop]';
$train_file = 'node[bbox=13,52,14,53][railway=station|stop|halt|tram_stop]';
// Output image file
$outfile = 'out.gif';
// Your graticule
$lat_min = 52; // n/s
$lon_min = 13; // e/w
// Distance to search in kilometres.
$distance = 2;
// You shouldn't need to edit anything below this line.
// Top right of the graticule
$lat_max = $lat_min + 1;
$lon_max = $lon_min + 1;
// Centre of the graticule.
$lat_centre = ($lat_min + $lat_max) / 2;
$lon_centre = ($lon_min + $lon_max) / 2;
// Spans (normally 1 unless you edit above)
$lat_span = $lat_max - $lat_min;
$lon_span = $lon_max - $lon_min;
// Graticule is rectangular using Mercator projection.
// All graticules are the same height.
$yres = 1000;
// <ekorren> north-south is always 60*1.852216 (because that's how the nautical mile is defined)
$ns_km = 60*1.852216;
// <ekorren> east-west is 39940.638 / 360 * cos($lat / 180 * 3.1415926) km
//$ew_km = 39940.638 / 360 * cos(deg2rad($lat_centre));
// Wikipedia formula:
$ew_km = deg2rad(cos(deg2rad($lat_centre)) * 6367.449);
echo "Graticule size: E-W: $ew_km km, N-S: $ns_km km\n";
$xres = (int)($yres * ($ew_km/$ns_km));
echo "Image resolution: $xres, $yres\n";
// Now, what is $distance at this latitude?
$ellipse_x = 2 * $xres * $distance / $ew_km;
$ellipse_y = 2 * $yres * $distance / $ns_km;
echo "${distance}km ellipse is: $ellipse_x, $ellipse_y\n";
// Create image and allocate colours
$img = ImageCreate($xres, $yres) or die();
$bg_col = ImageColorAllocate($img, 255,255,255);
$bus_col = ImageColorAllocate($img, 128, 128, 255);
$train_col = ImageColorAllocate($img, 255, 0, 0);
// Draw buses, then draw trains on top
draw_points($img, $bus_file, $bus_col);
draw_points($img, $train_file, $train_col);
// Output image
ImageGIF($img, $outfile);
// Now count pixels
// TODO adjust the weight of a pixel depending on its latitude (above the equator, higher
// pixels should have less effect on % because the physical width is less).
$total_pixels = $xres * $yres;
$train_pixels = 0;
$bus_pixels = 0;
for ($x = 0; $x < $xres; ++$x) {
for ($y = 0; $y < $yres; ++$y) {
$col = ImageColorAt($img, $x, $y);
if ($col == $train_col)
$train_pixels ++;
else if ($col == $bus_col)
$bus_pixels ++;
}
}
echo "Total pixels: $total_pixels\n";
echo "Train pixels: $train_pixels\n";
echo "Bus pixels: $bus_pixels\n";
echo "Chance of train within ${distance}km: "
. number_format(100 * $train_pixels / $total_pixels, 2) . "%\n";
echo "Chance of train or bus within ${distance}km; "
. number_format(100 * ($train_pixels + $bus_pixels) / $total_pixels, 2) . "%\n";
exit;
function draw_points($img, $file, $col)
{
global $lon_min, $lon_span;
global $lat_min, $lat_span;
global $xres, $yres;
global $ellipse_x, $ellipse_y;
$doc = new DOMDocument();
$doc->load($file) or die();
$xpath = new DOMXpath($doc);
$nodes = $xpath->query('/osm/node');
for ($i=0; $i<$nodes->length; ++$i) {
$node = $nodes->item($i);
$lat = $node->getAttribute('lat');
$lon = $node->getAttribute('lon');
$x = $xres * (($lon - $lon_min) / $lon_span);
$y = $yres - ($yres * (($lat - $lat_min) / $lat_span));
ImageFilledEllipse($img, $x, $y, $ellipse_x, $ellipse_y, $col);
}
}