Knowledge

Taxi navigation in the West End

Introduction

Peter has continued to use his ingenuity and determination to develop yet another highly impressive, original product, now available as web versions both with and without the code to help you keep the taxi on the road. Program Knowledge provides you with a destination chosen at random from more than 30 streets and 32 places of interest in the West End of London. An arrow in the top left of the screen indicates the direction to your destination as the crow flies. If the destination is a street, the arrow points you to the nearest junction on that street. Use the mouse to guide your taxi (represented by a small blue circle) to the destination. The arrow updates continuously to help you. (Of course, if you do not want to go to the chosen destination you can ignore it and just explore the West End!) If the taxi is heading off the road the code will detect the colour change and nudge it back on.

The width of the window is 500 pixels and each map square is 300 x 300 pixels. At each update of the screen the required bitmaps are loaded. (The bitmap file type is especially well-named for its use here!) The taxi is always drawn in the centre of the window.

The first screenshot shows the taxi on its way to Tottenham Court Road Station.

Taxi navigation in action

Taxi navigation in action

The second screenshot shows the taxi arriving at another destination - Leicester Square.

Destination reached

Destination reached

As soon as you click in the required area you are provided with another destination more than a certain minimum distance away.

The following extract is from a summary of some research by the Wellcome Department of Imaging Neuroscience.

“Dr Eleanor Maguire scanned the brains of 16 London black-cab drivers, who had spent an average of two years learning 'the Knowledge' – street names and routes in London. The taxi drivers had a larger right hippocampus than control subjects, and the longer they had been on the job, the larger their hippocampus was”.

This program is not guaranteed to increase the size of your right hippocampus but is likely to improve your knowledge of the West End. We urge you to download and extract the required zip files and try it. Over 70 map squares produced painstakingly by Peter are compressed in maps.zip which you can download here. Put the unzipped maps folder in the program folder. You will need to have downloaded also Stefan Berinde`s wingraph.zip file as described in our Graphics tutorial. You should copy the unzipped wincrt.pas, wingraph.pas and winmouse.pas (from the src folder) into your program folder. If this program gives a SIGSEGV error message when you attempt to compile and run it using Lazarus, it might have produced the required executable file. If so, open the program folder and double click on Knowledge.exe to execute the program.

Peter uses parallel arrays to store the street names and the corresponding X and Y coordinates of the junctions, so the name of the street occurs once for each of these junctions. The random choice of index for a street destination will favour quite reasonably the streets with most junctions.

The Program

program Knowledge;
{
    Copyright (c) 2011 Peter Hearnshaw
    
    Licensed under the Apache License, Version 2.0 (the "License"); you may not 
    use this file except in compliance with the License, as described at
    http://www.apache.org/licenses/ and http://www.pp4s.co.uk/licenses/
}
  {$mode objfpc}{$H+}
uses
  Classes, SysUtils, strutils, wingraph, wincrt, winmouse;
var
  shiftx, shifty: real;
  gm, gd : smallInt;
  release : boolean;
  currentDestination, destinationType : string;
  me : MouseEventType;
  i, a, degr, distance, currentMinDistance, indexMinDistance : integer;
  StreetsArr : array [1..140] of string = ('Welbeck Street','Welbeck Street','Welbeck Street','Welbeck Street','Welbeck Street','Welbeck Street','Welbeck Street','Welbeck Street',
    'Wigmore Place','Little Argyll Street','Little Argyll Street','Ganton Street','Bridle Lane','Bridle Lane','Denman Street','Denman Street',
    'New Bond Street','New Bond Street', 'New Bond Street','New Bond Street','New Bond Street','New Bond Street','New Bond Street','New Bond Street','New Bond Street','New Bond Street',
    'Cork Street','Cork Street','Berkeley Street','Berkeley Street','Berkeley Street','Berkeley Street','Berkeley Street',
    'Pall Mall','Pall Mall','Pall Mall','Pall Mall','Pall Mall','Pall Mall','Pall Mall','Sackville Street','Sackville Street','Vigo Street','Vigo Street','Vigo Street','Haymarket','Haymarket',
    'Haymarket','Haymarket','Haymarket','Haymarket','Haymarket','Haymarket','Haymarket','Duncannon Street','Duncannon Street',
    'Jermyn Street','Jermyn Street','Jermyn Street','Jermyn Street','Jermyn Street','Jermyn Street','Jermyn Street','Jermyn Street','Jermyn Street',
    'Northumberland Avenue','Northumberland Avenue','Northumberland Avenue','Northumberland Avenue','Northumberland Avenue','Northumberland Avenue','Northumberland Avenue','Northumberland Avenue',
    'Bow Street','Bow Street','Bow Street','Langley Street','Langley Street','Morwell Street','Morwell Street','Betterton Street','Betterton Street','Swallow Street',
    'Air Street','Air Street','Air Street','Great Windmill Street','Great Windmill Street','Great Windmill Street',
    'Wardour Street','Wardour Street','Wardour Street','Wardour Street','Wardour Street','Wardour Street','Wardour Street','Wardour Street','Wardour Street','Wardour Street','Wardour Street',
    'Park Lane','Park Lane','Park Lane','Park Lane','Adams Row','Adams Row','Weighhouse Street','Weighhouse Street','Weighhouse Street','Weighhouse Street',
    'Hertford Street','Hertford Street','Hertford Street','Hertford Street','North Audley Street','North Audley Street','North Audley Street',
    'Tottenham Court Road','Tottenham Court Road','Tottenham Court Road','Tottenham Court Road','Tottenham Court Road','Tottenham Court Road','Tottenham Court Road',
    'Goodge Street','Goodge Street','Goodge Street','Goodge Street','Portland Place','Portland Place',
    'Eastcastle Street','Eastcastle Street','Eastcastle Street','Eastcastle Street','Eastcastle Street','Eastcastle Street','Eastcastle Street','Eastcastle Street','Eastcastle Street','Eastcastle Street');
  StreetXArr : array [1..140] of integer = (544,519,498,487,468,459,443,413,695,1206,1256,1500,1633,1707,1783,1855,708,722,753,811,838,910,926,1022,1032,1080,1164,1227,932,956,1019,1084,1128,2139,2054,
    2005,1906,1893,1761,1552,1440,1540,1464,1433,1376,1966,1977,1989,2034,2046,2084,2096,2142,2156,2557,2635,1357,1462,1519,1667,1779,1836,1895,1937,1973,2548,2695,2733,2848,2880,2900,2942,2939,2926,2966,
    3067,2680,2733,2117,2192,2873,2786,1589,1703,1661,1677,1827,1868,1884,2021,1984,1979,1944,1922,1842,1803,1782,1753,1775,1730,59,208,224,272,250,418,339,371,412,478,622,527,491,419,70,111,126,2227,2204,
    2187,2162,2087,2061,2027,1583,1553,1475,1442,964,1022,1169,1210,1242,1336,1380,1549,1602,1654,1709,1738);
  StreetYArr : array [1..140] of integer = (540,459,391,325,288,230,190,58,346,702,682,878,1018,1138,1266,1257,710,766,773,895,894,1033,1031,1201,1195,1277,1271,1385,1507,1536,1641,1740,1783,1736,1781,
    1814,1865,1884,1949,2073,1358,1527,1325,1338,1351,1384,1383,1424,1496,1480,1546,1609,1661,1697,1582,1581,1767,1709,1674,1602,1544,1505,1477,1459,1431,1773,1811,1832,1850,1887,1853,1882,1900,819,853,
    953,864,926,100,208,600,680,1499,1431,1342,1371,1114,1161,1221,1086,1038,1002,995,937,767,654,645,588,576,508,1683,1879,1895,1955,1366,1312,879,868,855,837,1995,2033,2044,2081,882,988,1069,387,338,311,
    261,160,93,71,68,96,125,156,177,193,480,471,459,427,419,372,358,343,324,323);

  placesArr : array [1..32] of string = ('Hanover Square','John Lewis', 'Royal College of Nursing', 'Cavendish Square', 'All Souls Church', 'Tottenham Court Road Station', 'Dominion Theatre',
    'British Museum', 'Holborn Station', 'Seven Dials', 'Royal Opera House', 'Cleopatra''s Needle', 'Charing Cross Station', 'Trafalgar Sq', 'Leicester Square', 'The Trocadero', 'Piccadilly Circus',
    'London Waterstones', 'St James Square Gardens', 'The Ritz', 'Burlington Arcade', 'Green Park Station', 'Edge of Hyde Park', 'Grosvenor Square', 'Selfridges', 'Bond Street Station', 'Oxford Circus',
    'YHA Oxford Street', 'Covent Gardens Tube Station', 'Foyles Bookshop', 'Soho Square', 'Dominion Theatre');
  placesXArr : array [1..32] of integer = (979, 855,769, 875, 1028, 2208, 2249, 2530, 3140, 2550, 2982, 3082, 2722, 2446, 2262, 1945, 1894, 1725, 1769, 1199, 1281, 1070, 84, 272, 140, 446, 1149, 1624, 2753, 2267, 2059, 2255);
  placesYArr : array [1..32] of integer = (788, 624, 476, 412, 154, 448, 382, 70, 278, 811, 899, 1646, 1613, 1665, 1306, 1296, 1399, 1515, 1802, 1822, 1530, 1860, 2021, 1149, 729, 757, 592, 664, 852, 707, 578, 384);
  placesRadiiArr : array [1..32] of integer = (85, 50, 70, 100, 70, 50, 50, 150, 65, 50, 70, 65, 80, 150, 75, 70, 100, 65, 100, 70, 50, 50, 185, 150, 75, 55, 60, 60, 90, 50, 90, 50);

procedure LoadBMP(x1 : integer; y1 : integer);
var
  f : file;
  bitmap : pointer;
  size : longint;
  imgName, strImageNum : string;
  xi, yi, blockX, blockY, intImageNum : integer;
  fillShape : Array[1..5] Of PointType;

begin
  blockX := 1 + (-x1 div 300);
  blockY := 1 + (-y1 div 300);
  for xi := 0 to 11 do
    begin
      for yi := 0 to 8 do
        begin
          if(blockX + 1 >= xi)and(blockX-1 <= xi)and(blockY + 1 >= yi)and(blockY - 1 <= yi)then
            begin
              strImageNum := inttostr(xi) + inttostr(yi);
              intImageNum := strToInt(strImageNum);
              if intImageNum in [0 ..8, 10, 18, 20, 28, 30, 38, 40, 48, 50, 58, 60, 68, 70, 78, 80, 88, 90, 98, 100, 108, 110, 118] then
                imgName := 'maps\00.bmp'
              else
                imgName := 'maps\'+ strImageNum + '.bmp';
              {$I-} Assign(f,imgName);
              Reset(f, 1); {$I+}
              if (IOResult <> 0) then
                Exit;
              size := FileSize(f);
              GetMem(bitmap, size);
              BlockRead(f, bitmap^, size);
              Close(f);
              PutImage(x1 + xi * 300, y1 + yi * 300, bitmap^, NormalPut);
              FreeMem(bitmap);
            end;
        end;
    end;
  setcolor(darkblue);
  SetLineStyle(SolidLn, UserBitLn, normwidth);
  circle(250, 250, 5);
  SetFillStyle(SolidFill, black);
  Bar(0, 0, 100, 100);
  Bar(100, 0, 500, 20);
  setcolor(bronze);
  circle(50, 50, 39);
  setcolor(lime);
  SetLineStyle(SolidLn, UserBitLn, doublewidth);
  //Draw line of arrow
  line(50, 50, 50 + round(28 * cos(0.017453 * (degr + 90 + 180))), 50 + round(28 * sin(0.017453 * (degr + 90 + 180))));
  SetFillStyle(Solidfill, lime);
  setLineStyle(NullLn, UserBitLn, NormWidth);
  //Draw head of arrow
  fillShape[1].X := 50 + round(26 * cos(0.017453 * (degr + 90 + 180)));
  fillShape[1].Y := 50 + round(26 * sin(0.017453 * (degr + 90 + 180)));
  fillShape[2].X := 50 + round(26 * cos(0.017453 * (degr + 20 + 90 + 180)));
  fillShape[2].Y := 50 + round(26 * sin(0.017453 * (degr + 20 + 90 + 180)));
  fillShape[3].X := 50 + round(35 * cos(0.017453 * (degr + 90 + 180)));
  fillShape[3].Y := 50 + round(35 * sin(0.017453 * (degr + 90 + 180)));
  fillShape[4].X := 50 + round(26 * cos(0.017453 * (degr - 20 + 90 + 180)));
  fillShape[4].Y := 50 + round(26 * sin(0.017453 * (degr - 20 + 90 + 180)));
  fillShape[5].X := 50 + round(26 * cos(0.017453 * (degr + 90 + 180)));
  fillShape[5].Y := 50 + round(26 * sin(0.017453 * (degr + 90 + 180)));
  FillPoly(5, fillShape);
  setcolor(white);
  outTextXY(105, 5, 'Destination: ' + currentDestination);
  updategraph(updateNow);
end;

procedure CheckKeyboard;
var
  c:char;
begin
  if not(KeyPressed) then Exit;
  c := ReadKey;
  case c of
    #75 : begin //Left key
            shiftx := shiftx + 30;
          end;
    #77 : begin //Right key
            shiftx := shiftx - 30;
          end;
    #72 : begin //Up key
            shifty := shifty + 30;
          end;
    #80 : begin //Down key
           shifty := shifty - 30;
          end;
  end;
  if (shiftx > 2670) then
    shiftx := 2670;
  if (shifty > 1530) then
    shifty := 1530;
  if (shiftx < 0) then
    shiftx := 0;
  if (shifty < 0) then
    shifty := 0;
end;

procedure checkMouse;
var
  degr, distanceAway : integer;
begin
  if not((getmousex - 250) = 0) then
    degr := 90 + round((arctan((getmousey - 250) / (getmousex - 250))) / 3.141 * 180);
  if (250 - getmousex > 0) then
    degr := degr + 180;

  distanceAway := round(0.02 * sqrt((getmousey - 250) * (getmousey - 250) + (getmousex - 250) * (getmousex - 250)));
  if (distanceAway > 2) then
    distanceAway := 2;

  shiftx := shiftx + (distanceAway * sin(-(degr + 180) / 180 * 3.141));
  shifty := shifty + (distanceAway * cos(-(degr + 180) / 180 * 3.141));

  if (shiftx >= 3216) then
    begin
      shiftx := 3216;
    end;
  if (shifty >= 2081) then
    begin
      shifty := 2081;
    end;
  if (shiftx <= 58) then
    begin
      shiftx := 58;
    end;
  if (shifty <= 58) then
    begin
      shifty := 58;
    end;

  if (getpixel(250, 247) = 12250361) or (getpixel(250, 247) = 12447743) then  //I have two colour codes here for variation from different computers. If the car can go out of the road then your computer must use a different code for the background colour.
    begin
      shifty := shifty + 2;
    end;
  if (getpixel(250, 253) = 12250361) or (getpixel(250, 253) = 12447743) then
    begin
      shifty := shifty - 2;
    end;
  if (getpixel(247, 250) = 12250361) or (getpixel(247, 250) = 12447743) then
    begin
      shiftx := shiftx + 2;
    end;
  if (getpixel(253, 250) = 12250361) or (getpixel(253, 250) = 12447743) then
    begin
      shiftx := shiftx - 2;
    end;
end;

procedure giveMeADestination;
var
  chooseAnotherDestination : boolean = false;
begin
  i := random(2);
  if (i = 0) then
    begin   //choose a street
      repeat
        a := random(139) + 1;
        destinationType := 'street';
        currentDestination := StreetsArr[a];
        chooseAnotherDestination := false;
        for a := 1 to 140 do
          begin
            if (StreetsArr[a] = currentDestination) then
              begin
                distance := round(sqrt((StreetXArr[a] - shiftx) * (StreetXArr[a] - shiftx) + (StreetYArr[a] - shifty) * (StreetYArr[a] - shifty)));
                if (distance < 500) then
                  begin
                    chooseAnotherDestination := true;
                  end;
              end;
          end;
      until chooseAnotherDestination = false;
    end
  else
    begin   //choose a place
      repeat
        a := random(31) + 1;
        destinationType := 'place';
        currentDestination := PlacesArr[a];
        chooseAnotherDestination := false;
        distance := round(sqrt((PlacesXArr[a] - shiftx) * (PlacesXArr[a] - shiftx) + (PlacesYArr[a] - shifty) * (PlacesYArr[a] - shifty)));
        if (distance < 500) then
          begin
            chooseAnotherDestination := true;
          end;
      until chooseAnotherDestination = false;
   end;
end;

procedure reachedDestinationDialogBox;
begin
  SetFillStyle(SolidFill, black);
  Bar(100, 150, 400, 250);
  setcolor(white);
  outTextXY(112, 155, 'You have reached your destination!');
  outTextXY(120, 175, 'Click below for a new passenger');
  SetFillStyle(SolidFill, white);
  bar(200, 200, 300, 230);
  setcolor(black);
  outTextXY(210, 205, 'Click Here');
  updategraph(updatenow);
  release := false;
  repeat
    PollMouseEvent(me);
    GetMouseEvent(me);
    with me do
      case action of
        MouseActionDown: begin //mouse button pressed
                           if (getMouseX > 200) and (getMouseX < 300) and (getMouseY > 200) and (getMouseY < 230)then
                             begin
                               giveMeADestination;
                               release := true;
                             end;
                         end;
      end;
  until release = true;
end;

begin
  randomize;
  SetWindowSize(500, 500);
  gd := 9; gm := 13;
  InitGraph(gd, gm, 'West End');
  shiftx := 300;
  shifty := 300;
  giveMeADestination;
  updategraph(updateOff);
  repeat
    sleep(3);
    CheckKeyboard;
    checkMouse;
    LoadBMP(round(-shiftx), round(-shifty));
    setcolor(blue);
    circle(250, 250, 15);
    if (destinationType = 'street') then
      begin
        currentMinDistance := 10000;
        indexMinDistance := 0;
        for a := 1 to 140 do
          begin
            if (StreetsArr[a] = currentDestination) then
              begin
                distance := round(sqrt((StreetXArr[a] - shiftx) * (StreetXArr[a] - shiftx) + (StreetYArr[a] - shifty) * (StreetYArr[a] - shifty)));
                if (distance < currentMinDistance) then
                  begin
                    indexMinDistance := a;
                    currentMinDistance := distance;
                  end;
              end;
          end;
          //indexMinDistance now contains the index of the closest vertex on the current destination street
          degr := round(57.2957795 * arctan((StreetXArr[indexMinDistance] - shiftx) / (shifty - StreetYArr[indexMinDistance])));
          if (degr < 0 ) then
              degr := degr + 180;
          if (StreetXArr[indexMinDistance] < shiftx) then //place is to the right of taxi
              degr := degr + 180;

          if (StreetXArr[indexMinDistance] + 10 > shiftx) and (StreetXArr[indexMinDistance] - 10 < shiftx) and (StreetYArr[indexMinDistance] + 10 > shifty) and (StreetYArr[indexMinDistance] - 10 < shifty) then
            begin
              reachedDestinationDialogBox;
            end;
      end
    else
      begin
        for a := 1 to 32 do
          begin
            if (PlacesArr[a] = currentDestination) then
              begin
                degr := round(57.2957795 * arctan((placesXArr[a] - shiftx) / (shifty - placesYArr[a])));
                if (degr < 0) then
                  degr := degr + 180;
                if (placesXArr[a] < shiftx) then //place is to the right of taxi
                  degr := degr + 180;
                if (placesXArr[a] + (placesRadiiArr[a] / 2) > shiftx) and (placesXArr[a] - (placesRadiiArr[a] / 2) < shiftx) and (placesYArr[a] + (placesRadiiArr[a]/2) > shifty) and (placesYArr[a] - (placesRadiiArr[a]/2) < shifty) then
                  begin
                    reachedDestinationDialogBox;
                  end;
              end;
          end;
       end;
  until 1 = 2;
end.

Remarks

Could you develop this program, for example by allowing the user to select a destination? Could you use Peter's map squares in a program of your own?

Programming - a skill for life!

Fourteen programs (with five web versions) including 3D-Driving, GASP and Knowledge by Peter Hearnshaw