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
The second screenshot shows the taxi arriving at another destination - Leicester Square.

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?