London Underground geographic maps/PHP

<?PHP
set_time_limit(0); //suppress time-outs

/*
   [Start: Program Information Header]

    Name    : Tube mapper
    Purpose : ----
    Syntax  :
    Version : 0.15
    Date    : 20.vii.2004
    License : BSD

   [End: Program Information Header]

   [Start: Author Information Header]

    Name    : James D. Forrester
    Name    : Ed Sanders

   [End: Author Information Header]
*/

header('Content-type: text/plain');

// POPULATE WITH MYSQL DATABASE DETAILS
$mysql_server = '';
$mysql_user = '';
$mysql_password = '';
$mysql_dbname = '';

$dbserverlink = mysql_connect($mysql_server,$mysql_user,$mysql_password) or die('Error connecting to database: '.mysql_error().'</body></html>');
mysql_select_db($mysql_dbname) or die('Error selecting database: '.mysql_error().'</body></html>');

// choose a zone limit...
$this_zone = 1;
// or a specific line (0 if using a zone)
$this_line = 0;

$width=900; $height=600;

/* Sizes I used for each line, chosen by trial and improvement */

switch($this_line)
{
  case 1:
    $width = 900;
    $height = 600;
    break;

  case 2:
    $width = 1200;
    $height = 720;
    break;

  case 3:
    $width = 1000;
    $height = 570;
    break;

  case 4:
    $width = 2000;
    $height = 860;
    break;

  case 5:
    $width = 320;
    $height = 490;
    break;

  case 6:
    $width = 1200;
    $height = 350;
    break;

  case 7:
    $width = 910;
    $height = 580;
    break;

  case 8:
    $width = 1280;
    $height = 700;
    break;

  case 9:
    $width = 580;
    $height = 1000;
    break;

  case 10:
    $width = 1000;
    $height = 800;
    break;

  case 11:
    $width = 470;
    $height = 570;
    break;

  case 12:
    $width = 300;
    $height = 200;
    break;

  case 13:
    $width = 800;
    $height = 600;
    break;
}

$header = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink"
   xmlns="http://www.w3.org/2000/svg"
   id="svg2"
   width="'.$width.'"
   height="'.$height.'"
   y="0.00000000"
   x="0.00000000"
   version="1.0">';
/*
 == BR logo (non-free license) ==
      <polygon id="rail" fill="red" stroke="none" transform="scale(0.5)"
            points="5,0 12,0 21,4 29,4 29,7 21,7 14,11
                    29,11 29,14 14,14 23,18 16,18 7,14
                    0,14 0,11 7,11 14,7 0,7 0,4 14,4" />
*/

$header .= '
  <defs>
    <g transform="scale(0.2) translate(-186, -460)"
       id="rail">
      <rect
         id="body"
         style="fill:#808080;stroke:none;"
         y="477.49323"
         x="186.32202"
         height="24.131897"
         width="90.994476" />
      <circle
         id="boiler"
         style="fill:#808080"
         transform="translate(231.8686,481.5260)"
         r="10" cy="0" cx="0" />
      <circle
         id="wheel1"
         style="fill:#808080;stroke:#ffffff;stroke-width:3"
         transform="translate(201.8686,503.5260)"
         r="10" cy="0" cx="0" />
      <circle
         id="wheel2"
         style="fill:#808080;stroke:#ffffff;stroke-width:3"
         transform="translate(231.8686,503.5260)"
         r="10" cy="0" cx="0" />
      <circle
         id="wheel3"
         style="fill:#808080;stroke:#ffffff;stroke-width:3"
         transform="translate(261.8686,503.5260)"
         r="10" cy="0" cx="0" />
      <rect
         id="chimney"
         style="fill:#808080;"
         y="461.22095"
         x="255.97266"
         height="23.334524"
         width="14.142136" />
      <rect
         id="cabin"
         style="fill:none;stroke:#808080;stroke-width:4;"
         y="462.04932" x="188.32202"
         height="20" width="20" />
    </g>
  </defs>
  <defs>
    <circle id="intersection" cx="0" cy="0" r="4" style="fill: white; stroke: black; stroke-width: 1.5;" />
  </defs>';

// NB: "latitude * 1.6" is to account for the approximate ratio of latitude to longitude
// around London. For larger maps, this will obviously require a proper projection function

// Calculate boundaries
if($this_zone)
{
  $this_line_name = 'Zone '.$this_zone;
  
  extract(mysql_fetch_assoc(mysql_query("
    SELECT
      MAX(longitude) - MIN(longitude) as long_diff, MAX(latitude*1.6) - MIN(latitude*1.6) as lat_diff,
      MIN(longitude) as long_min, MIN(latitude*1.6) as lat_min
    FROM stations
    INNER JOIN links ON id=station1 OR id=station2
    WHERE zone < $this_zone + 1")));
}
else
{
  $this_line_name = mysql_result(mysql_query("SELECT name FROM routes WHERE line=$this_line"), 0);
  
  extract(mysql_fetch_assoc(mysql_query("
    SELECT
      MAX(longitude) - MIN(longitude) as long_diff, MAX(latitude*1.6) - MIN(latitude*1.6) as lat_diff,
      MIN(longitude) as long_min, MIN(latitude*1.6) as lat_min
    FROM stations
    INNER JOIN links ON id=station1 OR id=station2
    WHERE line = $this_line")));
}

if($long_diff/$lat_diff > $width/$height)
{
  $scale = ($width-100)/$long_diff;
}
else
{
  $scale = ($height-100)/$lat_diff;
}
$lat_min -= (($height/$scale)-$lat_diff)/2;
$long_min -= (($width/$scale)-$long_diff)/2;

// Load stations into memory
$stations = array();

$query_handle = mysql_query("
  SELECT id, longitude-$long_min as xpos, (latitude*1.6)-$lat_min as ypos, name, display_name, total_lines, rail, line
  FROM stations
  INNER JOIN links ON id=station1 OR id=station2") or die(mysql_error());
while($array = mysql_fetch_assoc($query_handle))
{
  extract($array);

  $xpos *= $scale;// $xpos += 50;
  $ypos *= $scale;// $ypos += 50;
  $ypos = $height - $ypos;

  $stations[$id] = array('xpos' => $xpos, 'ypos' => $ypos, 'name' => $name, 'display_name' => $display_name, 'total_lines' => $total_lines, 'rail' => $rail, 'line' => $line);
}

// Load lines/routes into memory
$lines = array();

$query_handle = mysql_query("
  SELECT * FROM routes") or die(mysql_error());
while($array = mysql_fetch_assoc($query_handle))
{
  extract($array);
  $lines[$line] = array('name' => $name, 'RR' => hexdec(substr($colour, 0, 2)), 'GG' => hexdec(substr($colour, 2, 2)), 'BB' => hexdec(substr($colour, 4, 2)), 'stripe' => $stripe, 'colour' => $colour);
}

//$query_handle = mysql_query("SELECT * FROM links WHERE line=$this_line") or die(mysql_error());

// Generate the line/route paths
$tangents = array();

$output_lines = '';

$output_stripes = '';

if($this_line == 11 or $this_zone)
{
  $this_line = 11;
  $route = find_path(11, 35, 274); //Victoria Line
  draw_path();
}
if($this_line == 1 or $this_zone)
{
  $this_line = 1;
  $route = find_path(1, 114, 84); //Bakerloo Line
  draw_path();
}
if($this_line == 3 or $this_zone)
{
  $this_line = 3;
  $route = find_path(3, 25, 25); //Circle Line
  draw_path();
}
if($this_line == 7 or $this_zone)
{
  $this_line = 7;
  $route = find_path(7, 243, 247); //Jubille Line
  draw_path();
}
if($this_line == 6 or $this_zone)
{
  $this_line = 6;
  $route = find_path(6, 15, 110); //Hammersmith & City Line
  draw_path();
}
if($this_line == 12 or $this_zone)
{
  $this_line = 12;
  $route = array(279, 13); //Waterloo & City Line
  draw_path();
}



/* Picadilly Line */
if($this_line == 10 or $this_zone)
{
  $this_line = 10;
  $route = find_path(10, 1, 57, array(73, 234)); //Acton Town to Cockfosters, not via Ealing Common or South Ealing
  draw_path();
  $route = find_path(10, 271, 1); //Uxbride to Acton Town
  array_push($route, 265); //Following on to Turnham Green
  draw_path(true);
  $route = find_path(10, 116, 1, array(117, 118)); //Hatton Cross to Acton Town
  array_push($route, 265); //Following on to Turnham Green
  draw_path(true);
  $route = find_path(10, 116, 116, array(132)); //Hatton Cross to Hatton Cross (Heathrow loop) not via Hounslow West
  array_push($route, 132); //Following on to Hounslow West
  draw_path(true);
}

/* Northern Line */
if($this_line == 9 or $this_zone)
{
  $this_line = 9;
  $route = find_path(9, 169, 121, array(165, 279, 170, 47)); //Morden to High Barnet not via Mill Hill East, Waterloo, Mornington Crescent or Chalk Farm
  draw_path();
  $route = find_path(9, 81, 40, array(139)); //Edgware to Camden Town not via Kentish Town
  array_push($route, 170); //...to Mornington Crescent
  array_push($route, 89); //...to Euston
  $route = find_path(9, 277, 136, array(84), 89, $route); //...to Warren Street to Kennington not via Elephant & Catle
  array_push($route, 191); //Following on to Oval
  draw_path(true);
  $route = array(165, 93, 77); //Mill Hill East to Finchley Central, following on to East Finchley
  draw_path(true);
}

/* East London Line */
if($this_line == 5 or $this_zone)
{
  $this_line = 5;
  $route = find_path(5, 175, 228, array(174)); //New Cross Gate to Shoreditch not via New Cross
  draw_path();
  $route = array(174, 253, 41); //New Cross Gate to Shoreditch onto Canada Water not via New Cross
  draw_path(true);
}

/* Central Line */
if($this_line == 2 or $this_zone)
{
  $this_line = 2;
  $route = find_path(2, 294, 88, array(286, 275, 215)); //West Ruislip to Epping not via West Acton, Wanstead or Roding Valley
  draw_path();
  $route = find_path(2, 72, 76, array(112)); //Ealing Broadway to East Acton (following on) not via Hanger Lane
  draw_path(true);
  $route = find_path(2, 301, 153, array(37, 241, 230)); //Woodford to Leyton (following on) not via Buckhurst Hill, South Woodford or Snaresbrook
  draw_path(true);
}

/* District Line */
if($this_line == 4 or $this_zone)
{
  $this_line = 4;
  $route = find_path(4, 72, 267, array(108, 287, 138, 122)); //Ealing Broadway to Upminster not via Gunnersbury, West Brompton, Kensington or High Street Kensington
  draw_path();
  $route = find_path(4, 213, 265, array(52)); //Richmond to Turnham Green not via Chiswick Park
  draw_path();
  $route = find_path(4, 83, 299, array(138, 293, 99)); //Edgware Road to Wimbledon not via Kensington, West Kensington or Gloucester Road
  draw_path();
  $route = array(138, 74); //Kensignton to Earl's Court
  draw_path();
}

/* Metropolitan Line */
if($this_line == 8 or $this_zone)
{
  $this_line = 8;
  $route = find_path(8, 271, 178, array(184)); //Uxbridge to Northwick Park (following on) not via North Harrow
  draw_path(true);
  $route = array(280, 62, 168, 179); //Watford to Croxley to Moor Park following on to Northwood
  draw_path(true);
  $route = array(50, 46); //Chesham to Chalfront & Latimer
  draw_path();
  $route = find_path(8, 6, 282, array(50, 62, 291)); //Amersham to Wembley Park not via Chesham, Croxley or West Harrow
  $route = find_path(7, 172, 290, array(), 282, $route); //Neasden to West Hampstead on Jubilee line, for path following
  $route = find_path(8, 94, 2, array(), 290, $route); //Finchley Road to Aldgate
  draw_path();
}

/* DLR */
if($this_line == 13 or $this_zone)
{
  $this_line = 13;
  $route = find_path(13, 13, 19, array(4, 292, 304)); //Bank to Beckton not via All Saints or West India Quay or West Silvertown
  draw_path();
  $route = find_path(13, 152, 155, array(201)); //Lewisham to Limehouse (following on) not via Poplar
  draw_path(true);
  $route = find_path(13, 247, 42, array(284, 27)); //Stratford to Canary Wharf (following on) not via Westferry or Blackwall
  draw_path(true);
  $route = array(262, 225); //Tower Gateway to Shadwell
  draw_path();
  $route = array(307, 306, 305, 304, 43, 79); //King George V to Canning Town (following on)
  draw_path(true);
}

$output_stations = '';
foreach($stations as $i => $station)
{
  extract($station);
  if(isset($tangents[$i]))
  {
    if($total_lines > 1 or $rail)
    {
      $output_stations .= plot_intersection($xpos, $ypos, $name);
    }
    else
    {
      $this_station = plot_station($xpos, $ypos, $i, $lines[$line]);
      $output_lines .= $this_station[0];
      $output_stripes .= $this_station[1];
    }
    $output_stations .= plot_stationname($xpos, $ypos, $name, $display_name, $rail);
  }
}

$output = $header.'
  <g id="lines">'.$output_lines.'
  </g>';

if($stripe)
  $output .= '
  <g id="stripes">'.$output_stripes.'
  </g>';

$output .= '
  <g id="stations">'.$output_stations.'
  </g>';

$output .= '
</svg>';

echo $output;

$file = fopen($this_line_name.'.svg', 'w+');
fwrite($file, $output);
fclose($file);

mysql_close($dbserverlink);


# --------------------------

function draw_path($follow_on = false)
{
  global $output_lines, $output_stripes, $tangents, $stations, $route, $this_line, $lines;

  $control_points = array();

  for($i=0; $i<count($route); $i++)
  {
    if(!$i)
    {
      $dx = ($stations[$route[$i+1]]['xpos'] - $stations[$route[$i]]['xpos']);
      $dy = ($stations[$route[$i+1]]['ypos'] - $stations[$route[$i]]['ypos']);
      $cx = $stations[$route[$i]]['xpos'] + ($dx/3);
      $cy = $stations[$route[$i]]['ypos'] + ($dy/3);
      $tangents[$route[$i]] = $dy/$dx;
      array_push($control_points, array('x' => $stations[$route[$i]]['xpos'], 'y' => $stations[$route[$i]]['ypos'])); #First station
      array_push($control_points, array('x' => $cx, 'y' => $cy)); #First control point
    }
    else if($i+1 == count($route))
    {
      $dx = ($stations[$route[$i]]['xpos'] - $stations[$route[$i-1]]['xpos']);
      $dy = ($stations[$route[$i]]['ypos'] - $stations[$route[$i-1]]['ypos']);
      $cx = $stations[$route[$i]]['xpos'] - ($dx/3);
      $cy = $stations[$route[$i]]['ypos'] - ($dy/3);
      array_push($control_points, array('x' => $cx, 'y' => $cy)); #Last control point
      array_push($control_points, array('x' => $stations[$route[$i]]['xpos'], 'y' => $stations[$route[$i]]['ypos'])); #Last station
      $tangents[$route[$i]] = $dy/$dx;
    }
    else
    {
      $dx = ($stations[$route[$i+1]]['xpos'] - $stations[$route[$i-1]]['xpos']);
      $dy = ($stations[$route[$i+1]]['ypos'] - $stations[$route[$i-1]]['ypos']);
  
      $cx = $stations[$route[$i]]['xpos'] - ($dx/6);
      $cy = $stations[$route[$i]]['ypos'] - ($dy/6);
      array_push($control_points, array('x' => $cx, 'y' => $cy)); #Pre-station control point
  
      array_push($control_points, array('x' => $stations[$route[$i]]['xpos'], 'y' => $stations[$route[$i]]['ypos'])); #Station
      $tangents[$route[$i]] = $dy/$dx;
  
      $cx = $stations[$route[$i]]['xpos'] + ($dx/6);
      $cy = $stations[$route[$i]]['ypos'] + ($dy/6);
      array_push($control_points, array('x' => $cx, 'y' => $cy)); #Post-station control point
    }
  }

  extract($lines[$this_line]);

  for($i=0; $i<count($control_points)-3; $i+=3)
  {
    if($follow_on and $i == count($control_points)-4)
      break;
    extract($control_points[$i], EXTR_PREFIX_ALL, 'st0');
    extract($control_points[$i+1], EXTR_PREFIX_ALL, 'st1');
    extract($control_points[$i+2], EXTR_PREFIX_ALL, 'st2');
    extract($control_points[$i+3], EXTR_PREFIX_ALL, 'st3');
  
    $end_line = false;
    if(!$i or $i == count($control_points)-4)
      $end_line = true;
  
    $output_lines .= '
    <path
      d="M '.$st0_x.' '.$st0_y.' C '.$st1_x.' '.$st1_y.' '.$st2_x.' '.$st2_y.' '.$st3_x.' '.$st3_y.'"
      style="stroke-width: 4.5; stroke: #'.$colour.'; fill: none;'.(!$end_line ? ' stroke-linecap: round;' : '').'" />';

    if($stripe)
    {
      $output_stripes .= '
    <path
      d="M '.$st0_x.' '.$st0_y.' C '.$st1_x.' '.$st1_y.' '.$st2_x.' '.$st2_y.' '.$st3_x.' '.$st3_y.'"
      style="stroke-width: 1.5; stroke: #'.$stripe.'; fill: none;'.(!$end_line ? ' stroke-linecap: round;' : '').'" />';
    }

  }
}

function find_path($line, $start, $end, $not_via = array(), $last_station = 0, $array = array(), $i=0)
{
  array_push($array, $start);

  $not_via_query = '';
  if(count($not_via))
  {
    foreach($not_via as $not_via_station)
    {
      $not_via_query .= " AND (station1 <> $not_via_station AND station2 <> $not_via_station)";
    }
  }

  $next_station = mysql_result(mysql_query("
    SELECT (station1 + station2 - $start) AS next_station
    FROM links
    WHERE (station1 = $start OR station2 = $start)
          AND (station1 <> $last_station AND station2 <> $last_station)
          $not_via_query
          AND line = $line"), 0);
  if($next_station <> $end)
  {
    return find_path($line, $next_station, $end, $not_via, $start, $array, $i+1);
  }
  else
  {
    array_push($array, $end);
    return $array;
  }
}

function plot_intersection($xpos, $ypos, $name)
{
  $name = htmlentities($name, ENT_QUOTES);
  $output = '
    <use id="'.$name.'" x="'.$xpos.'" y="'.$ypos.'" xlink:href="#intersection" />';
  return $output;
}


function plot_station($xpos, $ypos, $i, $line)
{
  global $tangents;

  $output_stripes = '';
  extract($line);
  $ds = sqrt(1+$tangents[$i]*$tangents[$i]);
  $dy = -1/$ds;
  $dx = $tangents[$i]/$ds;

  $output_lines = '
    <line 
      x1="'.$xpos.'" y1="'.$ypos.'" x2="'.($xpos+(5*$dx)).'" y2="'.($ypos+(5*$dy)).'"
      style="stroke-width: 4.5; stroke: #'.$colour.'; stroke-linecap: square;" />
    <line 
      x1="'.$xpos.'" y1="'.$ypos.'" x2="'.($xpos-(5*$dx)).'" y2="'.($ypos-(5*$dy)).'"
      style="stroke-width: 4.5; stroke: #'.$colour.'; stroke-linecap: square;" />';

  if($stripe)
  {
    $output_stripes = '
    <line 
      x1="'.$xpos.'" y1="'.$ypos.'" x2="'.($xpos+(5*$dx)).'" y2="'.($ypos+(5*$dy)).'"
      style="stroke-width: 1.5; stroke: #'.$stripe.'; stroke-linecap: square;" />
    <line 
      x1="'.$xpos.'" y1="'.$ypos.'" x2="'.($xpos-(5*$dx)).'" y2="'.($ypos-(5*$dy)).'"
      style="stroke-width: 1.5; stroke: #'.$stripe.'; stroke-linecap: square;" />';
#    $dy = $tangents[$i]/$ds;
#    $dx = 1/$ds;
#    $output .= '
#    <line 
#      x1="'.($xpos-(2.25*$dx)).'" y1="'.($ypos-(2.25*$dy)).'" x2="'.($xpos+(2.25*$dx)).'" y2="'.($ypos+(2.25*$dy)).'"
#      style="stroke-width: 1.5; stroke: #'.$stripe.'; stroke-linecap: square;" />';
  }

  return array($output_lines, $output_stripes);
}

function plot_stationname($xpos, $ypos, $name, $display_name, $rail)
{
  $name = htmlentities($name, ENT_QUOTES);
  $lines = split('<br />', ($display_name ? $display_name : $name));
  $output = '';
  $output .= '
    <text x="'.($xpos+7).'" y="'.$ypos.'" style="font-family: Verdana; font-size: 11px;">';
  foreach($lines as $line)
  {
    $output .= '
      <tspan x="'.($xpos+7).'" y="'.($ypos+4).'">'.htmlentities($line, ENT_QUOTES).'</tspan>';
    $ypos += 12;
  }
  $output .= '
    </text>';

  if($rail)
    $output .= '
    <use x="'.($xpos+40).'" y="'.($ypos-16).'" xlink:href="#rail" />';

  return $output;
}
?>