Imported Upstream version 1.4.1
[routino] / web / www / routino / router.js
1 //
2 // Routino router web page Javascript
3 //
4 // Part of the Routino routing software.
5 //
6 // This file Copyright 2008-2010 Andrew M. Bishop
7 //
8 // This program is free software: you can redistribute it and/or modify
9 // it under the terms of the GNU Affero General Public License as published by
10 // the Free Software Foundation, either version 3 of the License, or
11 // (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 // GNU Affero General Public License for more details.
17 //
18 // You should have received a copy of the GNU Affero General Public License
19 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 //
21
22 ////////////////////////////////////////////////////////////////////////////////
23 /////////////////////////// Routino default profile ////////////////////////////
24 ////////////////////////////////////////////////////////////////////////////////
25
26 var routino={ // contains all default Routino options (generated using "--help-profile-js").
27
28   // Default transport type
29   transport: 'motorcar',
30
31   // Transport types
32   transports: {foot: 1, horse: 2, wheelchair: 3, bicycle: 4, moped: 5, motorbike: 6, motorcar: 7, goods: 8, hgv: 9, psv: 10},
33
34   // Highway types
35   highways: {motorway: 1, trunk: 2, primary: 3, secondary: 4, tertiary: 5, unclassified: 6, residential: 7, service: 8, track: 9, cycleway: 10, path: 11, steps: 12},
36
37   // Property types
38   properties: {paved: 1, multilane: 2, bridge: 3, tunnel: 4},
39
40   // Restriction types
41   restrictions: {oneway: 1, weight: 2, height: 3, width: 4, length: 5},
42
43   // Allowed highways
44   profile_highway: {
45         motorway: {foot:   0, horse:   0, wheelchair:   0, bicycle:   0, moped:   0, motorbike: 100, motorcar: 100, goods: 100, hgv: 100, psv: 100},
46            trunk: {foot:  40, horse:  25, wheelchair:  40, bicycle:  30, moped:  90, motorbike: 100, motorcar: 100, goods: 100, hgv: 100, psv: 100},
47          primary: {foot:  50, horse:  50, wheelchair:  50, bicycle:  70, moped: 100, motorbike:  90, motorcar:  90, goods:  90, hgv:  90, psv:  90},
48        secondary: {foot:  60, horse:  50, wheelchair:  60, bicycle:  80, moped:  90, motorbike:  80, motorcar:  80, goods:  80, hgv:  80, psv:  80},
49         tertiary: {foot:  70, horse:  75, wheelchair:  70, bicycle:  90, moped:  80, motorbike:  70, motorcar:  70, goods:  70, hgv:  70, psv:  70},
50     unclassified: {foot:  80, horse:  75, wheelchair:  80, bicycle:  90, moped:  70, motorbike:  60, motorcar:  60, goods:  60, hgv:  60, psv:  60},
51      residential: {foot:  90, horse:  75, wheelchair:  90, bicycle:  90, moped:  60, motorbike:  50, motorcar:  50, goods:  50, hgv:  50, psv:  50},
52          service: {foot:  90, horse:  75, wheelchair:  90, bicycle:  90, moped:  80, motorbike:  80, motorcar:  80, goods:  80, hgv:  80, psv:  80},
53            track: {foot:  95, horse: 100, wheelchair:  95, bicycle:  90, moped:   0, motorbike:   0, motorcar:   0, goods:   0, hgv:   0, psv:   0},
54         cycleway: {foot:  95, horse:  90, wheelchair:  95, bicycle: 100, moped:   0, motorbike:   0, motorcar:   0, goods:   0, hgv:   0, psv:   0},
55             path: {foot: 100, horse: 100, wheelchair: 100, bicycle:  90, moped:   0, motorbike:   0, motorcar:   0, goods:   0, hgv:   0, psv:   0},
56            steps: {foot:  80, horse:   0, wheelchair:   0, bicycle:   0, moped:   0, motorbike:   0, motorcar:   0, goods:   0, hgv:   0, psv:   0}
57      },
58
59   // Speed limits
60   profile_speed: {
61         motorway: {foot:   0, horse:   0, wheelchair:   0, bicycle:   0, moped:  48, motorbike: 112, motorcar: 112, goods:  96, hgv:  89, psv:  89},
62            trunk: {foot:   4, horse:   8, wheelchair:   4, bicycle:  20, moped:  48, motorbike:  96, motorcar:  96, goods:  96, hgv:  80, psv:  80},
63          primary: {foot:   4, horse:   8, wheelchair:   4, bicycle:  20, moped:  48, motorbike:  96, motorcar:  96, goods:  96, hgv:  80, psv:  80},
64        secondary: {foot:   4, horse:   8, wheelchair:   4, bicycle:  20, moped:  48, motorbike:  88, motorcar:  88, goods:  88, hgv:  80, psv:  80},
65         tertiary: {foot:   4, horse:   8, wheelchair:   4, bicycle:  20, moped:  48, motorbike:  80, motorcar:  80, goods:  80, hgv:  80, psv:  80},
66     unclassified: {foot:   4, horse:   8, wheelchair:   4, bicycle:  20, moped:  48, motorbike:  64, motorcar:  64, goods:  64, hgv:  64, psv:  64},
67      residential: {foot:   4, horse:   8, wheelchair:   4, bicycle:  20, moped:  48, motorbike:  48, motorcar:  48, goods:  48, hgv:  48, psv:  48},
68          service: {foot:   4, horse:   8, wheelchair:   4, bicycle:  20, moped:  32, motorbike:  32, motorcar:  32, goods:  32, hgv:  32, psv:  32},
69            track: {foot:   4, horse:   8, wheelchair:   4, bicycle:  20, moped:  16, motorbike:  16, motorcar:  16, goods:  16, hgv:  16, psv:  16},
70         cycleway: {foot:   4, horse:   8, wheelchair:   4, bicycle:  20, moped:   0, motorbike:   0, motorcar:   0, goods:   0, hgv:   0, psv:   0},
71             path: {foot:   4, horse:   8, wheelchair:   4, bicycle:  20, moped:   0, motorbike:   0, motorcar:   0, goods:   0, hgv:   0, psv:   0},
72            steps: {foot:   4, horse:   0, wheelchair:   4, bicycle:   0, moped:   0, motorbike:   0, motorcar:   0, goods:   0, hgv:   0, psv:   0}
73      },
74
75   // Highway properties
76   profile_property: {
77            paved: {foot:  50, horse:  20, wheelchair:  90, bicycle:  50, moped: 100, motorbike: 100, motorcar: 100, goods: 100, hgv: 100, psv: 100},
78        multilane: {foot:  25, horse:  25, wheelchair:  25, bicycle:  25, moped:  25, motorbike:  75, motorcar:  75, goods:  75, hgv:  75, psv:  75},
79           bridge: {foot:  50, horse:  50, wheelchair:  50, bicycle:  50, moped:  50, motorbike:  50, motorcar:  50, goods:  50, hgv:  50, psv:  50},
80           tunnel: {foot:  50, horse:  50, wheelchair:  50, bicycle:  50, moped:  50, motorbike:  50, motorcar:  50, goods:  50, hgv:  50, psv:  50}
81      },
82
83   // Restrictions
84   profile_restrictions: {
85           oneway: {foot:    0, horse:    1, wheelchair:    0, bicycle:    1, moped:    1, motorbike:    1, motorcar:    1, goods:    1, hgv:    1, psv:    1},
86           weight: {foot:  0.0, horse:  0.0, wheelchair:  0.0, bicycle:  0.0, moped:  0.0, motorbike:  0.0, motorcar:  0.0, goods:  5.0, hgv: 10.0, psv: 15.0},
87           height: {foot:  0.0, horse:  0.0, wheelchair:  0.0, bicycle:  0.0, moped:  0.0, motorbike:  0.0, motorcar:  0.0, goods:  2.5, hgv:  3.0, psv:  3.0},
88            width: {foot:  0.0, horse:  0.0, wheelchair:  0.0, bicycle:  0.0, moped:  0.0, motorbike:  0.0, motorcar:  0.0, goods:  2.0, hgv:  2.5, psv:  2.5},
89           length: {foot:  0.0, horse:  0.0, wheelchair:  0.0, bicycle:  0.0, moped:  0.0, motorbike:  0.0, motorcar:  0.0, goods:  5.0, hgv:  6.0, psv:  6.0}
90      }
91
92 }; // end of routino variable
93
94 // Make a deep copy of the routino profile.
95
96 var routino_default={};
97 for(var l1 in routino)
98    if(typeof(routino[l1])!='object')
99       routino_default[l1]=routino[l1];
100    else
101      {
102       routino_default[l1]={};
103       for(var l2 in routino[l1])
104          if(typeof(routino[l1][l2])!='object')
105             routino_default[l1][l2]=Number(routino[l1][l2]);
106          else
107            {
108             routino_default[l1][l2]={};
109             for(var l3 in routino[l1][l2])
110                routino_default[l1][l2][l3]=Number(routino[l1][l2][l3]);
111            }
112      }
113
114
115 ////////////////////////////////////////////////////////////////////////////////
116 //////////////////////////////// Form handling /////////////////////////////////
117 ////////////////////////////////////////////////////////////////////////////////
118
119 //
120 // Form initialisation - fill in the uninitialised parts
121 //
122
123 function form_init()
124 {
125  // Update the routino variable with the URL settings (from the HTML).
126
127  for(var lang=0;lang< document.forms["form"].elements["language"].length;lang++)
128     if(document.forms["form"].elements["language"][lang].checked)
129        formSetLanguage(document.forms["form"].elements["language"][lang].value);
130
131  var transport=null;
132
133  for(var key in routino.transports)
134     if(document.forms["form"].elements["transport"][routino.transports[key]-1].checked)
135        transport=key;
136
137  if(transport==null)
138     formSetTransport(routino.transport);
139  else
140    {
141     routino.transport=transport;
142
143     for(var key in routino.profile_highway)
144       {
145        if(document.forms["form"].elements["highway-" + key].value=="")
146           document.forms["form"].elements["highway-" + key].value=routino.profile_highway[key][routino.transport];
147        else
148           formSetHighway(key);
149       }
150
151     for(var key in routino.profile_speed)
152       {
153        if(document.forms["form"].elements["speed-" + key].value=="")
154           document.forms["form"].elements["speed-" + key].value=routino.profile_speed[key][routino.transport];
155        else
156           formSetSpeed(key);
157       }
158
159     for(var key in routino.profile_property)
160       {
161        if(document.forms["form"].elements["property-" + key].value=="")
162           document.forms["form"].elements["property-" + key].value=routino.profile_property[key][routino.transport];
163        else
164           formSetProperty(key);
165       }
166
167     for(var key in routino.restrictions)
168       {
169        if(key=="oneway")
170           formSetRestriction(key);
171        else
172          {
173           if(document.forms["form"].elements["restrict-" + key].value=="")
174              document.forms["form"].elements["restrict-" + key].value=routino.profile_restrictions[key][routino.transport];
175           else
176              formSetRestriction(key);
177          }
178       }
179    }
180
181  // Delete the extra empty waypoints
182
183  var filled=0;
184
185  for(var marker=nmarkers;marker>=1;marker--)
186    {
187     var lon=document.forms["form"].elements["lon" + marker].value;
188     var lat=document.forms["form"].elements["lat" + marker].value;
189
190     if(lon != "" && lat != "")
191       {
192        filled++;
193        markerAddMap(marker);
194       }
195     else if(filled==0)
196        markerRemove(marker);
197    }
198
199  // Get the home location cookie and compare to each waypoint
200
201  var cookies=document.cookie.split('; ');
202
203  for(var cookie=0;cookie<cookies.length;cookie++)
204     if(cookies[cookie].substr(0,"Routino-home".length)=="Routino-home")
205       {
206        var data=cookies[cookie].split(/[=:;]/);
207
208        if(data[1]=="lon") homelon=Number(data[2]);
209        if(data[3]=="lat") homelat=Number(data[4]);
210       }
211
212  if(homelon!=null && homelat!=null)
213    {
214     for(var marker=nmarkers;marker>=1;marker--)
215       {
216        var lon=document.forms["form"].elements["lon" + marker].value;
217        var lat=document.forms["form"].elements["lat" + marker].value;
218
219        if(lon==homelon && lat==homelat)
220           updateIcon(marker);
221       }
222
223     // If the first location is empty and the cookie is set then fill it.
224
225     if(document.forms["form"].elements["lon1"].value=="" && document.forms["form"].elements["lat1"].value=="")
226       {
227        document.forms["form"].elements["lon1"].value=homelon;
228        document.forms["form"].elements["lat1"].value=homelat;
229
230        markerAddMap(1);
231       }
232    }
233
234  updateCustomURL();
235 }
236
237
238 //
239 // Change of language in the form
240 //
241
242 function formSetLanguage(type)
243 {
244  routino.language=type;
245
246  updateCustomURL();
247 }
248
249
250 //
251 // Change of transport in the form
252 //
253
254 function formSetTransport(type)
255 {
256  routino.transport=type;
257
258  for(var key in routino.transports)
259     document.forms["form"].elements["transport"][routino.transports[key]-1].checked=(key==routino.transport);
260
261  for(var key in routino.profile_highway)
262     document.forms["form"].elements["highway-" + key].value=routino.profile_highway[key][routino.transport];
263
264  for(var key in routino.profile_speed)
265     document.forms["form"].elements["speed-" + key].value=routino.profile_speed[key][routino.transport];
266
267  for(var key in routino.profile_property)
268     document.forms["form"].elements["property-" + key].value=routino.profile_property[key][routino.transport];
269
270  for(var key in routino.restrictions)
271    {
272     if(key=="oneway")
273        document.forms["form"].elements["restrict-" + key].checked=routino.profile_restrictions[key][routino.transport];
274     else
275        document.forms["form"].elements["restrict-" + key].value=routino.profile_restrictions[key][routino.transport];
276    }
277
278  paramschanged=true;
279
280  updateCustomURL();
281 }
282
283
284 //
285 // Change of highway in the form
286 //
287
288 function formSetHighway(type)
289 {
290  routino.profile_highway[type][routino.transport]=document.forms["form"].elements["highway-" + type].value;
291
292  paramschanged=true;
293
294  updateCustomURL();
295 }
296
297
298 //
299 // Change of Speed in the form
300 //
301
302 function formSetSpeed(type)
303 {
304  routino.profile_speed[type][routino.transport]=document.forms["form"].elements["speed-" + type].value;
305
306  paramschanged=true;
307
308  updateCustomURL();
309 }
310
311
312 //
313 // Change of Property in the form
314 //
315
316 function formSetProperty(type)
317 {
318  routino.profile_property[type][routino.transport]=document.forms["form"].elements["property-" + type].value;
319
320  paramschanged=true;
321
322  updateCustomURL();
323 }
324
325
326 //
327 // Change of oneway rule in the form
328 //
329
330 function formSetRestriction(type)
331 {
332  if(type=="oneway")
333     routino.profile_restrictions[type][routino.transport]=document.forms["form"].elements["restrict-" + type].checked;
334  else
335     routino.profile_restrictions[type][routino.transport]=document.forms["form"].elements["restrict-" + type].value;
336
337  paramschanged=true;
338
339  updateCustomURL();
340 }
341
342
343 //
344 // Set the feature coordinates from the form when the form changes.
345 //
346
347 function formSetCoords(marker)
348 {
349  var lonlat=map.getCenter().clone();
350
351  lonlat.transform(map.getProjectionObject(),epsg4326);
352
353  var lon=document.forms["form"].elements["lon" + marker].value;
354  var lat=document.forms["form"].elements["lat" + marker].value;
355
356  if(lon!="")
357    {
358     if(lon<-180) lon=-180;
359     if(lon>+180) lon=+180;
360     lonlat.lon=lon;
361    }
362
363  if(lat!="")
364    {
365     if(lat<-90 ) lat=-90 ;
366     if(lat>+90 ) lat=+90 ;
367     lonlat.lat=lat;
368    }
369
370  var point = lonlat.clone();
371
372  point.transform(epsg4326,map.getProjectionObject());
373
374  markers[marker].move(point);
375
376  markersmoved=true;
377
378  coordsSetForm(marker);
379 }
380
381
382 //
383 // Set the feature coordinates in the form.
384 //
385
386 function coordsSetForm(marker)
387 {
388  var lonlat = new OpenLayers.LonLat(markers[marker].geometry.x, markers[marker].geometry.y);
389  lonlat.transform(map.getProjectionObject(),epsg4326);
390
391  var lon=format5f(lonlat.lon);
392  var lat=format5f(lonlat.lat);
393
394  document.forms["form"].elements["lon" + marker].value=lon;
395  document.forms["form"].elements["lat" + marker].value=lat;
396
397  updateIcon(marker);
398
399  updateCustomURL();
400 }
401
402
403 //
404 // Format a number in printf("%.5f") format.
405 //
406
407 function format5f(number)
408 {
409  var newnumber=Math.floor(number*100000+0.5);
410  var delta=0;
411
412  if(newnumber>=0 && newnumber<100000) delta= 100000;
413  if(newnumber<0 && newnumber>-100000) delta=-100000;
414
415  var string=String(newnumber+delta);
416
417  var intpart =string.substring(0,string.length-5);
418  var fracpart=string.substring(string.length-5,string.length);
419
420  if(delta>0) intpart="0";
421  if(delta<0) intpart="-0";
422
423  return(intpart + "." + fracpart);
424 }
425
426
427 //
428 // Build a set of URL arguments
429 //
430
431 function buildURLArguments(all)
432 {
433  var url="?";
434
435  url=url + "transport=" + routino.transport;
436
437  for(var marker=1;marker<=vismarkers;marker++)
438     if(markers[marker].style.display == "" || all)
439       {
440        url=url + ";lon" + marker + "=" + document.forms["form"].elements["lon" + marker].value;
441        url=url + ";lat" + marker + "=" + document.forms["form"].elements["lat" + marker].value;
442       }
443
444  for(var key in routino.profile_highway)
445     if(routino.profile_highway[key][routino.transport]!=routino_default.profile_highway[key][routino.transport])
446        url=url + ";highway-" + key + "=" + routino.profile_highway[key][routino.transport];
447
448  for(var key in routino.profile_speed)
449     if(routino.profile_speed[key][routino.transport]!=routino_default.profile_speed[key][routino.transport])
450        url=url + ";speed-" + key + "=" + routino.profile_speed[key][routino.transport];
451
452  for(var key in routino.profile_property)
453     if(routino.profile_property[key][routino.transport]!=routino_default.profile_property[key][routino.transport])
454        url=url + ";property-" + key + "=" + routino.profile_property[key][routino.transport];
455
456  for(var key in routino.restrictions)
457     if(routino.profile_restrictions[key][routino.transport]!=routino_default.profile_restrictions[key][routino.transport])
458        url=url + ";" + key + "=" + routino.profile_restrictions[key][routino.transport];
459
460  if(routino.language)
461     url=url + ";language=" + routino.language;
462
463  return(url);
464 }
465
466
467 //
468 // Update custom URL
469 //
470
471 function updateCustomURL()
472 {
473  var visualiser_url=document.getElementById("visualiser_url");
474  var link_url      =document.getElementById("link_url");
475  var edit_url      =document.getElementById("edit_url");
476
477  visualiser_url.href="customvisualiser.cgi?" + map_args;
478  link_url.href="customrouter.cgi" + buildURLArguments(1) + ";" + map_args;
479  edit_url.href="http://www.openstreetmap.org/edit?" + map_args;
480 }
481
482
483 //
484 // Block the use of the return key to submit the form
485 //
486
487 function block_return_key()
488 {
489  var form=document.getElementById("form");
490
491  if(form.addEventListener)
492     form.addEventListener('keyup', discardReturnKey, false);
493  else if(form.attachEvent)
494     form.attachEvent('keyup', discardReturnKey); // Internet Explorer
495 }
496
497 //
498 // Function to discard the return key if pressed
499 //
500
501 function discardReturnKey(ev)
502 {
503  if(ev.keyCode==13)
504     return(false);
505
506  return(true);
507 }
508
509
510 ////////////////////////////////////////////////////////////////////////////////
511 ///////////////////////////////// Map handling /////////////////////////////////
512 ////////////////////////////////////////////////////////////////////////////////
513
514 var map;
515 var layerMapOSM, layerVectors, layerGPX;
516 var epsg4326, epsg900913;
517 var map_args;
518
519 // 
520 // Initialise the 'map' object
521 //
522
523 function map_init(lat,lon,zoom)
524 {
525  // Default configuration:
526  // UK coordinate range
527  // West -11.0, South 49.5, East 2.0, North 61.0
528  // Zoom level 4 to 15
529
530  // EDIT THIS below to change the visible map limits
531
532  var westedge  = -11.0;          // Minimum longitude (degrees)
533  var eastedge  =   2.0;          // Maximum longitude (degrees)
534  var southedge =  49.5;          // Minimum latitude (degrees)
535  var northedge =  61.0;          // Maximum latitude (degrees)
536  var zoomout   =     4;          // Minimum zoom
537  var zoomin    =    15;          // Maximum zoom
538
539  // EDIT THIS above to change the visible map limits
540
541  //
542  // Create the map
543  //
544
545  epsg4326=new OpenLayers.Projection("EPSG:4326");
546  epsg900913=new OpenLayers.Projection("EPSG:900913");
547
548  map = new OpenLayers.Map ("map",
549                            {
550                             controls:[
551                                       new OpenLayers.Control.Navigation(),
552                                       new OpenLayers.Control.PanZoomBar(),
553                                       new OpenLayers.Control.ScaleLine(),
554                                       new OpenLayers.Control.LayerSwitcher()
555                                       ],
556
557                             projection: epsg900913,
558                             displayProjection: epsg4326,
559
560                             minZoomLevel: zoomout,
561                             numZoomLevels: zoomin-zoomout+1,
562                             maxResolution: 156543.0339 / Math.pow(2,zoomout),
563
564                             maxExtent:        new OpenLayers.Bounds(-20037508.34, -20037508.34, 20037508.34, 20037508.34),
565                             restrictedExtent: new OpenLayers.Bounds(westedge,southedge,eastedge,northedge).transform(epsg4326,epsg900913),
566
567                             units: "m"
568                            });
569
570  map.events.register("moveend", map, mapMoved);
571
572  // Add a map tile layer (OpenStreetMap tiles, direct access)
573
574  layerMapOSM = new OpenLayers.Layer.TMS("Original OSM map",
575                                         "http://tile.openstreetmap.org/",
576                                         {
577                                          emptyUrl: "http://openstreetmap.org/openlayers/img/404.png",
578                                          type: 'png',
579                                          getURL: limitedUrl,
580                                          displayOutsideMaxExtent: true,
581                                          buffer: 1
582                                         });
583  map.addLayer(layerMapOSM);
584
585  // Get a URL for the tile; limited to map restricted extent.
586
587  function limitedUrl(bounds)
588  {
589   var z = map.getZoom() + map.minZoomLevel;
590
591   if (z>=7 && (bounds.right  < map.restrictedExtent.left ||
592                bounds.left   > map.restrictedExtent.right ||
593                bounds.top    < map.restrictedExtent.bottom ||
594                bounds.bottom > map.restrictedExtent.top))
595      return this.emptyUrl;
596
597   var res = map.getResolution();
598   var y = Math.round((this.maxExtent.top - bounds.top) / (res * this.tileSize.h));
599   var limit = Math.pow(2, z);
600
601   if (y < 0 || y >= limit)
602     return this.emptyUrl;
603
604   var x = Math.round((bounds.left - this.maxExtent.left) / (res * this.tileSize.w));
605
606   x = ((x % limit) + limit) % limit;
607   return this.url + z + "/" + x + "/" + y + "." + this.type;
608  }
609
610  // Define a GPX layer but don't add it yet
611
612  layerGPX={shortest: null, quickest: null};
613
614  gpx_style={shortest: new OpenLayers.Style({},{strokeWidth: 3, strokeColor: "#00FF00"}),
615             quickest: new OpenLayers.Style({},{strokeWidth: 3, strokeColor: "#0000FF"})};
616  
617  // Add a vectors layer
618  
619  layerVectors = new OpenLayers.Layer.Vector("Markers");
620  map.addLayer(layerVectors);
621
622  // A set of markers
623
624  nmarkers=99;
625  vismarkers=0;
626  markers={};
627  markersmoved=false;
628  paramschanged=false;
629
630  for(var marker=1;marker<=nmarkers;marker++)
631    {
632     if(document.forms["form"].elements["lon" + marker] != undefined)
633       {
634        markers[marker] = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(0,0),{},
635                                                        new OpenLayers.Style({},{externalGraphic: 'icons/marker-' + marker + '-red.png',
636                                                                                 fillColor: "white",
637                                                                                 graphicYOffset: -25,
638                                                                                 graphicWidth: 21,
639                                                                                 graphicHeight: 25,
640                                                                                 display: "none"}));
641
642        layerVectors.addFeatures([markers[marker]]);
643       }
644     else
645       {
646        nmarkers=marker-1;
647        vismarkers=marker-1;
648        break;
649       }
650    }
651
652  // A function to drag the markers
653
654  var drag = new OpenLayers.Control.DragFeature(layerVectors,
655                                                {onDrag:     dragMove,
656                                                 onComplete: dragComplete });
657  map.addControl(drag);
658  drag.activate();
659
660  // Markers to highlight a selected point
661
662  for(var highlight in highlights)
663    {
664     highlights[highlight] = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(0,0),{},
665                                                           new OpenLayers.Style({},{strokeColor: route_dark_colours[highlight],
666                                                                                    fillColor: "white",
667                                                                                    pointRadius: 10,
668                                                                                    strokeWidth: 4,
669                                                                                    fillOpacity: 0,
670                                                                                    display: "none"}));
671
672     layerVectors.addFeatures([highlights[highlight]]);
673    }
674
675  // A popup for routing results
676
677  for(var popup in popups)
678     popups[popup] = createPopup(popup);
679
680  // Set the map centre to the limited range specified
681
682  map.setCenter(map.restrictedExtent.getCenterLonLat(), map.getZoomForExtent(map.restrictedExtent,true));
683  map.maxResolution = map.getResolution();
684
685  // Move the map
686
687  if(lon != 'lon' && lat != 'lat' && zoom != 'zoom')
688    {
689     var lonlat = new OpenLayers.LonLat(lon,lat).transform(epsg4326,map.getProjectionObject());
690
691     map.moveTo(lonlat,zoom-map.minZoomLevel);
692    }
693 }
694
695
696 //
697 // Map has moved
698 //
699
700 function mapMoved()
701 {
702  var centre = map.getCenter().clone();
703
704  var lonlat = centre.transform(map.getProjectionObject(),epsg4326);
705
706  var zoom = this.getZoom() + map.minZoomLevel;
707
708  map_args="lat=" + lonlat.lat + ";lon=" + lonlat.lon + ";zoom=" + zoom;
709
710  updateCustomURL();
711 }
712
713
714 //
715 // OpenLayers.Control.DragFeature callback for a drag occuring.
716 //
717
718 function dragMove(feature,pixel)
719 {
720  for(var marker in markers)
721     if(feature==markers[marker])
722       {
723        markersmoved=true;
724
725        coordsSetForm(marker);
726       }
727 }
728
729
730 //
731 // OpenLayers.Control.DragFeature callback for completing a drag.
732 //
733
734 function dragComplete(feature,pixel)
735 {
736  for(var marker in markers)
737     if(feature==markers[marker])
738       {
739        markersmoved=true;
740
741        coordsSetForm(marker);
742       }
743 }
744
745
746 ////////////////////////////////////////////////////////////////////////////////
747 /////////////////////////////// Marker handling ////////////////////////////////
748 ////////////////////////////////////////////////////////////////////////////////
749
750 var nmarkers, vismarkers, markers, markersmoved, paramschanged;
751 var homelat=null, homelon=null;
752
753
754 //
755 // Toggle a marker on the map.
756 //
757
758 function markerToggleMap(marker)
759 {
760  if(markers[marker].style.display == "")
761     markerRemoveMap(marker);
762  else
763     markerAddMap(marker);
764 }
765
766
767 //
768 // Show a marker on the map.
769 //
770
771 function markerAddMap(marker)
772 {
773  markers[marker].style.display = "";
774
775  formSetCoords(marker);
776
777  updateIcon(marker);
778
779  markersmoved=true;
780 }
781
782
783 //
784 // Remove a marker from the map.
785 //
786
787 function markerRemoveMap(marker)
788 {
789  markers[marker].style.display = "none";
790
791  updateIcon(marker);
792
793  markersmoved=true;
794 }
795
796
797 //
798 // Centre the marker on the map
799 //
800
801 function markerCentre(marker)
802 {
803  document.forms["form"].elements["lon" + marker].value="";
804  document.forms["form"].elements["lat" + marker].value="";
805
806  formSetCoords(marker);
807
808  markersmoved=true;
809 }
810
811
812 //
813 // Clear the current marker.
814 //
815
816 function markerRemove(marker)
817 {
818  for(var marker2=marker;marker2<vismarkers;marker2++)
819    {
820     document.forms["form"].elements["lon" + marker2].value=document.forms["form"].elements["lon" + (marker2+1)].value;
821     document.forms["form"].elements["lat" + marker2].value=document.forms["form"].elements["lat" + (marker2+1)].value;
822
823     if(markers[marker2+1].style.display=="")
824        markerAddMap(marker2);
825     else
826        markerRemoveMap(marker2);
827    }
828
829  markerRemoveMap(vismarkers);
830
831  var marker_tr=document.getElementById("point" + vismarkers);
832
833  marker_tr.style.display="none";
834
835  vismarkers--;
836
837  if(vismarkers==1)
838     markerAddAfter(1);
839
840  updateCustomURL();
841 }
842
843
844 //
845 // Add a marker before the current one.
846 //
847
848 function markerAddBefore(marker)
849 {
850  if(vismarkers==nmarkers || marker==1)
851     return false;
852
853  vismarkers++;
854
855  var marker_tr=document.getElementById("point" + vismarkers);
856
857  marker_tr.style.display="";
858
859  for(var marker2=vismarkers;marker2>marker;marker2--)
860    {
861     document.forms["form"].elements["lon" + marker2].value=document.forms["form"].elements["lon" + (marker2-1)].value;
862     document.forms["form"].elements["lat" + marker2].value=document.forms["form"].elements["lat" + (marker2-1)].value;
863
864     if(markers[marker2-1].style.display=="")
865        markerAddMap(marker2);
866     else
867        markerRemoveMap(marker2);
868    }
869
870  document.forms["form"].elements["lon" + marker].value="";
871  document.forms["form"].elements["lat" + marker].value="";
872  markers[marker].style.display="none";
873
874  markerRemoveMap(marker);
875
876  updateCustomURL();
877 }
878
879
880 //
881 // Add a marker after the current one.
882 //
883
884 function markerAddAfter(marker)
885 {
886  if(vismarkers==nmarkers)
887     return false;
888
889  vismarkers++;
890
891  var marker_tr=document.getElementById("point" + vismarkers);
892
893  marker_tr.style.display="";
894
895  for(var marker2=vismarkers;marker2>(marker+1);marker2--)
896    {
897     document.forms["form"].elements["lon" + marker2].value=document.forms["form"].elements["lon" + (marker2-1)].value;
898     document.forms["form"].elements["lat" + marker2].value=document.forms["form"].elements["lat" + (marker2-1)].value;
899
900     if(markers[marker2-1].style.display=="")
901        markerAddMap(marker2);
902     else
903        markerRemoveMap(marker2);
904    }
905
906  document.forms["form"].elements["lon" + (marker+1)].value="";
907  document.forms["form"].elements["lat" + (marker+1)].value="";
908  markers[marker+1].style.display="none";
909
910  markerRemoveMap(marker+1);
911
912  updateCustomURL();
913 }
914
915
916 //
917 // Set this marker as the home location.
918 //
919
920 function markerHome(marker)
921 {
922  if(markerHomeCookie(marker))
923     for(marker=1;marker<=nmarkers;marker++)
924        updateIcon(marker);
925 }
926
927
928 //
929 // Update an icon to set colours and home or normal marker.
930 //
931
932 function updateIcon(marker)
933 {
934  var lon=document.forms["form"].elements["lon" + marker].value;
935  var lat=document.forms["form"].elements["lat" + marker].value;
936
937  if(lon==homelon && lat==homelat)
938    {
939     if(markers[marker].style.display=="")
940        document.images["waypoint" + marker].src="icons/marker-home-red.png";
941     else
942        document.images["waypoint" + marker].src="icons/marker-home-grey.png";
943
944     markers[marker].style.externalGraphic="icons/marker-home-red.png";
945    }
946  else
947    {
948     if(markers[marker].style.display=="")
949        document.images["waypoint" + marker].src="icons/marker-" + marker + "-red.png";
950     else
951        document.images["waypoint" + marker].src="icons/marker-" + marker + "-grey.png";
952
953     markers[marker].style.externalGraphic="icons/marker-" + marker + "-red.png";
954    }
955
956  layerVectors.drawFeature(markers[marker]);
957 }
958
959
960 //
961 // Set or clear the home marker icon
962 //
963
964 function markerHomeCookie(marker)
965 {
966  var lon=document.forms["form"].elements["lon" + marker].value;
967  var lat=document.forms["form"].elements["lat" + marker].value;
968
969  if(lon=="" || lat=="")
970     return(false);
971
972  var cookie;
973  var date = new Date();
974
975  if((homelat==null && homelon==null) ||
976     (homelat!=lat  && homelon!=lon))
977    {
978     cookie="Routino-home=lon:" + lon + ":lat:" + lat;
979
980     date.setUTCFullYear(date.getUTCFullYear()+5);
981
982     homelat=lat;
983     homelon=lon;
984    }
985  else
986    {
987     cookie="Routino-home=unset";
988
989     date.setUTCFullYear(date.getUTCFullYear()-1);
990
991     homelat=null;
992     homelon=null;
993    }
994
995  document.cookie=cookie + ";expires=" + date.toGMTString();
996
997  return(true);
998 }
999
1000
1001 //
1002 // Move this marker up.
1003 //
1004
1005 function markerMoveUp(marker)
1006 {
1007  if(marker==1)
1008     return false;
1009
1010  markerSwap(marker,marker-1);
1011 }
1012
1013
1014 //
1015 // Move this marker down.
1016 //
1017
1018 function markerMoveDown(marker)
1019 {
1020  if(marker==vismarkers)
1021     return false;
1022
1023  markerSwap(marker,marker+1);
1024 }
1025
1026
1027 //
1028 // Swap a pair of markers.
1029 //
1030
1031 function markerSwap(marker1,marker2)
1032 {
1033  var lon=document.forms["form"].elements["lon" + marker1].value;
1034  var lat=document.forms["form"].elements["lat" + marker1].value;
1035  var display=markers[marker1].style.display;
1036
1037  document.forms["form"].elements["lon" + marker1].value=document.forms["form"].elements["lon" + marker2].value;
1038  document.forms["form"].elements["lat" + marker1].value=document.forms["form"].elements["lat" + marker2].value;
1039  if(markers[marker2].style.display=="")
1040     markerAddMap(marker1);
1041  else
1042     markerRemoveMap(marker1);
1043
1044  document.forms["form"].elements["lon" + marker2].value=lon;
1045  document.forms["form"].elements["lat" + marker2].value=lat;
1046  if(display=="")
1047     markerAddMap(marker2);
1048  else
1049     markerRemoveMap(marker2);
1050
1051  updateCustomURL();
1052 }
1053
1054
1055 //
1056 // Reverse the markers.
1057 //
1058
1059 function markersReverse()
1060 {
1061  for(var marker=1;marker<=vismarkers/2;marker++)
1062     markerSwap(marker,vismarkers+1-marker);
1063
1064  updateCustomURL();
1065 }
1066
1067
1068 ////////////////////////////////////////////////////////////////////////////////
1069 //////////////////////////// Route results handling ////////////////////////////
1070 ////////////////////////////////////////////////////////////////////////////////
1071
1072 var route_light_colours={shortest: "#60C060", quickest: "#6060C0"};
1073 var route_dark_colours ={shortest: "#408040", quickest: "#404080"};
1074
1075 var highlights={shortest: null, quickest: null};
1076 var popups={shortest: null, quickest: null};
1077 var routepoints={shortest: {}, quickest: {}};
1078 var gpx_style={shortest: null, quickest: null};
1079
1080 //
1081 // Zoom to a specific item in the route
1082 //
1083
1084 function zoomTo(type,line)
1085 {
1086  var lonlat = new OpenLayers.LonLat(routepoints[type][line].lon,routepoints[type][line].lat).transform(epsg4326,map.getProjectionObject());
1087
1088  map.moveTo(lonlat,map.numZoomLevels-2);
1089 }
1090
1091
1092 //
1093 // Highlight a specific item in the route
1094 //
1095
1096 function highlight(type,line)
1097 {
1098  if(line==-1)
1099    {
1100     highlights[type].style.display = "none";
1101
1102     drawPopup(popups[type],null);
1103    }
1104  else
1105    {
1106     // Marker
1107
1108     var lonlat = new OpenLayers.LonLat(routepoints[type][line].lon,routepoints[type][line].lat).transform(epsg4326,map.getProjectionObject());
1109
1110     highlights[type].move(lonlat);
1111
1112     if(highlights[type].style.display = "none")
1113        highlights[type].style.display = "";
1114
1115     // Popup
1116
1117     drawPopup(popups[type],"<table>" + routepoints[type][line].html + "</table>");
1118    }
1119
1120  layerVectors.drawFeature(highlights[type]);
1121 }
1122
1123
1124 //
1125 // Create a popup - not using OpenLayers because want it fixed on screen not fixed on map.
1126 //
1127
1128 function createPopup(type)
1129 {
1130  var popup=document.createElement('div');
1131
1132  popup.className = "popup";
1133
1134  popup.innerHTML = "<span></span>";
1135
1136  popup.style.display = "none";
1137
1138  popup.style.position = "fixed";
1139  popup.style.top = "-4000px";
1140  popup.style.left = "-4000px";
1141  popup.style.zIndex = "100";
1142
1143  popup.style.padding = "5px";
1144
1145  popup.style.opacity=0.85;
1146  popup.style.backgroundColor=route_light_colours[type];
1147  popup.style.border="4px solid " + route_dark_colours[type];
1148
1149  document.body.appendChild(popup);
1150
1151  return(popup);
1152 }
1153
1154
1155 //
1156 // Draw a popup - not using OpenLayers because want it fixed on screen not fixed on map.
1157 //
1158
1159 function drawPopup(popup,html)
1160 {
1161  if(html==null)
1162    {
1163     popup.style.display="none";
1164     return;
1165    }
1166
1167  if(popup.style.display=="none")
1168    {
1169     var map_div=document.getElementById("map");
1170
1171     popup.style.left  =map_div.offsetParent.offsetLeft+map_div.offsetLeft+60 + "px";
1172     popup.style.top   =                                map_div.offsetTop +30 + "px";
1173     popup.style.width =map_div.clientWidth-100 + "px";
1174
1175     popup.style.display="";
1176    }
1177
1178  popup.innerHTML=html;
1179 }
1180
1181
1182 //
1183 // Remove a GPX trace
1184 //
1185
1186 function removeGPXTrace(type)
1187 {
1188  map.removeLayer(layerGPX[type]);
1189  layerGPX[type].destroy();
1190  layerGPX[type]=null;
1191
1192  displayStatus(type,"no_info");
1193
1194  var div_links=document.getElementById(type + "_links");
1195  div_links.style.display = "none";
1196
1197  var div_route=document.getElementById(type + "_route");
1198  div_route.innerHTML = "";
1199
1200  hideshow_hide(type);
1201 }
1202
1203
1204 ////////////////////////////////////////////////////////////////////////////////
1205 /////////////////////////////// Server handling ////////////////////////////////
1206 ////////////////////////////////////////////////////////////////////////////////
1207
1208 //
1209 // Display data statistics
1210 //
1211
1212 function displayStatistics()
1213 {
1214  // Use AJAX to get the statistics
1215
1216  OpenLayers.loadURL("statistics.cgi",null,null,runStatisticsSuccess);
1217 }
1218
1219
1220 //
1221 // Success in running data statistics generation.
1222 //
1223
1224 function runStatisticsSuccess(response)
1225 {
1226  var statistics_data=document.getElementById("statistics_data");
1227  var statistics_link=document.getElementById("statistics_link");
1228
1229  statistics_data.innerHTML="<pre>" + response.responseText + "</pre>";
1230
1231  statistics_link.style.display="none";
1232 }
1233
1234
1235 //
1236 // Submit form - perform the routing
1237 //
1238
1239 function findRoute(type)
1240 {
1241  tab_select("results");
1242
1243  hideshow_hide('help_options');
1244  hideshow_hide('shortest');
1245  hideshow_hide('quickest');
1246
1247  displayStatus("result","running");
1248
1249  var url="router.cgi" + buildURLArguments(0) + ";type=" + type;
1250
1251  // Destroy the existing layer(s)
1252
1253  if(markersmoved || paramschanged)
1254    {
1255     if(layerGPX.shortest!=null)
1256        removeGPXTrace("shortest");
1257     if(layerGPX.quickest!=null)
1258        removeGPXTrace("quickest");
1259     markersmoved=false;
1260     paramschanged=false;
1261    }
1262  else if(layerGPX[type]!=null)
1263     removeGPXTrace(type);
1264
1265  // Use AJAX to run the router
1266
1267  routing_type=type;
1268
1269  OpenLayers.loadURL(url,null,null,runRouterSuccess,runRouterFailure);
1270 }
1271
1272
1273 //
1274 // Success in running router.
1275 //
1276
1277 function runRouterSuccess(response)
1278 {
1279  var lines=response.responseText.split('\n');
1280
1281  var uuid=lines[0];
1282  var cpuinfo=lines[1];
1283  var distinfo=lines[2];
1284  var message=lines[3];
1285
1286  // Update the status message
1287
1288  if(message!="")
1289    {
1290     displayStatus("result","error");
1291     hideshow_show('help_route');
1292     return;
1293    }
1294  else
1295    {
1296     displayStatus("result","complete");
1297     hideshow_hide('help_route');
1298    }
1299
1300  // Update the routing result message
1301
1302  displayStatus(routing_type,"info",distinfo.bold());
1303
1304  var link;
1305
1306  link=document.getElementById(routing_type + "_html");
1307  link.href="results.cgi?uuid=" + uuid + ";type=" + routing_type + ";format=html";
1308  link=document.getElementById(routing_type + "_gpx_track");
1309  link.href="results.cgi?uuid=" + uuid + ";type=" + routing_type + ";format=gpx-track";
1310  link=document.getElementById(routing_type + "_gpx_route");
1311  link.href="results.cgi?uuid=" + uuid + ";type=" + routing_type + ";format=gpx-route";
1312  link=document.getElementById(routing_type + "_text_all");
1313  link.href="results.cgi?uuid=" + uuid + ";type=" + routing_type + ";format=text-all";
1314  link=document.getElementById(routing_type + "_text");
1315  link.href="results.cgi?uuid=" + uuid + ";type=" + routing_type + ";format=text";
1316
1317  var div_links=document.getElementById(routing_type + "_links");
1318  div_links.style.display = "";
1319
1320  // Add a GPX layer
1321
1322  var url="results.cgi?uuid=" + uuid + ";type=" + routing_type + ";format=gpx-track";
1323
1324  layerGPX[routing_type] = new OpenLayers.Layer.GML("GPX (" + routing_type + ")", url,
1325                                                    {
1326                                                     format:     OpenLayers.Format.GPX,
1327                                                     style:      gpx_style[routing_type],
1328                                                     projection: map.displayProjection
1329                                                    });
1330
1331  map.addLayer(layerGPX[routing_type]);
1332
1333  hideshow_show(routing_type);
1334
1335  displayResult(routing_type,uuid);
1336 }
1337
1338
1339 //
1340 // Failure in running router.
1341 //
1342
1343 function runRouterFailure(response)
1344 {
1345  displayStatus("result","failed");
1346 }
1347
1348
1349 //
1350 // Display the status
1351 //
1352
1353 function displayStatus(type,subtype,content)
1354 {
1355  var div_status=document.getElementById(type + "_status");
1356
1357  var child=div_status.firstChild;
1358
1359  do
1360    {
1361     if(child.id != undefined)
1362        child.style.display="none";
1363
1364     child=child.nextSibling;
1365    }
1366  while(child != undefined);
1367
1368  var span_status=document.getElementById(type + "_status_" + subtype);
1369
1370  span_status.style.display="";
1371
1372  if(content != null)
1373     span_status.innerHTML=content;
1374 }
1375
1376
1377 //
1378 // Display the route
1379 //
1380
1381 function displayResult(type,uuid)
1382 {
1383  routing_type = type;
1384
1385  // Add the route
1386
1387  var url="results.cgi?uuid=" + uuid + ";type=" + routing_type + ";format=html";
1388
1389  // Use AJAX to get the route
1390
1391  OpenLayers.loadURL(url,null,null,getRouteSuccess,getRouteFailure);
1392 }
1393
1394
1395 //
1396 // Success in getting route.
1397 //
1398
1399 function getRouteSuccess(response)
1400 {
1401  var lines=response.responseText.split('\n');
1402  var div_route=document.getElementById(routing_type + "_route");
1403
1404  routepoints[routing_type]=[];
1405
1406  var points=routepoints[routing_type];
1407
1408  var table=0;
1409  var point=0;
1410  var total_table,total_word;
1411
1412  for(var line=0;line<lines.length;line++)
1413    {
1414     var thisline=lines[line];
1415
1416     if(table==0)
1417       {
1418        if(thisline.match('<table>'))
1419           table=1;
1420        else
1421           continue;
1422       }
1423
1424     if(thisline.match('</table>'))
1425        break;
1426
1427     if(thisline.match('<tr class=\'([a-z])\'>'))
1428       {
1429        var rowtype=RegExp.$1;
1430
1431        if(rowtype=='c')
1432          {
1433           thisline.match('<td class=\'r\'> *([-0-9.]+) *([-0-9.]+)');
1434           points[point]={lat: Number(RegExp.$1), lon: Number(RegExp.$2), html: "", highway: "", distance: "", total: ""};
1435
1436           point++;
1437          }
1438        else if(rowtype=='n')
1439          {
1440           points[point-1].html += thisline;
1441          }
1442        else if(rowtype=='s')
1443          {
1444           thisline.match('<span class=\'h\'>([^<]+)</span>');
1445           points[point-1].highway = RegExp.$1;
1446
1447           thisline.match('<span class=\'d\'>([^<]+)</span>');
1448           points[point-1].distance = RegExp.$1;
1449
1450           thisline.match('(<span class=\'j\'>[^<]+</span>)');
1451           points[point-1].total = RegExp.$1;
1452
1453           thisline.match('^(.*).<span class=\'j\'>');
1454
1455           points[point-1].html += RegExp.$1;
1456          }
1457        else if(rowtype=='t')
1458          {
1459           points[point-1].html += thisline;
1460
1461           thisline.match('^(.*<td class=\'r\'>)');
1462           total_table = RegExp.$1;
1463
1464           thisline.match('<td class=\'l\'>([^<]+)<');
1465           total_word = RegExp.$1;
1466
1467           thisline.match('<span class=\'j\'>([^<]+)</span>');
1468           points[point-1].total = RegExp.$1;
1469          }
1470       }
1471    }
1472
1473  var result="<table onmouseout='highlight(\"" + routing_type + "\",-1)'>";
1474
1475  for(var p=0;p<point-1;p++)
1476    {
1477     points[p].html += total_table + points[p].total;
1478
1479     result=result + "<tr onclick='zoomTo(\"" + routing_type + "\"," + p + ")'" +
1480                     " onmouseover='highlight(\"" + routing_type + "\"," + p + ")'>" +
1481                     "<td class='distance' title='" + points[p].distance + "'>#" + (p+1) +
1482                     "<td class='highway'>" + points[p].highway;
1483    }
1484
1485  result=result + "<tr onclick='zoomTo(\"" + routing_type + "\"," + p + ")'" +
1486                  " onmouseover='highlight(\"" + routing_type + "\"," + p + ")'>" +
1487                  "<td colspan='2'>" + total_word + " " + points[p].total;
1488
1489  result=result + "</table>";
1490
1491  div_route.innerHTML=result;
1492 }
1493
1494
1495 //
1496 // Failure in getting route.
1497 //
1498
1499 function getRouteFailure(response)
1500 {
1501  var div_route=document.getElementById(routing_type + "_route");
1502  div_route.innerHTML = "";
1503 }