/*
<!-- Copyright 2008-2009 Parthian Systems. All Rights Reserved -->
<!-- Terramapper - Converts XML Data to Dynamic Pictures -->
*/


var iconinfo = new Array();  // Moved from TerraMapper for onclick
var relations;          // From xmlDoc
var nodes;              // From xmlDoc
var nodeDBIndex;        // Database Index of related relations
var relationsLUT;       // Look Up Table to match JOSM assigned Relations ID to index in relations array
var hashNodeDBIndex = {};    // Store multiple Indexes
var hashRelationsLUT = {};   // Store mulitple LUT

var nodeIndex;          // index into DB - where we are
var nearestNodeIndex;   // index into DB - where we are
var relationIndex;      // index into DB - which Relation we are on
var currentNode;        // remembers where we are
var startRelationRef;   // The Relation ID equivalent of position of Start passed into terramapper from button
var startRelationNode;  // The node which is the actual start (JOSM usage must add start tag)
var firstRelationRef;   // First Relation for offset - may not be same as sRR
var reverse = false;    // identify if operator is going in reverse
var facingInward = false; // Remember if we are looking into node or not
var firstStart = true;  // to full navigate before a button can be clicked
var looper = 0;
var downer = 100;
var uptimer;
var downtimer;
var photo;
var movieDOM;
var mediaDIV;           // The block where direction arrows are added
var theImage;           // Replacement for targ - cannot be passed to function  must be global
var delay;
var currentLocation;    // Terra Mapper
var distanceTravelled = 0;
var moviesDOM;          // For dryrun and for real play
var photosDIV;          // DIV that shows the pictures
var moviesID;           // holds link to the Player
var duration;           // holds the length in seconds of the current video - use in stepVideo
var stepSeconds;        // external store of the number of seconds to rotate video by
var debug = false;

var hashPoints = null;    // For OL Convex Hull
var hashPolygons = null;


var stepper = 0;
var playstateValues = new Array("Undefined","Stopped","Paused","Playing","Scan Forward","Scan Reverse","Buffering","Waiting","Media Ended","Transitioning","Ready","Reconnecting");
var videoStatus = "";

var xmlDoc;
var xmlAttributes;
var routeInfo;
var currentfname = ""; // active XML file
var explored = new Array(); // XML filename

// For animated Icons
var VertAmount = 0;
// var HorizAmount = 190;
var  intervalId = 0;

var dateShapeMap; // JSON to hold complex object for shape to calendar data
var pinHashInterval; // Hash 2D Array to hold Vertical Amount and Interval value for each pin that is animated
var debugString = "";
var origCursor = null;
var feedbackPinID; // Used to hold pin of Way highlighter temporarily until removed when mouse away from Arrow

var steps = []; // Array of JSON objects for AutoWalk
var stepCount, stepMax; // Used with AutoWalk
var captureStepsMode = false;
var stepsJSON = null;
var restart = false; // extra logic for direction testing after a restart
var autoFacingInward = false; // For Autowalk
var autoNearestNodeIndex = "";
var autoRelationIndex = "";
var autoCurrentNode = "";

// ------------- Global Var Declarations  End ---------------------------


// This is outside function - create an object and add a function to that object
if (GeoHelper == undefined) {
  var GeoHelper = {}; }

    GeoHelper.IsInPolygon=function(points,latlong)
    {
        // This code adapted from the following URL:
        // http://msdn.microsoft.com/en-us/library/cc451895.aspx

        var i;
        var j=points.length-1;
        var inPoly=false;
        var lat = latlong.Latitude;
        var lon = latlong.Longitude;

        for (i=0; i<points.length; i++)
        {
            if (points[i].Longitude<lon && points[j].Longitude>=lon ||  points[j].Longitude<lon && points[i].Longitude>=lon)
            {
                if (points[i].Latitude+(lon-points[i].Longitude)/(points[j].Longitude-points[i].Longitude)*(points[j].Latitude-points[i].Latitude)<lat)
                {
                    inPoly=!inPoly;
                }
            }
            j=i;
        }

        return inPoly;
    };

// extend Number object with methods for converting degrees/radians - moveabletype
Number.prototype.toRad = function() {  // convert degrees to radians
  return this * Math.PI / 180;
}
Number.prototype.toDeg = function() {  // convert radians to degrees (signed)
  return this * 180 / Math.PI;
}
Number.prototype.toBrng = function() {  // convert radians to degrees (as bearing: 0...360)
  return (this.toDeg()+360) % 360;
}
function LatLon(lat, lon) {
  this.lat = lat;
  this.lon = lon;
}

function floatTrim(inputNum, desiredPrecision){
 var precisionGuide = Math.pow(10, desiredPrecision);
 return( Math.round(inputNum * precisionGuide) / precisionGuide );
}


function get_nextSibling(n)
{ // From http://www.w3schools.com/dom/dom_nodes_navigate.asp
  // handle FF funny
y=n.nextSibling;
while (y.nodeType!=1)
  {
  y=y.nextSibling;
  }
return y;
}

function get_firstChild(n)
{ // Handle FF funny
y=n.firstChild;
while (y.nodeType!=1)
  {
  y=y.nextSibling;
  }
return y;
}

function toggleCreatePushpins(thisBtn ){
  if (thisBtn.className.indexOf ("eicon_pinF") > -1) { // indexOf returns -1 if string not found
    map.AttachEvent("onclick", createPushPin);
    //thisBtn.setAttribute ('value', 'Disable Add Comments Mode');
    //thisBtn.style.background = "red"; // But this removes the nice button styling...
    //thisBtn.style.color = "red";
    thisBtn.className = "editingicons eicon_pinT";
    origCursor = document.getElementById('Map').childNodes[0].style.cursor; // also use this for starting Explorer
    document.getElementById('Map').childNodes[0].style.cursor = "crosshair";
    //map.AttachEvent("onmouseover", mouseOverHandler);
  }
  else {
    map.DetachEvent("onclick", createPushPin);
    //thisBtn.setAttribute ('value', 'Enable Add Comments Mode');
    //thisBtn.style.color = "black";
    thisBtn.className = "editingicons eicon_pinF";
    document.getElementById('Map').childNodes[0].style.cursor = origCursor;
  }
}

function reenableCreatePushpin(e) {
  // Callback from Accordion onActivate: funcnamewithoutinvertedcommas;
  // Doesn't activate with close of POI but open of any other accordion will switch submit mode off
  //if (e.id == "POI") {
    var btn = $('CComm');
    if (btn.className.indexOf("eicon_pinT") > -1) {
      toggleCreatePushpins(btn); } // if the user closes the POI without switching off Submit mode it will be turned off
}


function buildPopupWindow (shapeID, LL, isSimple, commentText)
{
  var routes = ""; // only used for Line Geometries - but could there be Area or something here?
  // Styling and ClearInfoBoxStyles makes popup disappear unless on an input element
  // now see .customInfoBox-body styling in SamplePAge
  var mainID = '<div id="' + shapeID + '_pin">';
  var comments =  '<textarea name="usersnotes" id="' + shapeID + '_notes" rows="5" cols="28" autocomplete="off">Type message here...</textarea><br />';
  if (commentText != "") {
    routes =  '<textarea style="display:none;" name="usersroutes" id="' + shapeID + '_routes" rows="5" cols="28" autocomplete="off" readonly="readonly"><div>Directions<br />' + commentText  + '</div></textarea><br />';
  }
  var blockCal = '<input type="button" value="Calendar" onclick="javascript:\n\
        { var xmlpopup = \'<configs><initcfg><name>calendarPopup<\/name><mode>flat<\/mode><multiselect>no<\/multiselect><\/initcfg><\/configs>\';\n\
        calendarPopup = new EpochPrime(document.getElementById(\'epochalarm\'),xmlpopup); \n\
        document.getElementById(\'startdate\').value = calendarPopup.curDate.dateFormat(\'d-m-Y\'); \n\
        this.disabled = true; \n\
        }"/> \n\
        <input type="button" value="Add Date" onclick="javascript:\n\
        {document.getElementById(\'startdate\').value = calendarPopup.selectedDates[0].dateFormat(\'d-m-Y\');\n\
          calendarPopup.hide();}"/> \n\
        <input type="text" value="today" name="startdate" id="startdate" size="8" onclick="javascript:{ this.value = calendarPopup.selectedDates[0].dateFormat(\'d-m-Y\');}" /> \n\
        <br />Open the Calendar then select Date and <br />finally click Add Date button\n\
        <div id="epochalarm"></div><br />';


  var blockSimpleRadio = '<input type="radio" class="inpleft" name="commenttype" value="default" checked="checked" /><label class="llb">General</label>\n\
      <input type="radio" class="inpright" name="commenttype" value="map" /><label class="rlb">Map</label><br /><br />\n\
      <input type="radio" class="inpleft" name="commenttype" value="trees" /><label class="llb">Trees</label>\n\
      <input type="radio" class="inpright" name="commenttype" value="plants" /><label class="rlb">Plants</label><br /><br />\n\
      <input type="radio" class="inpleft" name="commenttype" value="animals" /><label class="llb">Animals</label>\n\
      <input type="radio" class="inpright" name="commenttype" value="birds" /><label class="rlb">Birds</label><br /><br />\n\
      <input type="radio" class="inpleft" name="commenttype" value="history" /><label class="llb">History</label>\n\
      <input type="radio" class="inpright" name="commenttype" value="cultural" /><label class="rlb">Cultural</label><br /><br />\n\
      <input type="radio" class="inpleft" name="commenttype" value="arts" /><label class="llb">Arts</label>\n\
      <input type="radio" class="inpright" name="commenttype" value="cycling" /><label class="rlb">Cycling</label><br /><br />\n\
      <input type="radio" class="inpleft" name="commenttype" value="disabled" /><label class="llb">Disabled</label>\n\
      <input type="radio" class="inpright" name="commenttype" value="horses" /><label class="rlb">Horses</label><br /><br />\n\
      <input type="radio" class="inpleft" name="commenttype" value="bus" /><label class="llb">Bus</label>\n\
      <input type="radio" class="inpright" name="commenttype" value="train" /><label class="rlb">Train</label><br /><br />\n\
      <input type="radio" class="inpleft" name="commenttype" value="sport" /><label class="llb">Sport</label>\n\
      <input type="radio" class="inpright" name="commenttype" value="litter" /><label class="rlb">Litter</label><br /><br />\n\ ';

  var blockComplexRadio = '<input type="radio" class="inpleft" name="commenttype" value="default" checked="checked" /><label class="ll">General</label>\n\
      <input type="radio" class="inpright" name="commenttype" value="points" /><label class="rl">Points</label><br /><br />\n\
      <input type="radio" class="inpleft" name="commenttype" value="lines" /><label class="ll">Lines</label>\n\
      <input type="radio" class="inpright" name="commenttype" value="polygons" /><label class="rl">Shapes</label><br /><br />\n\ ';

  var blockLL = '<input type="text" id="hidlat" size="11" value=' + LL.Latitude + ' autocomplete="off" />\n\
      <input type="text" id="hidlong" size="12" value=' + LL.Longitude + ' autocomplete="off" /><br />\n\ ';
  var editBuffer = '<input type="text" id="buffer" size="6" value="0.0" />Buffer \(metres\)<br />\n\ ';

  if (isSimple) {
    var btnSimpleSubmit = '<input type="button" value="Submit Comment" id="submitPin" onclick="submitPin(\'' + shapeID + '\', document.forms[\'file_upload_form\'].file.value); document.forms[\'file_upload_form\'].submit();" />\n\ ';
    var btnSimpleClear = '<input type="button" value="Clear Pin" id="clearPin" onclick="clearPin(\'' + shapeID + '\');" />\n\ ';
  } else {
    var btnComplexSubmit = '<input type="button" value="Submit Comment" id="submitPin" onclick="submitGeom(\'' + shapeID + '\');" />\n\ ';
    var btnComplexClear = '<input type="button" value="Clear Pin" id="clearPin" onclick="clearPin(\'' + shapeID + '\');" />\n\ ';
  }

  // May not be required but remember /div
  var blockEnd = '<input type="hidden" id="hiddennotes" name="copynotes" value="" /> \n\
        </div>';


  // see PHPMySQL pg 407 'name' is passed to PHP
  // http://www.openjs.com/articles/ajax/ajax_file_upload/ Need IFrame for Ajax - note ajaxError text not reaching IFrame -->
  var fileUploader = '<form id="file_upload_form" target = "upload_target";" \n\
                 method="post" enctype="multipart/form-data" action="php/login/upload.php"> \n\
  Upload a JPG Image. Browse to select file<br /> \n\
  <input name="userfile" id="file" size="18" type="file" /><br /> \n\
  </form>';

/*
var fileUploader = '<input name="userfile" id="commentfile" size="20" type="file" /><br /> \n\
  <input type="button" name="action" value="Upload" onclick="$(\'file\').value = $(\'commentfile\').value; alert ($(\'file\').value); document.forms[\'file_upload_form\'].submit();" /><br /> \n\
  <br />';
 */
// C:/WebPages/ParthianWeb/crunched/icons/bus.png  "alert($(\'commentfile\').value); $(\'file\').value = $(\'commentfile\').value; alert ("File " + $(\'file\').value);"
//onsubmit=function() {
  //                document.getElementById('file_upload_form').target = 'upload_target';

  if (isSimple) {
    return mainID + comments + routes + blockCal + blockSimpleRadio + fileUploader + blockLL + editBuffer + btnSimpleSubmit + btnSimpleClear + blockEnd;
  } else {
    return mainID + comments + routes + blockCal + blockSimpleRadio + fileUploader + blockLL + editBuffer + btnComplexSubmit + btnComplexClear + blockEnd;
  }
}


/*
        var xmlpopup = \'<configs><initcfg><name>calendarPopup<\/name><mode>flat<\/mode><multiselect>no<\/multiselect><\/initcfg><\/configs>\';\n\
        if (calendarPopup == null) {\n\
          calendarPopup = new EpochPrime(document.getElementById(\'epochalarm\'),xmlpopup); }\n\

          document.getElementById(\'epochalarm\').style.display = \'none\';  \n\
      <a href="" onfocus="javascript:{var xmlpopup = \'<configs><initcfg><name>calendarPopup<\/name><mode>flat<\/mode><multiselect>no<\/multiselect><\/initcfg><\/configs>\';\n\
        calendarPopup = new EpochPrime(document.getElementById(\'epochalarm\'),xmlpopup);\n\
        document.getElementById(\'startdate\').value = calendarPopup.curDate.dateFormat(\'d-m-Y\'); \n\
        }"> Alarm </a>\n\

        var xmlpopup = \'<configs><initcfg><name>calendarPopup<\/name><mode>flat<\/mode><multiselect>no<\/multiselect><\/initcfg><\/configs>\';\n\
        calendarPopup = new EpochPrime(document.getElementById(\'epochalarm\'),xmlpopup);\n\
        document.getElementById(\'startdate\').value = calendarPopup.curDate.dateFormat(\'d-m-Y\'); \n\
        alert(calendarPopup.curDate.dateFormat(\'d-m-Y\')); \n\
*/

function toggleDateColour (thisBtn) {
  // Calendar Buttons need to change when clicked On
  if (thisBtn.value == " ") {
    thisBtn.value = "#";
  } else {
    thisBtn.value = " ";
  }
}

function highlightPins (day, month, year1) {
  // highlight all pins on this day using dataShapeMapping 4D array
  if (dateShapeMap[year1][month][day] == null) {
    return;
  } else {
    for (var loop = 0; loop < dateShapeMap[year1][month][day].length; loop++){
      // with negative deltaTime for Red but possibly should use deltaTime to get correct colour for that pin
      // but this approach would need colours for past dates and greater than 1 week dates...
      highlightPin (dateShapeMap[year1][month][day][loop], -1); 
    }
  }
}

function highlightPinsLayer (layer) {
  // Layer specific call to highlight, automatically toggles background
  for (var loop = 0; loop < layer.GetShapeCount(); loop++){
    hoverPin (layer.GetShapeByIndex(loop).GetID(), -1); // with negative deltaTime
  }
    // For Clusters getClusters and find 1st shape - decide if it is the appropriate type by layer name????
           var clusterShapes = new Array();
           clusterShapes = layer.GetClusteredShapes(VEClusteringType.Grid);
           //alert("The pushpins can be clustered into " + clusterShapes.length + " shapes.");
//alert (clusterShapes.length);
           if(clusterShapes.length != 0)
             {
             //alert (clusterShapes[0].Shapes[0].GetID());
             //alert (clusterShapes[0].Shape.GetID()); 
              //hoverPin(clusterShapes[0].GetClusterShape(), 999); // GetClusterShape doesn't work - only use in Callback
              //hoverPin(clusterShapes[0].Shapes[0].GetID(), 999);
              //var cluster1Count = clusterShapes[0].Shapes.length;
              //alert("There are " + cluster1Count + " pushpins in the first cluster.");
           }
}

function buildPinIcon (pinID, icon) {
// Set the Point Icon using DIV + CSS styling for greater control
// Perhaps changing the className could be useful for this task (especially highlighting)
//     http://www.howtocreate.co.uk/alpha.html -- ImageLoader needs the src as parameter
var currentMarkerID = pinID + '_icon';
var currentPinHTML = pinHTML.replace('{0}', currentMarkerID); // pinHTML is global var
//var bground = "background-image: url(icons/" + icon + ".png) ";
// Assign new class to replace dummy - this must be the image mapping classes
return currentPinHTML.replace('dummy', "pinStyle " + "icon" + icon);
}



function showPicture (username, leafletName) {

/*  var image = new Image();
image.onload = function(){
    var width = this.width;
    var height = this.height;
alert (width, height);    // do something with width and height
};
image.src = "http://parthian.webfactional.com/data/" + username + "/" + leafletName;
  */

  picture_object = new Image();
  // First setup Event handler (before setting src that will fire event).
  // Must use onload not onLoad for some reason... but onLoad did fire event when i.onLoad=theFunc();  method was used
  // cannot pass parameter unless the fancy function(e) method is used
  // function(e) { thefunction(e); return false; }; // where return false removes param from memory (I think)
  picture_object.onload = function (leafletName) {displayPicture(leafletName); return false; };
  picture_object.src= DATAPATH + username + "/" + leafletName;
}

function displayPicture(leafletName) {
  var leafletDIV;
  if (picture_object.height > 600) {
    picture_object.height = 600;
  }

  if (picture_object.width > 800) {
    picture_object.width = 800;
  }

  var boxWidth = picture_object.width + 90; // space for Close button if a small image
  var boxHeight = picture_object.height + 20;

  leafletDIV = "<div class='xmlsave-box' id='foldout-box' style='z-index:1000; position:absolute; height:" + boxHeight + "px; width:" + boxWidth + "px; top:30px; left:70px;'>\n\
<img id='page3' height='" + picture_object.height +  "' width='" + picture_object.width +  "' src=" + picture_object.src + " style='position:absolute; top:10px; left:55px; display:block;' />\n\
<button type='button' style='position:absolute; z-index:1001' onclick='javascript:close_popup(\"foldout-box\");'>Close</button>\n\
</div>";

  var obj = document.getElementById('leaflet');
  if (leafletName != "") {
    obj.innerHTML = leafletDIV;
    obj.focus();
  }
  obj.style.visibility = "visible";
  obj.style.display = "inline";
  //setupFoldOut(pageWidth);
  show_popup("foldout-box", "");
}


function dateUsage (dateCal, pinID, dateArray) {
  // processing required to setup calendar
  dateArray.push(dateCal.d); // Add date object updated with Epoch information into the array to send to Calendar
  // To Filter by Date create a structure which stores all pins on a specific day - used by Calendar
  createDateShapeMapping(pinID, dateCal.day, dateCal.month, dateCal.year1);
  // Less than a week to go (may be sometime yesterday as deltaTime is exactly when pin was submitted) - used for Emergency Highlight
  // Assume 1st second (1000) is reserved
  if (dateCal.deltaTime > 1000 && dateCal.deltaTime < dateCal.oneWeek) { // (Math.abs ( was used to do +/- 1 week - just do future now
    // Set the Calendar Switch to ON for these dates
    highlightPin(pinID, dateCal.deltaTime); // so show the warning if Pin is set for on or around + 1 week
    dateCal.d.cellHTML = '<input type="button" value="#" onclick="highlightPins(\'' + parseInt(dateCal.day) +  '\', \'' + parseInt(dateCal.month) +  '\', \'' + parseInt(dateCal.year1) +  '\'); toggleDateColour(this);"></input>';
  }
  else { // create the button without the #
      dateCal.d.cellHTML = '<input type="button" value="" onclick="highlightPins(\'' + parseInt(dateCal.day) +  '\', \'' + parseInt(dateCal.month) +  '\', \'' + parseInt(dateCal.year1) +  '\'); toggleDateColour(this);"></input>';
  }
}

function DateCalendar (datetime) {
  // Object to cope with Dates with respect to updating the Calendar
  // Constructor - good info here http://www.howtocreate.co.uk/tutorials/javascript/objects
  // Publicly set Properties
  var dNow = new Date();  // need a new Date object each time otherwise all entries in array share same object
  dNow.setTime(datetime); // the time from the database
  this.d = dNow;
  // Internally calculated Private Properties use var not this - perhaps these are really Setter Methods????
  var todayIs = new Date().getTime(); // Now...
  this.deltaTime = this.d.getTime() - todayIs;
  this.oneWeek = 86400000 * 7; // Look a week ahead
  this.days30 = 86400000 * 30;
  this.days60 = 86400000 * 60;
  this.days90 = 86400000 * 90;
  this.day=(this.d.getDate()).toString();   // TODO want strings here as this may be best for Associative Array in DateShapeMapping
  this.month=(this.d.getMonth() + 1).toString();  // I think months start from 0
  this.year1=(this.d.getFullYear()).toString();
  this.theDate = " for " + this.day + "/" + this.month + "/" + this.year1;
  // Now update the Calendar with all links to these downloaded points
  this.d.title = "Highlight specific Pins";
  this.d.type = "normal"; // Possibly required or dates cannot be cleared from Calendar
  this.d.cellHTML = ""; // create the property but populate outside constructor
  // Now add any methods if required
}


/* An attempt at day/monthyearpin JSON
  var dSM = {'ds':
              [{'2008':
              [
                {'September':[{'1':pinID}]},
                {'August': [{'30':pinID,'31':pinID}]}
              ] } ] };
  */


function createDateShapeMapping (pinID, day, month, year1 ) {
  // Multidimensional Array - not quite Associative as input currently integers ie 1, 08, 2008
  if (dateShapeMap == null) {
    dateShapeMap = [];
  }
  // an Object
  if (dateShapeMap[year1] == null) {
    dateShapeMap[year1] = new Array(); }
  if (dateShapeMap[year1][month] == null){
    dateShapeMap[year1][month] = new Array();}
  if (dateShapeMap[year1][month][day] == null) {
    dateShapeMap[year1][month][day] = new Array(); }
  // All the pins on the same day/month/year stored together in one array
  dateShapeMap[year1][month][day].push (pinID);
}

function getRadioChecked(commentType) {
  //
  var radios = document.getElementById(commentType); // Id of containing div
  var buttons = radios.getElementsByTagName("input");  // now search only within that div for inputs
  for (var i = 0; i < buttons.length; i++) {
    if ( (buttons[i].type == "radio") &&  (buttons[i].checked)) {   // some of the inputs may not be the radios
      return buttons[i].value;  }
  }
  return "default"; // should never get here
}


function hideEachShape()
{ // Make invisible until searched for
  dataLayer.Hide();
/*for(var i=0;i<dataLayer.GetShapeCount();i++)
  {
    var shape = dataLayer.GetShapeByIndex(i);
    shape.Hide();
  }
  */
}


function toggleLayerCheckboxes (box, DIVid) {
  $(DIVid).select('[type=\'checkbox\']').each(function(s)
    {
    if (box.checked==false) {
       s.checked = true;
    } else {
       s.checked = false;
    }
    s.onclick();
    });
}


function getClicked(e) {
if (!e) {
  e=window.event; } // IE }

if (e.target) {
  clicked=e.target; // FF }
} else {if (e.srcElement) {
  clicked=e.srcElement; } // IE
}

if (clicked.nodeType==3) {  // defeat Safari bug
  clicked = clicked.parentNode; }
return clicked;
 }

function photoViewer (target) {
  //  Update the DOM such that the Image is created or updated - floating on the map in some way
  photo = document.getElementById("photo");
  photo.style.visibility="visible";
  photo.style.display="block";
  preLoadImage.src = "images/" + target;  // start fetching next picture - TODO Perhaps make this the full path...
  // Fade out the last photo and wait until that is complete
  downer = 100;
  fadeToGrey(); // Return immediately but wait below before new photo is pulled in
  delay = setTimeout ("newPhoto()", 1000); // Now Create the newPhoto
  // if both images need to be displayed (one fading out the other fading in) then 2 DIVs will be needed
}


function pausemillis(millis)
{
// www.sean.co.uk
var date = new Date();
var curDate = null;
do { curDate = new Date(); }
while(curDate-date < millis);
}

// ---------------   Movie Section ---------------------------

// This function will run every time the Media changes in Windows Media Player
function wmpMediaChange(item) {

     // alert the data for convenience
     //alert ( moviesID.currentMedia.name );
     //alert ( moviesID.currentMedia.sourceURL );
     // alert (moviesID.currentMedia.duration);
}

// This function will run every time the Play State changes in Windows Media Player
// Called by events setup in HTML page (IE and FF have own technique)
function wmpPlayStateChange(newState) {

// alert the play state value in plain text for convenience
 // Array("Undefined","Stopped","Paused","Playing","Scan Forward","Scan Reverse","Buffering","Waiting","Media Ended","Transitioning","Ready","Reconnecting");

  videoStatus += playstateValues[newState] + "\n "; // + moviesID.currentMedia.duration + "\n";
  if ((playstateValues[newState] == "Playing") || (playstateValues[newState] == "Stopped") ) {
    //alert (videoStatus);
    videoStatus = "";
  }

  if (playstateValues[newState] == "Stopped")  {
    //alert (playstateValues[newState]);
    moviesDOM.style.left = "-500px"; // video finished so hide it
    looper = 0;
   }
}

function restartMoveHandler(eve) { // not used this time
  moviesDOM.style.left = "-500px"; // video finished so hide it
  moviesID.settings.rate = 1.0;    // ensure going forward
  //alert (videoStatus);
  videoStatus = "";
  moveHandler (eve);               // pretend nothing has happened - actually returning the clickedIcon copy? of eve
}

function autoStepFinish (halt) {
  // need to stop the timer
  clearTimeout(autoStepTimer);
  if (halt) { // Pausing so save values
    autoFacingInward = facingInward; // remember the state when halted
    autoNearestNodeIndex = nearestNodeIndex;
    autoRelationIndex = relationIndex;
    autoReverse = reverse;
    autoCurrentNode = currentNode;
    $('restart').show(); // if pausing show restart button
  }

  // Reset button - 2 different ways of getting set of elements to work on.
  $('autostepbuttons').childElements().each(function (el) {el.enable(); });  // can press buttons again
  $('autostepbuttons').childElements().each(function(s)
    {
      if (s.value.indexOf ("Touring") > -1 ) {
        s.value = "AutoWalk - " + s.name;
      }
    });
  $('halt').hide();
}

function autoStepRestart () {
  facingInward = autoFacingInward; // reset to state appropriate for halted position
  nearestNodeIndex = autoNearestNodeIndex;
  relationIndex = autoRelationIndex;
  reverse = autoReverse;
  currentNode = autoCurrentNode;
  restart = true;
  //stepCount = stepCount - 1;  // go back to where we left off
  autoStep (); // Pic up with same XML and step count...
}

function autoStep() {
  $('restart').hide();
  $('halt').show();
  if (stepCount < stepMax) {

    /* Debug - need to add to HTML before reuse
    var button = document.getElementById(steps[stepCount].getAttribute('id'));
    replaceText ($('stepFeedBack1'), steps[stepCount].getAttribute('id'));
    if (button)
      replaceText ($('stepFeedBack2'), button.id);
    else
      replaceText ($('stepFeedBack2'), "nullified");
    replaceText ($('stepFeedBack3'), facingInward);
    */

    //button.style.top = "30px";
    moveHandler ("fakedStart", steps[stepCount].getAttribute('id'), steps[stepCount].getAttribute('name') );
    stepCount = stepCount + 1;
    autoStepTimer = setTimeout ("autoStep()", 6000);
    }
  else {
    autoStepFinish (false);
  }
}

function autoWalker (fname) {
// Automatically step through data structure of ID and Name of buttons at set intervals

// steps[0] = {"id": "-56", "name": "-218"};

var xmlSteps = ajaxGetXML ("data/" + fname); // read XML using prototype
if (xmlSteps == null) {
  autoStepFinish (false);
}
steps = xmlSteps.documentElement.getElementsByTagName ('autowalk'); // turn into array (global)
stepMax = steps.length;
stepCount = 0;

// Reset these in cases where user has clicked to switch routes where still e.g. in reverse
if (currentNode == steps[stepCount].getAttribute('id') ) {
  relationIndex = 99999; // A false cN to force crossing
} else {
  currentNode = steps[stepCount].getAttribute('id'); // 1st launch needs correct currentNode
}
autoStep();
}

function captureStepsModeToggle () {
  if (captureStepsMode) {
    captureStepsMode = false;
    storeSteps();
  } else {
    captureStepsMode = true;
    //captureSteps();
  }
}

function captureSteps (id, name) {
  // To store the steps required to get around
  //Create a JSON Object and appeand each pair into object - presumably as an array
  if (stepsJSON == null ) {
    stepsJSON = {};  // new object
    stepsJSON.steps = []; // Array within object
  }
  var temp = {"id": id, "name": name};
  stepsJSON.steps.push(temp);
}

function storeSteps () {
  // and then send to PHP as JSON
  //var temp = stepsJSON.toJSON();   //toJSONString();
  var temp = Object.toJSON (stepsJSON);
  alert (temp);
  var request = new Ajax.Request(
  "php/login/storesteps.php",
  {
    method: 'post',
    parameters: {steps: temp.toString() },
    onSuccess: function (theReturn) {
      var response = theReturn.responseText;
      alert ("Steps Stored " + response);
      },
    onFailure: function (theReturn) {
      // the Header Response is populated with 400 (200 is success) and "Status: some error stuff"
      var response = theReturn.responseText || "No Error Message";
      alert ("Submit Steps Failed " + theReturn.getResponseHeader("Status"));
    }
  });


}


function nowPlay() {
  moviesID.controls.play();
}

function playMovie (showMe) {
  // This is the code used for HotSpot video playing
  // A simple play through for hotspots etc
  // Fade out the last photo and wait by interval until that is complete
  //moviesDOM.style.zindex="202"; // FF3.02 video plays underneath faded out picture
  downer = 100;
  moviesID = document.getElementById("Player");
  moviesDOM = document.getElementById("movies");
  if (browser == "IE") {
    moviesDOM.style.bottom = "0px"; // simple solution to IE hiding icons CHANGE from 70px to 10px 11/1/09
    // http://msdn.microsoft.com/en-us/library/bb249373(VS.85).aspx  Only set at design time and upsets Vista!!!
    // TESTING 10thMay09 - Apparently bad for Vista - uimode may be replacement
    // moviesID.windowlessVideo = true; // doing this here instead of object appears to help IE but break FF
    moviesID.stretchToFit = true; // make it fit window
  }
   //moviesDOM.style.cssText = 'display:block; visibility:visible; position:absolute; bottom:40px; left:10px; z-index:200';

  moviesDOM.style.left = "0px"; // video was tucked away out of sight - need to do this here or FF gets upset
  moviesID.uiMode = "mini";  // WAS none  -  this worked as FF param but not IE param - vital for IE here
  theImage = (window.location.href).substring(0, (window.location.href).lastIndexOf("/")) + "/images/" + showMe;
  moviesID.URL = theImage; //"http://plugindoc.mozdev.org/testpages/ffwmv11.php";
  setTimeout ("nowPlay()", 50);
  //moviesID.controls.play();   // in FF the first time around the play doesn't work unless the entire path is provided
  }

// ------------------- End Movie Section -------------------------



function newPhoto() {
  photo.src = preLoadImage.src; // This was set at start of Preload image - should be in memory by now...
  theImage = preLoadImage.src;  // may need to set this string to keep everything else in sync

  // If function(e) with onclick to ensure event is captured at the time
  // of the onclick and passed through via a parameter to the actual function
  // NOTUSED bu useful for hotspots on photos... perhaps with imgmap
  // photo.onclick = function(e) { moveHandler(e); return false; };
  looper = 0;
  fadeFromGrey(); // Now make the photo slowly appear
}

function stopuptimer() // TODO - sort this out - pass param or not?
{ clearTimeout(uptimer); }
function stopdowntimer(downt)
{ clearTimeout(downt); }


function fadeToGrey() {
  if (downer >= 0) {
     photo.style.filter = "alpha(Opacity=" + downer + ")";
     photo.style.opacity = downer/100;
    downer = downer - 10;
    downtimer = setTimeout ("fadeToGrey()", 100);
    }
  else {
    stopdowntimer(downtimer);
  }
}

function fadeFromGrey() {
  if (looper < 110) {
      photo.style.filter = "alpha(Opacity=" + looper + ")";
      photo.style.opacity = looper/100;
    //photo.style.filter = alpha(opacity=20, finishopacity=100, style=1, startx=0,
    //starty=0, finishx=140, finishy=270) // Can highlight different areas of the image but how to do same in FF?
      // this technique from http://blog.sanfordsworld.com/?p=10 - replaces photo.style.filter = "opacity=" + looper/100;
      // Note that FF normally needs 0-1 values but here 0-99 works but problem at 100...
      // photo.style['opacity'] = '.' + ((looper==100) ? "99": looper);  // but flashes so replace with easier...
    looper = looper + 10;
    uptimer = setTimeout ("fadeFromGrey()", 100);
    }
  else {
    stopuptimer(uptimer); }
}

function findMatch (startCount) {
  var count = startCount;
  var matchCount = 0;
  while (nodeDBIndex[nearestNodeIndex][count] != null) {
    if (relationsLUT [nodeDBIndex[nearestNodeIndex][count].relationRef] == relationIndex) {
      matchCount = count;
      }
    count++;
    }
  return matchCount;
}
                                  // TODO passed in but not used
function angleChanger (startCount, invertedAngle, matchCount) {
  if (startCount != matchCount) {
    var baseAngle = 0 - nodeDBIndex[nearestNodeIndex][matchCount].angle;
    var angle = 180 + nodeDBIndex[nearestNodeIndex][startCount].angle + baseAngle;
    if (angle < -180) {
      {angle += 360;} }
    if(angle > 180) {
      {angle -= 360;} }
    return angle;
    }
  else {
    return invertedAngle = 0 - nodeDBIndex[nearestNodeIndex][matchCount].angle; }
  }


function moveHandler (eve, autoid, autoname)
{
// The main code to traverse the Index of Nodes and Ways
  var relation;
  var nodeStart;
  var nodeEnd;
  var photoDirection = ""; // use photo or photorev
  var photoNode; // Holds the node which needs to be displayed
  var count = 0;
  var tempDetails = new Array(); // hold the Objects with Index Details
  var invertedAngle = 0;
  var invertedApproach = false;
  var crossing = false;
  var localLocation; // to store tooltip
  var firstRelationIndex = Math.abs(firstRelationRef);

  // Remove the Feedback Line Route Highlighter - do it here for case where moving across node
  if (feedbackPinID) {
    var shapeHighlighter = map.GetShapeByID(feedbackPinID);  // get the pin
    map.DeleteShape (shapeHighlighter);
    feedbackPinID = "";
  }

// get current Relation in order to update photo as a result of users click requirements.
  var clickedIcon;
  if (eve == "fakedStart") { // Allows fakedStart and AutoWalk
    clickedIcon = document.createElement ('span');
    clickedIcon.id = autoid;
    clickedIcon.setAttribute('name', autoname);
  } else {
   clickedIcon = getClicked (eve);
   if (captureStepsMode) {
     captureSteps (clickedIcon.id, clickedIcon.getAttribute ('name'));
   }

  }
  var reversePressed = false;
  if (clickedIcon.getAttribute('id') == 'reverse') {
    reverse = !reverse; // Operator has turned around
    reversePressed = true;
  }
//alert ("cI.id" + clickedIcon.id + "  cI.name " + clickedIcon.getAttribute('name') + "  rI " + relationIndex + "  nnI " + nearestNodeIndex +
//    "  fI "+ facingInward  + "  SC " + stepCount + "  rev " + reverse);
if (moviesDOM.style.left == "0px") {
    moviesDOM.style.left = "-500px"; // not really using videos just now - ensure out of sight
    // There must be a video playing so get angle from icon (alt) and step video around to view
    // then remove the video - perhaps by shifting left again
    //var angle = clickedIcon.getAttribute('alt'); // String to Int?
    //stepVideo (angle, clickedIcon); // pass the clicked icon details to restart moveHandler when the video has finished
    // note that passing eve itself is undefined in IE (okay in FF). getClicked is required to actually get at the object
    // slight concern that some other windows event could overwrite the contents of clickedIcon... TODO
    //return; // jump out
  }

  if (!firstStart) { // ( (!reverse) && (!firstStart) ) { // if reverse just keep last known values...
    // Remembering relationIndex from last time - see if we have moved to new relation
    // Or user just clicked to Reverse - do not use reverse bool as this may be ongoing reverse movement
    // Mod 1/7/08 Reverse button means rI won't change so no need to pass Relation back from icon
    // but dodgy code TODO as if JS runs LUT is will get a 'reverse' string not an int
    if ((reversePressed) || (relationIndex == relationsLUT [clickedIcon.getAttribute('name')]) ) {
      // the user has moved through current OR has clicked Reverse from either start or end
      // Moving Through Relation can be from start to end or end to start if reversing generally
      crossing = false;
      facingInward = !facingInward; // only true if Crossed Node OR clicked 'Reverse' (but can toggle)
      // We can't be sure which was clicked so find the Object that matches using nNI and rI from previous run.
      count = 0; // The first Index Count is always the reverse entry
      while (relationsLUT [nodeDBIndex[nearestNodeIndex][count].relationRef] != relationIndex ) {
        count++; // move on to next possible route
        } // while

      // For the selected direction way - add its length to the distance travelled
      if (clickedIcon.getAttribute('id') != 'reverse') { // except if reverse arrow pressed
        distanceTravelled += nodeDBIndex[nearestNodeIndex][count].distance;  }
      localLocation = nodeDBIndex[nearestNodeIndex][count].tooltip;

      // Now we can get the currentNode using the correct link - but move to .node
      // to represent movement through the relation (as opposed to .currentNode below)
      // When in reverse the count will be for the 0 Object so node will be the one
      // at the other end of current end of node nnI
      if (clickedIcon.getAttribute('id') != 'reverse') {
        currentNode = nodeDBIndex[nearestNodeIndex][count].node;   }

      // Now point to the Node at either end of the current relation to get directions for this photo
      nearestNodeIndex = Math.abs(currentNode); // - 2; // TODO Cope with -1 to -3 but what if there are more gaps?
      // Need a hack to cope with start Relation
      if (nearestNodeIndex < 0) {
        nearestNodeIndex = 0;   }

      // 1. Where are we? Handled above - also copes with moving in Reverse or toggled direction (no move)
      // 2. What Direction are we now facing. In relation to current relation
      // 3. What Direction Arrows should we now see

      // 2. What Direction are we now facing. In relation to current relation
      // Are we facing towards the nNI or inward Looking to other side of the relation
      // Either due to travelling in Forward/Reverse direction and having just crossed a node
      // (but in that case wouldn't be at this part of code).
      // So can only toggle direction after Reverse pressed (when travelling either forward or back)

      // 3. What Direction Arrows should we now see
      // First figure out if the node we are building directions for is being approached
      // from forward or inverted directions. You have to be on the same Relation as the Return Object
      // to be travelling towards the node normally. This includes -27
      if ((facingInward) || (relationIndex == relationsLUT [nodeDBIndex[nearestNodeIndex][0].currentRelationRef])) {
          invertedApproach = false;
          count = 0;
      } else {
        invertedApproach = true;
        count = 1; // Don't need reverse Node for inverted arrows
        }

      var matchCount = findMatch(count); // find the adjoining relation with same relationRef for inverter

      // loop over all objects to get direction arrows - watch Return Object!
      while (nodeDBIndex[nearestNodeIndex][count] != null) {
        if (!invertedApproach) { // The normal case
          if (facingInward){
            // Start Nodes only have Straight Ahead so set angle to 0
            with (nodeDBIndex[nearestNodeIndex][matchCount]) { // TODO Check this - looks odd - also [0] on next line
              tempDetails [0] = new adjoiningRelation (currentRelationRef, currentNode, relationRef, node, 0, distance, tooltip, route, video);
            }
          } else {
            tempDetails[count] = nodeDBIndex[nearestNodeIndex][count];
            }
          }
        else { // Reverse Direction so angles need to be 'inverted'
          // Special case (probably a neater way) If heading back to Start node break out - don't need any arrows
          if (nodeDBIndex[nearestNodeIndex][0].currentRelationRef == "0" ) {
            break; }
          // Work out angles to other Relations
          if (facingInward) {
            invertedAngle = 0;    // Can only go forward from End of Relation inwards
          } else {
            // use current relation identified by matchCount to work out how to change the angle
            invertedAngle = angleChanger (count, invertedAngle, matchCount);
            }
          with (nodeDBIndex[nearestNodeIndex][count]) {
            if (count == matchCount) {    // Arrow to return to original relation that is input to this node  -- Tooltip now comes from Origin Way - arguably WITH should be same. 12/8/08    // 14/1/09 0 was matchCount fix for reverse feedback
              tempDetails [count] = new adjoiningRelation (nodeDBIndex[nearestNodeIndex][matchCount].relationRef, nodeDBIndex[nearestNodeIndex][0].node, currentRelationRef, currentNode, invertedAngle, distance, nodeDBIndex[nearestNodeIndex][0].tooltip, route, video);
            } else {   // Arrow to go to another direction
              tempDetails [count] = new adjoiningRelation (nodeDBIndex[nearestNodeIndex][matchCount].relationRef, nodeDBIndex[nearestNodeIndex][count].currentNode, relationRef, node, invertedAngle, distance, tooltip, route, video);
            }
          } // with
          if (facingInward) {
            break; }  // just use the 0 Object to get details of return to start so now can quit
          }
        count++; // move on to next possible route
        } // while

    } else { // Crossed to new relation
      crossing = true;
      facingInward = true; // having crossed node must be looking into relation

      // The Relation has changed so we need to update the rI but nNI okay (unless Start From Here)
      relationIndex = relationsLUT [clickedIcon.getAttribute('name')];

      // Look through possible routes from the current node to see if any have been
      // selected by Arrows or by Start From Here Buttons. In the latter case
      // a flag will be set to update the cN and nNI so process can start again
      count = 0;
      while ( (nodeDBIndex[nearestNodeIndex][count] != null) && (relationsLUT [nodeDBIndex[nearestNodeIndex][count].relationRef] != relationIndex ) ) {
        count++; // move on to next possible route
        } // while

      if (nodeDBIndex[nearestNodeIndex][count] == null) { // The requested Relation is nowhere
        // Start From Here must have been pressed so new node
        currentNode = clickedIcon.getAttribute('id');
        nearestNodeIndex = Math.abs(currentNode);
      }

      // We can't be sure which was clicked so find the Object that matches
      // using nNI from previous run or Start From Here
      count = 0;
      while (relationsLUT [nodeDBIndex[nearestNodeIndex][count].relationRef] != relationIndex ) {
        count++; // move on to next possible route
        } // while
      distanceTravelled += 0.01; // TODO Magic Number - Assume Node crossing takes 10 metres
      localLocation = nodeDBIndex[nearestNodeIndex][count].tooltip;
      // We now have the correct Index entry but must overwrite the angle to 0
      // Start Nodes only have Straight Ahead (and possibly Reverse)
      with (nodeDBIndex[nearestNodeIndex][count]) {
        tempDetails [0] = new adjoiningRelation (currentRelationRef, currentNode, relationRef, node, 0, distance, tooltip, route, video);
        }
      }  // else moving through relation
  }  //!reverse


  if (firstStart) {
    relationIndex = relationsLUT [startRelationRef];
    nearestNodeIndex = Math.abs (startRelationNode);   //was 1
    currentNode = startRelationNode; // was '-1';
    firstStart = false;
    crossing = false;
    facingInward = true;
    var falseStart = 1; // the count of ways at that node
    // Only one link from a Start Node - Remember we may have future start nodes
    // where there are multiple entrances to e.g. a park...
    if (nodeDBIndex[nearestNodeIndex][1] == null) {
      falseStart = 0; // Probably due to a fakedEntrance with start set to 2nd Node e.g. SRPS entrance
      reverse = true; // and need to tell system heading back into the map
    }
    with (nodeDBIndex[nearestNodeIndex][falseStart]) { //TODO Magic Number #### AND in next LINE
      // Simply pass on the first genuine links details but with angle set to 0 manually
      // arguably not necessary as this is a manually created index entry...
      tempDetails [0] = new adjoiningRelation (currentRelationRef, currentNode, relationRef, node, 0, distance, tooltip, route, video);
       }
    distanceTravelled += 0; // First node adds no distance
    localLocation = nodeDBIndex[nearestNodeIndex][falseStart].tooltip;  // TODO Magic Number

    // Set the button to Exploring Map - use of name deprecated for document.formname.elements etc
    // using protoype technique to select by ID the button elements and iterate over each to update
    $('layerCheckboxForm').select('[type=\'button\']').each(function(s)
      {
        if (s.value == "Processing - please wait") {
          s.value = "Exploring " + s.name;
        }
      });

    } // if firstStart

  // Get the Relation where numbering matches XML order and is the same for the Index
  relation = relations[relationIndex];

  // get the Start Node in this case from that Relation
  // This has problems with FF assuming /n/n etc is a node... so use function
  // Also note [0] at end as this is an array
  nodeStart = get_nextSibling(relation.getElementsByTagName("member")[0]); // Move past Way
  nodeEnd = get_nextSibling(nodeStart);

  // Toggle the Reverse direction if required but only at a crossing of a node
  // but not when reverse has been pressed (the direction will already be correct)
  // and this change would negate the change by the user
  if ((crossing) && (clickedIcon.getAttribute('id') != 'reverse') ) {
  //if (restart || ((crossing) && (clickedIcon.getAttribute('id') != 'reverse') )) {
    restart = false;
    if (currentNode == nodeStart.getAttribute('ref')) {
      reverse = false;
    } else {
      reverse = true;
    }
  }

  if (reverse) { // Toggle Values
    photoDirection = "photorev";
  } else {
    photoDirection = "photo";
  }
  // but which node are we at to determine which photo to show
  if ( nodeStart.getAttribute('ref') == currentNode ) {
    photoNode = nodeStart;
  } else {
    photoNode = nodeEnd; }

  var theVideo = "";
  if (!facingInward) {  // only show the video if facing the node not inward into relation
    theVideo = nodeDBIndex[nearestNodeIndex][0].video;   }

  // This was done in updatePosn but also need this location for compassLoad - cannot rely on nodeStart - nodeEnd esp for Reverse
  var Index = Math.abs(photoNode.getAttribute("ref")) - 1;
  var Lat = parseFloat(nodes[Index].getAttribute("lat"));
  var Lon = parseFloat(nodes[Index].getAttribute("lon"));
  var newPosn = new VELatLong(Lat, Lon); // TODO VECommand move to VE.js

  updatePosnAndPhoto (photoNode, newPosn, photoDirection, theVideo ); // could be "photorev"
  displayFeedback (distanceTravelled, localLocation);
  // Now update the Compass and the Segment now that we are at the new posn.
  if (reverse) {
    compassLoad (nodeEnd.getAttribute("ref"), nodeStart.getAttribute("ref"), newPosn, crossing, reversePressed);
  } else {
    compassLoad (nodeStart.getAttribute("ref"), nodeEnd.getAttribute("ref"), newPosn, crossing, reversePressed);
  }

  // Now Add the Arrows - one per Count Object -
  // All StartNodes have a Straight Ahead and a Reverse is added in direction func
  // Pass in nodes for CreateFeedback to know which way we are going 14/1/09 tempDetails in reverse currentNode may be wrong
  direction (tempDetails, nodeStart.getAttribute("ref"), nodeEnd.getAttribute("ref"));

} // moveHandler

function compassUpdate(angle) {
  angle = Math.round (angle / 10) * 10;
  if (angle == 360) { angle = 0; }
  // Create a Table to Associate headings with Compass Cardinals
  var cardinal = "N";
  switch (true) {
  case (angle < 23): cardinal = "North"; break;
  case (angle < 68): cardinal = "North East"; break;
  case (angle < 113): cardinal = "East"; break;
  case (angle < 158): cardinal = "South East"; break;
  case (angle < 203): cardinal = "South"; break;
  case (angle < 248): cardinal = "South West"; break;
  case (angle < 293): cardinal = "West"; break;
  case (angle < 338): cardinal = "North West"; break;
  case (angle < 360): cardinal = "North"; break;
  default:
  break;
}
  replaceText ($('compassElement'), angle);
  replaceText ($('cardinalElement'), cardinal);
  // Now using CSS Image Cropping
  //  //<span id="compBear" class="compass comp0"></span>
  //  $('compBear').setAttribute("className", "compass comp" + angle);
  $('compBear').className="compass comp" + angle; // Don't use setAttribute for classname - fails in FF
}


function destPointRhumb(lat, lon, brng, dist) {
  var R = 6371; // earth's mean radius in km
  var d = parseFloat(dist)/R;  // d = angular distance covered on earth's surface
  var lat1 = lat.toRad(), lon1 = lon.toRad();
  brng = brng.toRad();

  var lat2 = lat1 + d*Math.cos(brng);
  var dLat = lat2-lat1;
  var dPhi = Math.log(Math.tan(lat2/2+Math.PI/4)/Math.tan(lat1/2+Math.PI/4));
  var q = (Math.abs(dLat) > 1e-10) ? dLat/dPhi : Math.cos(lat1);
  var dLon = d*Math.sin(brng)/q;
  // check for some daft bugger going past the pole
  if (Math.abs(lat2) > Math.PI/2) lat2 = lat2>0 ? Math.PI-lat2 : -(Math.PI-lat2);
  lon2 = (lon1+dLon+Math.PI)%(2*Math.PI) - Math.PI;

  if (isNaN(lat2) || isNaN(lon2)) return null;
  return new LatLon(lat2.toDeg(), lon2.toDeg());
}

function compassLoad (nodeStart, nodeEnd, posn, crossing, reversePressed) {
  // Round compass rose to 10 degrees - This is True angle (at least as far as Projections allow...)
  // Not the angle in the Index which is for relative turns ie left is -90 but could result in travelling north
  var snsIndex = Math.abs(nodeStart) - 1; // only required for -1, -3 etc
  var snsLat = parseFloat(nodes[snsIndex].getAttribute("lat"));
  var snsLon = parseFloat(nodes[snsIndex].getAttribute("lon"));

  var sneIndex = Math.abs(nodeEnd) - 1;
  var sneLat = parseFloat(nodes[sneIndex].getAttribute("lat"));
  var sneLon = parseFloat(nodes[sneIndex].getAttribute("lon"));

  var angle = angleOfAttack (snsLat,snsLon,sneLat,sneLon);
  // Draw a cone pointing from current location out to 50 metres with a width of 30 degrees (15 either side)

  if (!crossing ) { // Must be travelling along a leg so stop short by adjusting posn CHECK THIS Reverse not quite right
    if (!reversePressed) {
    // Code from - http://www.movable-type.co.uk/scripts/latlong.html
    // θ = (θ+180) % 360
    // Reverse angle then move back along path by 10 metres
    var ll = destPointRhumb (sneLat, sneLon, (angle+180) % 360, 0.01);
    posn.Latitude = ll.lat;
    posn.Longitude = ll.lon;
    }
  }
  // Make a Segment of 50 metres or so at zoom 18 - calculate for other zooms via a table
  var radius = new Array(0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.3, 0.2, 0.1, 0.075, 0.05, 0.05);
  AddSegment(posn, radius[ZOOMLEVEL], angle, 15);
  compassUpdate (angle);
  return angle; // Return angle required for CreateFeedback - Accurate angle - not rounded to 10 degrees
}



function displayFeedback(distanceTotal, localLocation)
{ // show Location as well as distance
  // convert to round metres
  replaceText($("distanceNode"), parseInt(distanceTotal * 1000)); // text-utils.js
  replaceText ($("whereami"), localLocation);
}


function updatePosnAndPhoto (node, newPosn, photoDirection, video) {

  setTimeout (function(){moveLocation (newPosn); newPosn = null}, 2000); // move after a delay so photos are finished

  // Photos can be replaced with videos for Relations OR special Node Videos can exist
  // This code is designed to cope with a Relation video then a Node tagged video then the normal picture
  // Possible confusion if Relation Video AND a Node Tag Video - only former will run....TODO sort out logic
  // Also watch string != ""
  // THIS VIDEOS ARE NOT USED AS PART OF THE NORMAL MOVEHANLDING - THIS COULD BE USED TO LAUNCH QUICKTIME PANORAMA
  // So the photoViewer Code only will run
  if ((node.getAttribute(photoDirection)).indexOf(".wmv") > 0)  {
    initMovie(node.getAttribute(photoDirection));
    mediaDIV = document.getElementById("icons"); // 21/6/08 now using icons separately to make movie clicking easier but keep this code in case need to return
    } else {
      if (video != "") {
        initMovie(video);
        mediaDIV = document.getElementById("icons"); // note sure why mediaDIV needs to be repeated but removal caused problems
      } else {
        photoViewer(node.getAttribute(photoDirection));  // Pass in the photo jpg to display
        mediaDIV = document.getElementById("icons"); //
      }
    }
}

function showfeedbackBox(theText){
  fBox = document.getElementById("feedback");
  fBox.style.visibility="visible";
  fBox.style.display="block";
  fBox.style.position="absolute";
  fBox.style.top = "0px";
  fBox.style.color = "yellow";
  replaceText(fBox, theText); // text-utils.js
  }

function hidefeedbackBox(){
  fBox = document.getElementById("feedback");
  fBox.style.visibility="hidden";
  fBox.style.display="none";
  }



function direction (tempDetails, startRef, endRef) {
  // Take the segments sharing a point in turn and calculate the angle and number.
  // Based on this determine the available directions to click to advance
  // Forward/Back/Left/Right etc.
  // Sort the array of objects based on the angle - now from left to right
  tempDetails.sort(function (a,b){return a.angle - b.angle});
  var gifArray = new Array();
  for (var p = 0; p < tempDetails.length; p++) {
    if (tempDetails [p] == null) { continue; } // Sometimes undefined passed in

  if ((tempDetails[p].angle < -170) && (tempDetails[p].angle != -180)) { // Special case  TODO What happens with exactly -180 or 180???
      gifArray [p] = "arrD";
    } else { if (tempDetails[p].angle < -120) {
      gifArray [p] = "arrDL";
    } else { if (tempDetails[p].angle < -60) {
      gifArray [p] = "arrL";
    } else { if (tempDetails[p].angle < -20) {
      gifArray [p] = "arrTL";
    } else { if (tempDetails[p].angle < 20) {
      gifArray [p] = "arrUp";
    } else {if (tempDetails[p].angle < 60) {
      gifArray [p] = "arrTR";
    } else {if (tempDetails[p].angle < 120) {
      gifArray [p] = "arrR";
    } else { if (tempDetails[p].angle < 170) {
      gifArray [p] = "arrDR";
    } else { if (tempDetails[p].angle < 180) {  // Angle of exactly 180 is special case
      gifArray [p] = "arrD";
    } } } } } } } } }

  } // for

  gifArray [gifArray.length] = "arrRev"; // The special extra Reverse

  // Now add these icons to the photo - Remove old links
  var theIMG = mediaDIV.getElementsByTagName ('span');
  while (theIMG.length > 0) { // There will always be the photo - only remove icons - 21/6/08 now using icon div so no photo
    //clearText(mediaDIV.lastChild); // Remove text from IMG
    removeLocation ();
    mediaDIV.removeChild (mediaDIV.lastChild);  //TODO doesn't work in IE. August - not sure this is true...
    }

  var offset = gifArray.length / 2 * 32; // ##################################
  var newIcon; // Array of Icon Images

  for (var c = 0; c < gifArray.length; c++) {
    icon = document.createElement ('span');  //("<IMG width='16' height='16'>"); // http://msdn2.microsoft.com/en-us/library/ms536389.aspx
    if (gifArray[c] == "arrRev") {
      icon.setAttribute ('id', 'reverse');    // Reverse button detected by id or name
      icon.setAttribute ('name', 'reverse');   // Was 0 - (relationIndex + Math.abs(firstRelationRef) )); but relationIndex unpredictable now
    } else {
      icon.setAttribute ('id', tempDetails[c].node );
      icon.setAttribute ('name', tempDetails[c].relationRef ); // name so that getElementsByName can be used
      }
    icon.className = "arrows " + gifArray[c];

    // August 5th add alt to hold actual angle to be used by video stepper - note consider use of longdesc for similar
    if (gifArray[c] != "arrRev") {
      icon.setAttribute ('alt', tempDetails[c].angle);  // TODO Note - c and p could get out of sync see tD[p] == null above
    } else {
      icon.setAttribute ('alt', 180); // must be reverse
    }
    posn = 150 - offset; // MAGIC NUMBER TODO

    // The following only works in FF
    //icon.setAttribute ('style', "display:inline; position:absolute; bottom:0px; left:" + posn + "px; z-index:203");
    // so see this for alternative
    //http://www.quirksmode.org/bugreports/archives/2005/03/setAttribute_does_not_work_in_IE_when_used_with_th.html
    icon.style.cssText = 'display:inline; position:absolute; bottom:0px; left:' + posn + 'px; z-index:204';
    mediaDIV.appendChild(icon);

    // Try and Repeat newPhoto technique - not sure why setAttr process above doesn't work'
    // Chaining the getElement type calls did not work... perhaps incorrect combination

    // When clicked make a move to next location
    newIcon = $(icon.getAttribute('id') );
    newIcon.onclick = function(e) { moveHandler(e); return false; };

    // Display Way on hover
    if (gifArray[c] != "arrRev") {
      // Need to pass variable to Referenced Function - this is difficult - see the following
      // http://www.howtocreate.co.uk/referencedvariables.html
      // why is this function not a Referenced function like the others?
            //icon.setAttribute ('onclick', 'function (window.event) {navigate(e); return false;}'); //'navigate(window.event)'); //'function (e) {navigate(e); return false;}');
      //newIcon.onmouseover = function (e, tempDetails[c].tooltip) { displayLocation(e, tempDetails[c].tooltip) };
      newIcon.onmouseover = displayLocation(tempDetails[c].tooltip, tempDetails[c].currentNode, tempDetails[c].node);
      newIcon.onmouseout = removeLocation(); // function (e) {removeLocation(); return false; };
    }
    else { // tempDetails[0] is always turnaround - but don't draw feedback highlighter as it is unpredicatable
      newIcon.onmouseover = displayLocation("Turn Around", startRef, endRef);
      newIcon.onmouseout = removeLocation(); // function (e) {removeLocation(); return false; };
    }
    offset -= 32;
    } // for

// Some good ideas for events
//http://yuiblog.com/blog/2007/01/17/event-plan/
}

function displayLocation (localLocation, startNode, endNode) {
  // http://www.howtocreate.co.uk/referencedvariables.html
  return function () { // scopePreserver
    // Update the Compass - NO ~It gets too complicated as each way has to be tested to see if it needs
    // to be drawn in reverse - instead use the nodes to draw a background case around line - direction doesn't matter
    //var angle = compassLoad(startNode, endNode); // perhaps need to reverse in some cases???

    showfeedbackBox(localLocation); // Text on Photo
    createFeedback ("feedback", "h3", "location", "10", localLocation, startNode, endNode);
    //alert (startNode + " " + endNode);
    }
}


//<!-- XML Woodland Walker Parser -->
//<!-- Read the osm xml - parse out the photo tags lat/long - draw on map -->
// Perhaps could use Prototype here

  function parseXML (fname)
  // Replaced by ajaxGetXML which uses prototype Ajax so that Safari can work
  {
  var xmlD;
  try //Internet Explorer
    {
    xmlD=new ActiveXObject("Microsoft.XMLDOM");
    browser = "IE";
    } catch(e) {
      try //Firefox, Mozilla, Opera, etc.
      {
           xmlD=document.implementation.createDocument("","",null);
           browser = "FF";
      } catch(e) {
        alert(e.message);
        return null;
        }
    }
    xmlD.async=false; // need to wait until the file is loaded

  //THIS POINTS TO THE DATA FILE
  // For Firefox an extension osm causes the xmlDoc.DocumentElements to be null
  // Also files are relative to where the HTML is i.e. crunched folder in this
  // case. NOT where the javascript happens to be stored.
  // Safari does not have this capability

  xmlD.load(fname); // Caching can be a problem - clear FF cache if this file changes
  return xmlD;
}

function ajaxGetXML (fname) {
// ajaxGetXML which uses prototype Ajax so that Safari can work
var xmlData; // to return data declare out here but amend within onSuccess (and then return at end)
try {
  var request = new Ajax.Request(
  fname,
  {
    method: 'get',
    contentType: "application/xml",
    asynchronous: false, // Need result straight away or nothing works - could cause lockup if problem
    onSuccess: function( transport ) {
      xmlData = transport.responseXML;
    },
    onFailure: function (theReturn) {
      var response = theReturn.responseText || "No Error Message";
      //alert ("Get XML Failed " + theReturn.getResponseHeader("Status"));
      throw new Error('Get XML Failed  - getting ' + fname + " Status: " + theReturn.getResponseHeader("Status"));
    }
  } );
} catch (e) {alert ("Error - Get XML: " + e.message);}
  return xmlData;
}


function getDomAdapter()
// From http://www.alexatnet.com/node/62  to replace parseXML which fails in Safari
// parseXML is for Strings of XML - return from Ajax of contents of XML file is already in XML object so not required
{
        var adapter = '';
        if ('undefined' != typeof ActiveXObject) {
                adapter = 'MS';
        } else { if ('undefined' != typeof document
                && document.implementation
                && document.implementation.createDocument
                && 'undefined' != typeof DOMParser) {
                  adapter = 'default';
          }
  }
        switch (adapter) {
                case 'MS':
                        return new (function () {
                                this.createDocument = function () {
                                        var names = ["Msxml2.DOMDocument.6.0",
                                                "Msxml2.DOMDocument.3.0", "MSXML2.DOMDocument",
                                                "MSXML.DOMDocument", "Microsoft.XMLDOM"];
                                        for (var key in names) {
                                                try {
                                                        return new ActiveXObject(names[key]);
                                                } catch (e) {}
                                        }
                                        throw new Error('Unable to create DOMDocument');
                                };
                                this.serialize = function (doc) {
                                        return doc.xml;
                                };
                                this.parseXml = function (xml) {
                                        var doc = this.createDocument();
                                        if (!doc.loadXML(xml)) {
                                                throw new Error('Parse error');
                                        }
          return doc;
          /* createDocument.async = false;
          createDocument.load(xml);
                                        return createDocument; */
                                };
                        })();
                case 'default':
                        return new (function () {
                                this.createDocument = function () {
                                        return document.implementation.createDocument("", "", null);
                                };
                                this.serialize = function (doc) {
                                        return new XMLSerializer().serializeToString(doc);
                                };
                                this.parseXml = function (xml) {
                                        var doc = new DOMParser().parseFromString(xml, "text/xml");
                                        if ("parsererror" == doc.documentElement.nodeName) {
                                                throw new Error('Parse error');
                                        }
          return doc;
          /* createDocument.async = false;
          createDocument.load(xml);
                                        return createDocument; */
                                };
                        })();
                default:
                        throw new Error('Unable to select the DOM adapter');
        }
}




function getRouteColours () {
 // Also updates the Key
 // Clear old key
 var liArray = $('routekey').descendants();
 for (var j=0; j < liArray.length; j++) {
      liArray[j].remove();
 }

 // ignoring the routes tag - possibly gElByTName cannot cope if only one tag - just use route
 var routes = xmlAttributes.documentElement.getElementsByTagName("route");
 // Build up a Hash Table [route:lineColour Object;]
 routeInfo = new Array();
 //===========Loop through the array to pull out each of the markers
  for (var i = 0; i < routes.length; i++) {
    // obtain the attributes of each relation
    var route = routes[i];
    var namer = route.getAttribute("namer"); // name is probably a reserved word - caused problems as name
    var colour = route.getAttribute("color");
    var red = (colour & 0xFF0000) >> 16;
    var green = (colour & 0x00FF00) >> 8;
    var blue = colour & 0x0000FF;
    var transparency = route.getAttribute("trans"); // this is "1" or "0.5" etc perhaps should turn into float
    var hull = route.getAttribute("hull");
    lineColor = createColour (red, green, blue, transparency); // VEColor appears to accept string transparent
    routeInfo[namer] = lineColor; // Auto create entry and assign value in one operation - no push
    var hullswitch = namer + "hull";
    routeInfo[hullswitch] = hull; // now add e.g. househull: 1
    var notes = route.getAttribute("notes");
    var routenotes = namer + "notes"; // e.g. Hash = westnotes = unique key
    routeInfo[routenotes] = notes;

    // Update Key - perhaps add unorder list items  - Prototype
    // http://bobobobo.wordpress.com/2008/05/22/the-stupidly-brief/  ADD a Blob of Colour STUPIDLY BRIEF
    //colour = parseInt(colour); colour = colour.toString (16);
    colour = colour.replace("0x","#"); // this suggests strings are not objects that can have methods run on them - this returns another string
    var holdingSpan = new Element ('span', {});
    var keyText = new Element ('span', {}).update(" - " + notes);
    var aSpan = new Element ('span', {}).update("__");
    var btn = new Element ('input', {'class': 'routeOn', 'type':'button', 'value': namer});
    btn.observe('click', toggleRouteAreaLayer.bindAsEventListener(namer));
    btn.setStyle ({'backgroundColor': colour, 'marginRight': '5px'});
    // Prototype Hashes need camelized versions of CSS ( dash removed then Uppercase - no semicolon at end)
    // colour needs to be #FFFFFF not 0xFFFFFF but IE appears to accept either.
    aSpan.setStyle({'backgroundColor': colour, 'marginRight': '5px'});//,opacity : transparency });
    var li = new Element('li', { 'class': 'routestyle'}); // .update(namer + " - " + notes); //"<span id='spankey'>...</span>" +
    li.setStyle({'listStyleType': 'none'});
    var ul = $('routekey');
    holdingSpan.appendChild(btn); // Put in first part of active span
    holdingSpan.insertBefore(keyText, aSpan.nextSibling); // insert next part after 1st
    li.update( holdingSpan ); // list item now contains entire span with 2 children
    ul.appendChild(li); // DOM finally updated

    // handle.bindAsEventListener(obj, namer, 2, 3));
    //Event.observe(aSpan, 'click', toggleRouteAreaLayer.bindAsEventListener(namer)); // 1st param is treated as this
    // Is this technique correct?  http://bytes.com/forum/thread588406.html

  }
}

function addAutoWalkButton (name, id, xmlFile) {
  var value = 'AutoWalk - ' + name;
  var btn = new Element ('input', {'class': '', 'type':'button', 'value': value, 'name': name, 'id': id });
  btn.observe('click', function () {
    this.value='Touring ' + name;
    $('autostepbuttons').childElements().each(function (el) {el.disable(); });
    autoWalker (xmlFile);
  }  );
  $('autostepbuttons').appendChild(btn);

}

function autoWalkButtons (location) {
  // Use prototype techniques to add button elements but only if not already available
  $('autostepbuttons').childElements().each(function (el) {el.remove(); });
  if ( location == "Boness" && $('autowalkS2K') == null) {
    addAutoWalkButton ("Kinneil to Railway", "autowalkK2S", "kinneilToSRPS_autowalk.xml");
    addAutoWalkButton ("Railway to Kinneil", "autowalkS2K", "srpsTokinneil_autowalk.xml");
  }
  if ( location == "Falkirk" && $('autowalkFW2RC') == null) {
    addAutoWalkButton ("Wheel to Roman Fort", "autowalkFW2RC", "FW2RC.xml");
    addAutoWalkButton ("Roman Fort to Wheel", "autowalkRC2FW", "RC2FW.xml");
  }
}


function bindTest (willbethis, willbeevent, willbe1stparam) {
  alert (ul.toSource());
  alert (willbeevent.toString());
  alert (willbe1stparam);
}


// Some alternative code for links
// links[i].addEventListener('click', function() {
//  Update the DOM such that the Image is created or updated - floating on the map in some way
//  var photo = document.getElementById("photo");
//  photo.src="images/" + iconinfo[i];
//  }, false);
//links = links + "<a target='terrestrial' href=images/" + iconinfo[i] + " /a>Terrestrial Image " + i + "<br />";
// links = links + '<a href="javascript:location='SamplePage.html'; window.open('images/'" + iconinfo[i] + "','height=200, width=200')">' + '<br />';


function parseHotSpot (hotspots) {
  var count = 1;
  var hotspot = "";
  var hotHtml = "<div style='width:100%; z-index:301; position: relative;'>";
  var imagery = ""; var keywords = ""; var text = "";
  var www = ""; var wwwPosn = -1;
  var icon = ""; var iconPosn = -1;
  var hash = [];  // Return hash
  while (hotspots["hotspot" + count] != null) {  // If Hotspot available add a Pin
    hotspot = hotspots["hotspot" + count];  //getHotSpotData (tooltip);
    //var parsedHotSpot = parseHotSpot (hotspot);
    // Take the Hash Array and split into text, image, video components
    // Assume that hotSpot will contain image: or video: and then text: in all cases
    // ICONS
    // Should really add a new entry for icon - modify JOSM to do this via ComboBox
    // for now pull it from end of Keywords - only allow one icon per HotSpot... could this be a problem?
    // Could perhaps use type=segmented_tag in OSM - probably not used
    // icon_ text in OSM must be same as route.xml

    if (hotspot.indexOf ("image:") != -1 ) {
      imagery = hotspot.slice (6, hotspot.indexOf(" "));   }  // jump past image: or video: and on to text or keyword
    if (hotspot.indexOf ("keywords:") != -1 ) {   // Keywords must start immediately after : and have no spaces.
      keywords = hotspot.slice (hotspot.indexOf("keywords:") + 9, hotspot.indexOf(" ", hotspot.indexOf("keywords:") + 9));   }
    if (hotspot.indexOf ("text:") != -1 ) {
      text = hotspot.slice (hotspot.indexOf("text:") + 5); }  // text is at the end
    wwwPosn = text.indexOf ("http");
    if (wwwPosn != -1) {
      www = text.substring (wwwPosn); // URL must have www and be at the end of the text
      text = text.substring (0, wwwPosn); // get rid of the www stuff add as MoreURL
    }
    iconPosn = keywords.indexOf ("icon_");
    if (iconPosn != -1) {
      icon = keywords.substring (iconPosn + 5); // icon_ must have www and be at the end of the text
      keywords = keywords.substring (0, iconPosn); // get rid of the icon stuff
    }

    if (text) {
      hotHtml += "<p>" + text + "</p><br />"; }
    if (keywords) {
      hotHtml += "<p>Keywords: " + keywords + "</p><br />"; }
    if (www) {
        hotHtml += "<a target='_blank' href=" + www + ">Web Link</a><br /><br />";  }
    if (imagery) {
      if (imagery.indexOf(".wmv") != -1 ) {
        // A GREAT EXAMPLE of passing a parameter in a function within some embedded HTML
        // note the use of ' ' on outside but onclick="func(\'' + theVAR + '\');" />'   escape the single quotes
        // useful alternative using DOM here http://www.tek-tips.com/viewthread.cfm?qid=1460404&page=2
        hotHtml += '<input type="button" name="PlayMedia" value="Play Video" onclick="playMovie(\'' + imagery + '\');" />';
      } else {
        if (keywords.indexOf("Fullsize") > -1) {
          hotHtml += "<img src='images/" + imagery + "' width='320' height='240'> </img>";
        } else {
          hotHtml += "<img src='images/" + imagery + "' width='160' height='120'> </img>";
        }
      }
    }
    if (text || keywords || imagery) {
      hotHtml += "<br />-------------------<br />";  }
    text = ""; keywords = ""; imagery = ""; wwwPosn = -1; // reset in case of more hot spots on this relation
    count++;
  }
  hotHtml += "</div>";
  hash ['hotHtml'] = hotHtml;
  hash ['iconName'] = icon;
  return hash;
}


function getHotSpotData (tooltip) {
  // Possibly vunerability if HTML read from String ??? TODO
  //var path = "/hotspots/" + tooltip + "/jsonOutput.js";
  // This could be XML/JSON or String or perhaps HTML file but will essentially contain HTML
  //http://betterexplained.com/articles/using-json-to-exchange-data/
  // This adds the required scripts to the header - arguably should remove them when used to prevent bloat TODO
  //path = (window.location.href).substring(0, (window.location.href).lastIndexOf("/")) + path;
  var trimmed = tooltip.trim(tooltip);
  return eval("hotspotjson." + trimmed); // return the JSON data for the specific HotSpot
}


function segLength (lat1,lon1,lat2,lon2) {
// http://www.movable-type.co.uk/scripts/latlong.html
//var R = 6371; // km
//var d = Math.acos(Math.sin(lat1)*Math.sin(lat2) + Math.cos(lat1)*Math.cos(lat2) * Math.cos(lon2-lon1)) * R;
//return d;

  var R = 6371; // earth's mean radius in km
  var d = Math.acos(Math.sin(lat1.toRad())*Math.sin(lat2.toRad()) +
                    Math.cos(lat1.toRad())*Math.cos(lat2.toRad())*Math.cos((lon2-lon1).toRad())) * R;
  return d;
}

function angleOfAttack (lat1,lon1,lat2,lon2) {
// http://www.movable-type.co.uk/scripts/latlong.html
  lat1 = lat1 * Math.PI / 180;
  lat2 = lat2 * Math.PI / 180;
  var dLon = (lon2-lon1) * Math.PI / 180;

  var y = Math.sin(dLon) * Math.cos(lat2);
  var x = Math.cos(lat1)*Math.sin(lat2) -
          Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);

  var brng = Math.atan2(y, x);
  brng = Math.floor (((brng * 180/Math.PI) + 360) % 360);
  return brng;
}

function adjoiningRelation (sRef, cNode, ref, node, angle, distance, tooltip, route, video)
{ // This is a js Object Template
// Each Relation (max 8) that shares node
this.currentRelationRef = sRef;   // the current relation reference
this.currentNode = cNode;         // Where we are - could be same as index but uses '-1' etc numbering system
// These should really be stored as one per index not one per count per index TODO
this.relationRef = ref;           // The adjoining Relation/Segment Reference number
this.node = node;                 // The Node Reference at end of this Relation
this.angle = angle;               // The Angle between the original Segment and this Segment
this.distance = distance;         // Distance from the previous Segment Start to this.node
this.tooltip = tooltip;           // The WayName
this.route = route;               // Tag for Route made of lots of ways, Could be array ie Relation is in lots of routes
this.video = video;               // Indicate if this.node (at end of relation) has a video for turning.
// The angle is based on 0 being straight ahead - Left being -90 and right +90 etc

// methods defined like this - with nothing passed in and external function to define it
//this.method=methodWithNoParams; // external function can have params
}


function getStartRelation () {
// RETIRED NOW GET THIS SORTED IN TERRAMAPPER
// Don't know which Relation will have node -1 so find it
// TODO - in the future the start may simply be the Node id that is first in the list
// of nodes. This to cope with data from the real OSM database.
var looper = 0;
var notFound = true;
while ((looper < relations.length) && (notFound)) {
  var relationID = relations[looper].getAttribute("id");
  // No relationtooltip so just find 1st Node in Relations ie entrance to Kinneil
  var members = relations[looper].getElementsByTagName("member");
    // This looping assumes Way/Node order cannot be trusted - not sure this is the case
  for (var mem = 0; mem < members.length; mem++) {
      if (members[mem].getAttribute("ref") == "-1") { // TODO MAGIC NUMBER
        notFound = false; }
      } // for
  looper++;
  } // while
  return relationID;
} // getStartRelation

function getTag (relationIndex, tagname) {
  // get the tooltip attribute tag from the relation XML
  var tags = relations[relationIndex].getElementsByTagName("tag");
  var count = 0;

  // TODO seems clumsy - also what happens if tag missing will 2nd part cause access violation?
  while ((count < tags.length) && (tags [count].getAttribute("k") != tagname) ) {
      count++;
    }
  if (tags[count] == null) {
    return "";
  } else {
    return tags[count].getAttribute("v");
  }
}


function relationMembersParse (membersStore, relationCount) {
  // This looping assumes Way/Node order cannot be trusted - not sure this is the case
  // Work through each of the 2 Nodes in each Relation (Segment)
  for (var memS = 0, cmem = membersStore[relationCount].length; memS < cmem; memS++) {
       if (membersStore[relationCount][memS].getAttribute("type") == "way") {
          continue; // just ignore way for now. Move to node
          } // if

       // Get Information to calculate angle between start node (this) and any joining relations/segments
       // NOTE - With Segment 27 (a segment that ends at our crossroads) the From is not from the shared
       // Segment therefore the Angle will be the wrong way.
       // So if first Ref is higher than 2nd mark as Reverse (perhaps do this in JOSM via direction tag)
       if (membersStore[relationCount][memS].getAttribute("role") == "from") {
          // sns = segment node start - perhaps should be rns for relation
          membersStore[relationCount]['snsNodeRef'] = membersStore[relationCount][memS].getAttribute("ref");
          membersStore[relationCount]['snsIndex'] = Math.abs(membersStore[relationCount][memS].getAttribute("ref")) - 1; // only required for -1, -3 etc
          membersStore[relationCount]['snsLat'] = parseFloat(nodes[membersStore[relationCount]['snsIndex']].getAttribute("lat"));
          membersStore[relationCount]['snsLon'] = parseFloat(nodes[membersStore[relationCount]['snsIndex']].getAttribute("lon"));
       } // if node from

        if (membersStore[relationCount][memS].getAttribute("role") == "to") {
          // segment node end
          membersStore[relationCount]['sneNodeRef'] = membersStore[relationCount][memS].getAttribute("ref");
          membersStore[relationCount]['sneIndex'] = Math.abs(membersStore[relationCount][memS].getAttribute("ref")) - 1;
          membersStore[relationCount]['sneLat'] = parseFloat(nodes[membersStore[relationCount]['sneIndex']].getAttribute("lat"));
          membersStore[relationCount]['sneLon'] = parseFloat(nodes[membersStore[relationCount]['sneIndex']].getAttribute("lon"));
        } // if node to
    } // for memS
}

function buildNodeIndex () {
// Take the xmlDoc and build an index so that each Segment can quickly find coincident
// Segments (relations). Determined by the Segment End node matching the Start OR End
// of any other Segment. Remember that the End of a Segment is the Start of the next.
// But that Segments can Start or End at a Node e.g. a Loop.
// All Segments will likely have at least one Join - to the next segment where they
// both share the same Node.

// Create an Array of Relation count indexes where each is 6 Objects
// - could this be made dynamic????? ie only as many indexes as required...
// Each Object will contain the adjoining Segment Relation Ref
// And an nS or nE boolean or flag. Mostly, this will be nS for next Node Start.
// This could be expanded to contain more information that is required quickly.

  nodeDBIndex = new Array (); // Creation of Index of Nodes with links to adjoing Relations // watch off by one - extra node per relation
  relationsLUT = new Array (); // new associative array with e.g. index = -51 and contents is the relations array index
  membersStore = new Array (); // Store Once Read Many of 3 members per Relation

  firstRelationRef = relations[0].getAttribute("id"); // First Relation for offset - may not be same as sRR
  //delta2StartRelation = Math.abs(startRelationRef) - Math.abs(firstRelationRef);

  var distance;
  var relationCount = 0;
  relationIndex = 0;
  nodeIndex = 0;
  var locationlocation = "Start"; // for tooltip

  // Optimise code by moving var declarations out of loops
  var adjoiningRelID;
  var nsNodeRef, nsIndex, nsLat, nsLon;
  var neNodeRef, neIndex, neLat, neLon;
  var startNode = null; // Nodes of the Adjoining Relation
  var endNode = null;
  var route, video;

  var tempArray = new Array();
  // Speed up processing by storing this information for repeated use
  while (relations[relationCount] != null) {
    //membersStore[relationCount] = new Array(); // For IE need to explicitly declare 2nd Dimension
    // For IE only direct assignment of Array from getElements prevents access to hash variables... So build in the 
    // 3 member elements - perhaps just make it the last two which in theory should be the nodes...
    tempArray = relations[relationCount].getElementsByTagName("member");
    membersStore[relationCount] = new Array(); // IE is requires 2nd Dimension declared
    membersStore[relationCount][0] = tempArray[0];
    membersStore[relationCount][1] = tempArray[1];
    membersStore[relationCount][2] = tempArray[2];
    // 
    // Parse Relations Members information and calculated Start/End Node Lat/Long etc - add all to array
    // Array is automatically updated so no return required.
    relationMembersParse(membersStore, relationCount);
    // Store as much as possible
    membersStore[relationCount]['locationlocation'] = getTag(relationCount, "tooltip");
    membersStore[relationCount]['route'] = getTag(relationCount, "route");

    relationCount++;
  }
  relationCount = 0;
  // Now add a 2nd dimension entry for each Relation that shares a node with current node.
  // Needs to cope with oddly numbered Relations (check end AND start of all other relations)
  // Create new entries where required and cope with start special case
  while (relations[relationIndex] != null) {
    relationCount = 0; // reset counter
    // Get focused Segments details
    var currentRelID = relations[relationIndex].getAttribute("id");
    // Need to find a way to index into the relations array that copes with gaps in JOSM assigned RelationIDs.
    // Possibly a bug in JOSM but this is a simple way ehich keeps most of the current code.
    relationsLUT [currentRelID] = relationIndex; // In JS style Associative Array style add to an array by adding a new entry

    // Find the other tags and loop through until tooltip found for streetname
    locationlocation = membersStore[relationIndex]['locationlocation'];
    route = membersStore[relationIndex]['route'];
    video = "";  // NOT USED AT THIS TIME getTag(relationIndex, "video");

    var snsNodeRef = membersStore[relationIndex]['snsNodeRef'];
    var snsIndex = membersStore[relationIndex]['snsIndex'];
    var snsLat = membersStore[relationIndex]['snsLat'];
    var snsLon = membersStore[relationIndex]['snsLon'];

    var sneNodeRef = membersStore[relationIndex]['sneNodeRef'];
    var sneIndex = membersStore[relationIndex]['sneIndex'];
    var sneLat = membersStore[relationIndex]['sneLat'];
    var sneLon = membersStore[relationIndex]['sneLon'];
    // The end of the StartSegment (sne) can match either end of the adjoining
    // segment - this is to cope with Reverse nodes like loops and oddly
    // numbered ways which join the main path instead of leave it.
    // Assumes First Relation is -1 to something but JOSM cannot guarantee this
    var testNode = "";

    /*  RETIRED Code - Increasingly complex attempts to cope with odd cases such as Carriden 5-4
      *  and Kinneil 11-2. BUT just assume that always use End node of Relation for new NodeDBIndex
      *  this will result in 2 being created for 11-2 and 11 for 1-11 with 1 as special case
      *  BUT 5-4 won't work but this is same problem as Park 4-5. It had to be drawn FROM the main
      *  way to the Stub end NOT from Stub end to main way. For loops the joining node e.g. 6 in Kinneil
      *  will have been draw as part of 2-6 and 10 will come from 9-10 thus 10-6 will be handled
    */
    testNode = sneNodeRef; // Default - normally start of Relation was created as it was sne of previous
    if (snsNodeRef == "-1") { // Special case - TODO MAGIC NUMBER
      // The Start Node always points straight ahead with no turning
      // Simply sneak it in while building the first relation - remember nothing
      // can join the start (-1) node. It must be a lead into the map
      nodeDBIndex [1] = new Array (2); // First Index has only 2 connections
      // Reverse direction is how you got to that node - for the 1st there is 0 which should
      // be treated as a special case ie null                             // 13/86/08 was "0", "0"
      nodeDBIndex [1][0] = new adjoiningRelation ("0", "-1", "0", "-1", 180, 0.02, locationlocation, route, video);
      nodeDBIndex [1][1] = new adjoiningRelation (startRelationRef, "-1", startRelationRef, testNode, 0, 0.02, locationlocation, route, video); // TODO MAGIC NUMBER testNode was "-2" 13/6/08
      }
    // Test if Node exists if not; create it
    if (nodeDBIndex[Math.abs(testNode)] == null) {
      nodeIndex = Math.abs(testNode);
      nodeDBIndex [nodeIndex] = new Array (6); // TODO MAGIC Number - Create the Index Object
      }
    else { // Node exists so all work will have been done! Move to next Relation immediately
      relationIndex++; //
      continue;
      }

    // Now calculate Direction of Segment
    var segAngle;
    segAngle = angleOfAttack (snsLat, snsLon, sneLat, sneLon);
    distance = segLength (snsLat, snsLon, sneLat, sneLon); // Not sure this distance is useful

    // For each Node (apart from start) add Return Relation information for Reverse travel
    //          curR curN  NextR NextN  Angle
    //  [1][0]  -25  -3    -25    -1    180    distance
    if (( Math.abs(sneNodeRef) < Math.abs(snsNodeRef))  && (testNode == snsNodeRef)  ) {  // 13/8/08 Lookdown for similar
      nodeDBIndex [nodeIndex][relationCount] = new adjoiningRelation (currentRelID, snsNodeRef, currentRelID, sneNodeRef, 180, distance, locationlocation, route, video);
    } else {
      nodeDBIndex [nodeIndex][relationCount] = new adjoiningRelation (currentRelID, sneNodeRef, currentRelID, snsNodeRef, 180, distance, locationlocation, route, video);
    }
    relationCount++;

    // Now loop through all other relations getting their details
    for (var looper = 0, clooper = relations.length; looper < clooper; looper++) {
        if (relationIndex == looper ) { continue; } // Of course the same relation will match - move on
        // Compare the current nodes nS and nE against the looper nS, nE
        //members = relations[looper].getElementsByTagName("member");
        adjoiningRelID = relations[looper].getAttribute("id"); // This Relations Ref Number (ID)

        nsNodeRef = membersStore[looper]['snsNodeRef'];
        //nsIndex = membersStore[looper]['snsIndex'];
        nsLat = membersStore[looper]['snsLat'];
        nsLon = membersStore[looper]['snsLon'];

        neNodeRef = membersStore[looper]['sneNodeRef'];
        //neIndex = membersStore[looper]['sneIndex'];
        neLat = membersStore[looper]['sneLat'];
        neLon = membersStore[looper]['sneLon'];

        startNode = null; // Nodes of the Adjoining Relation
        endNode = null;
        // We now know the sns/sne and the ns/ne - if any match add an Object to the Index
        // Ideally do this in some sort of order such that start node populated first
        // But watch we are already at the end of Relation 1 ie node -3 in this case

        if (testNode == nsNodeRef) {
          startNode = nsNodeRef;
          endNode = neNodeRef;
          }
        // This was for reverse Relation but actually want same ordering
        if (testNode == neNodeRef) {
          startNode = neNodeRef;
          endNode = nsNodeRef;
          }

        if (startNode != null) {
          // A Hit - a startSegment Node matches a node from the looper Segment
          // Create an Object to record these details.

          // Work out the angle of this adjoining line to compare with segAngle
          // but watch for reverse joining ways. Ensure that this is a genuine 'reverse'
          // like going from node 20 to 23 when relation is 23 to 20 before using
          // the reverse angle code.
          var newAngle;
          if (testNode == neNodeRef) {  // Lookup for similar
            newAngle = angleOfAttack (neLat, neLon, nsLat, nsLon); // Reverse
          } else {
            newAngle = angleOfAttack (nsLat, nsLon, neLat, neLon); } // default
          var angle = newAngle - segAngle;
          if (angle < -180) {
            angle += 360; }
          if(angle > 180) {
            angle -= 360; }
          distance = segLength (nsLat, nsLon, neLat, neLon);

          // Update the Tag info
          locationlocation = membersStore[looper]['locationlocation']; // get tooltip info for other relations
          route = membersStore[looper]['route'];
          //video = getTag(looper, "video");

          // TODO Need to check when 6 entries per Node limit has been hit
          // Build Index with the current Relation we are on and current Node,
          // the relation that joins at this node (tested above) and its node then angle and distance
          nodeDBIndex [nodeIndex][relationCount] = new adjoiningRelation (currentRelID, startNode, adjoiningRelID, endNode, angle, distance, locationlocation, route, video);
          relationCount++; // up to 10 entries
        }
    }  // looper
  relationIndex++;
  } // while more relations

  var x = relationCount; // just for debugger to stop on
}

function clearMovie (){
  moviesDOM.style.visibility = "hidden"; // After a pause long enough for movie to start make the player invisible
  // but do not remove from the DOM using display:none. This causes the player to disappear in such a way that it will not
  // be available for function calls even when made display:block at the appropriate time. (But okay when code stepped through
  // suggesting a timing issue perhaps...

}

function terraMapper (fname, startLocation)
{ 
// Main Programme

$('restart').hide(); // ensure a change of XML cannot allow a restart with old data
if ($('halt').visible()) {
  autoStepFinish(false); // Halt is available so kill any running route.
}

var extant = false; // is the XML already loaded - don't reload
var j = 0;
try {
 // test to see if this XML has been processed
 for (j = 0; j < explored.length; j++) {
   if (explored [j] == fname) {
     extant = true; // create hash entry with XML name
   }
 }
 if (!extant) {
   explored.push(fname);
 }
 // Media player only seems to run properly after first video has played. Autostart isn't enough appears to need to be
 // visible using display property. So show a fake movie at start off screen BUT even this is hard.
 // autostart must be true(1). div must be visible onscreen. cannot be completely obscured either by z or by position.
 moviesDOM = document.getElementById("movies");
 photosDIV = document.getElementById("photos");
 // Make the photos div visible to prevent funny in IE - ideally move this so it only runs once
 photosDIV.style.visibility="visible";
 photosDIV.style.display="block";
 //moviesDOM.style.cssText = 'display:block; visibility:visible; position:absolute; bottom:40px; left:10px; z-index:200';

 moviesID = document.getElementById("Player");

 // Reset these in cases where user has clicked to switch routes where still e.g. in reverse
 reverse = false;    // identify if operator is going in reverse
 facingInward = false; // Remember if we are looking into node or not
 firstStart = true;  // to full navigate before a button can be clicked

 ZOOMLEVEL = 17; // Dynamically changeable so reset at each Explore
 distanceTravelled = 0; // reset for each explore
 var aNode;
 var lat = 0;
 var lon = 0;
 if (nodeDBIndex != null) {
   nodeDBIndex.slice(0);  }  // delete the array in case it has been used before

 xmlDoc = ajaxGetXML(fname);
 xmlAttributes = ajaxGetXML ("data/routes.xml");
 getRouteColours (); // use xmlAttributes to create hash table with colour details and hull flag

 //=========Get the Markers from the XML file=========
 // obtain the nodes for later use -
 // note this link suggests XMLDOM requires getElementsByTagName should have [0]
 // when referring to a specific node - we do this by array so okay
 // but watch fact that HTMLDOM uses getElementByID which does not exist in XMLDOM
 // http://www.w3schools.com/xml/xml_dom.asp
 nodes = xmlDoc.documentElement.getElementsByTagName("node");
 // obtain the array of relations and loop through it
 relations = xmlDoc.documentElement.getElementsByTagName("relation");

 //===========Loop through the array to pull out each of the markers
  for (var i = 0; i < relations.length; i++) {
    // obtain the attributes of each relation
    var count = 0;
    var members = relations[i].getElementsByTagName("member");
    for (var mem = 0; mem < members.length; mem++) {
        if (members[mem].getAttribute("type") == "way") {
          var way = members[mem].getAttribute("ref");
          iconinfo [count] = way;  // way Ref ID
          count++;
          } // if

        if ((members[mem].getAttribute("type") == "node") && (members[mem].getAttribute("role") == "from")) {
          var forwardFrom = members[mem].getAttribute("photo");
          var reverseTo = members[mem].getAttribute("photorev");
          iconinfo [count] = forwardFrom;  // Start Node Photo From
          count++;
          iconinfo [count] = reverseTo;  // Start Node Photo To
          count++;

          //$('apples').select('[title="yummy!"]'); // Prototype.js  ---aNode = $('node').select('[id=members[mem].getAttribute("ref")]');
          for (j = 0; j < nodes.length; j++) {
            if (nodes[j].getAttribute("id") == members[mem].getAttribute("ref") ) {
              lat = parseFloat(nodes[j].getAttribute("lat"));
              lon = parseFloat(nodes[j].getAttribute("lon"));
              break; // all done
              }
          } // for j
          var pointStart = createPoint (lat,lon);  // Start Node Point

        } // if node from

        if ((members[mem].getAttribute("type") == "node") && (members[mem].getAttribute("role") == "to")) {
          var forwardTo = members[mem].getAttribute("photo");
          var reverseFrom = members[mem].getAttribute("photorev");
          iconinfo [count] = forwardTo;  // End Node Photo To
          count++;
          iconinfo [count] = reverseFrom;  // End Node Photo From
          count++;

          //aNode = $('node').select('[id=members[mem].getAttribute("ref")]');
          for (j = 0; j < nodes.length; j++) {
            if (nodes[j].getAttribute("id") == members[mem].getAttribute("ref") ) {
              lat = parseFloat(nodes[j].getAttribute("lat"));
              lon = parseFloat(nodes[j].getAttribute("lon"));
              break; // all done
              }
          } // for j
          var pointEnd = createPoint (lat,lon);
        } // if node to
    } // for mem
    // Use the route to decide the colour
    var route = getTag(i, "route");
    var hotspots = getHotSpotsTags(i);
    var tooltip = getTag(i, "tooltip");
    var startOrder = getTag (i, "start");  // Some nodes will be starts

    // test which node is the Start
    var start = ""; // first member node is the node marked as a start position
    if (startOrder == "1st") {
      start = members[1].getAttribute("ref"); }
    if (startOrder == "2nd") {
      start = members[2].getAttribute("ref"); }
    // Added to interprate multiple start buttons from tooltip text (assume the starts have a start from here flag)
    if (tooltip == startLocation) {
      startRelationRef = relations[i].getAttribute("id");
      startRelationNode = start;
      // If the same XML data file is being used we have enough information - no need to create index or add Lines/Hulls etc
      if (extant) {
        // The start relation has been found and the XML is extant so pull the prepared Indexes from the Hash
        // and quit without reworking indexes and hulls as we are ready to move.
        nodeDBIndex = hashNodeDBIndex [fname];
        relationsLUT = hashRelationsLUT [fname];
        return;
        } // we have enough for firstStart
    } // if startLocation

    if (extant) {continue;} // Move on until start is found

    // Build the line on the map with data from iconinfo to populate links
    AddLine (pointStart, pointEnd, iconinfo, route, hotspots, tooltip, start, startOrder, relations[i].getAttribute("id"));
    // Empty the iconinfo array for the next loop
    // TODO LOOKS LIKE ICONINFO NOT USED -
    var counter = iconinfo.length;
    for (var x = 0; x < counter; x++) {
      iconinfo.pop();
      } // for x

    // For each route create a Convex Hull
    storePoints (route, pointStart, pointEnd);
  } // for relations

  buildNodeIndex ();
  // Use hash to store Indexes for each XML
  hashNodeDBIndex [fname] = nodeDBIndex;
  hashRelationsLUT [fname] = relationsLUT;
  addLocation(pointStart, iconperson, "<b>You are here.</b><br /><br />To make me move look on the Display Panel for the arrows. \n\
Click the White on Green arrow to move forward. The darker arrow is to turn around.\n\
<br /><br />Hover over icons you see on the map to see more information (they do not respond to clicks).\n\
<br /><br />Please email me if you get stuck. I need to find out where people have problems. ");

  buildErieConvexHull ();
  //alert ("Finished");

} catch (e) {alert ("Explorer Failed " + e.message) }
} // terraMapper


function getHotSpotsTags (relationIndex) {
  // get the hotspots tags and add to Hash Array
  var tags = relations[relationIndex].getElementsByTagName("tag");
  var count = 0;
  var hotspots = new Array ();
  // TODO seems clumsy - also what happens if tag missing will 2nd part cause access violation?
  while (count < tags.length) {
    if (tags [count].getAttribute("k").indexOf("hotspot") != -1) {
      var aKey = tags [count].getAttribute("k");
      var aValue = tags[count].getAttribute("v");
      hotspots[aKey] = aValue; // Auto create entry and assign value in one operation - no push
      }
    count++;
    }
  if (hotspots == null) {
    return "";
  } else {
    return hotspots;
  }
}

function storePoints (route, pStart, pEnd) {
  // VE equivalent using Erie Convex Hull - no need to use OpenLayers - use flag just to switch calls
  if (hashPoints == null) {  // cheeky technique
    hashPoints = new Array (); }    // Perhaps Object is better
  // For each route string add a new associative array
  // For each route string add a new entry for a point (2D array will auto grow with each push)
  if (hashPoints[route] == null) {
    hashPoints[route] = new Array(); }  // Add another dimension

  // Ensure Start of Convex hulls is included - Relations are not in correct order
  // so add all points - this will mean that there are many duplicates - use unique below to sort that.
  (hashPoints[route]).push( pStart );
  (hashPoints[route]).push( pEnd );

}


function unique(arrayName)
// Tidy up hashpoints which will have duplicates.
// array elements are VELatLongs - so really comparing objects...
{
    var newArray=new Array();
    label:for(var i=0; i<arrayName.length;i++ )
    {
        for(var j=0; j<newArray.length;j++ )
        {
            if(newArray[j]==arrayName[i]) {
                continue label; }
        }
        newArray[newArray.length] = arrayName[i];
    }
    return newArray;
}



function buildErieConvexHull () {
  // create a polygon feature from a list of points
  if (hashPolygons == null) {  // this will make global var
    hashPolygons = new Array (); }  // Object or Array ????


  //now transform it into a hash
  //http://alternateidea.com/blog/articles/2005/12/7/prototype-meets-ruby-a-look-at-enumerable-array-and-hash
  //  $H(F.Products[0]).each(function(product) {       logger.info(product.key + ": " + product.value);     });
 /*
 $H(hashPoints['house']).each(function(pointsArr){
   var route = pointsArr.key;
    var convexPoints = getConvexHullPoints (hashPoints[route]);
    createConvexHull (convexPoints, routeInfo[route]);
 });

 // http://www.prototypejs.org/api/array
  hashPoints.each(function(item) {   // Your code working on item here..
    var convexPoints = getConvexHullPoints (hashPoints[item]);
    createConvexHull (convexPoints, routeInfo[item]);
});
*/

for (var route in hashPoints) { // worry that this might loop over methods/properties as well as routes
    if( !hashPoints.hasOwnProperty(route) ) {
      continue; } // solution to Prototype Array extending object - Parthian 31/7/08

    if (routeInfo [route + "hull"] == "0") { // This route doesn't need a hull
      continue; }

    var convexPoints = getConvexHullPoints (unique (hashPoints[route]));
    createConvexHull (convexPoints, routeInfo[route], route, routeInfo[route + "notes"]);
    // Empty hashPoints of the Hulled Route so it is not redrawn again in later Explores!
    hashPoints[route] = null;
    delete hashPoints[route];
  }
}


function buildConvexHull () {
  // use openlayers and WPServer to create a convex hull from the nodes of each Route
  // First test openlayers on top of virtual earth
  //init();
  // Probably not possible to draw OL with VE (one or the other - should possibly move to OL
  // with VE as background but this may cause issues with mapcruncher
  // Instead get nodes and build geojson and send to WPServer
  // Get Response (ajax) and convert back to VE points
  // http://crschmidt.net/mapping/wpserverdemo/
  //init();
  //request('convex', null, hashPolygons); // Send hash array of polygons
  //for (route in hashPolygons)
  alert ("Is buildConvexHull called?");
}

/* --------------------- OLD CODE ---------------------------- */