/*
<!-- Copyright 2008-2009 Parthian Systems. All Rights Reserved -->
<!-- virtualearth - VE specific code -->
*/

var dataLayer = new VEShapeLayer();      // Hold interesting facts file as GPX that is imported
var searchShapeLayer = new VEShapeLayer(); // Blank layer that has stuff added
var layerDefault;  // Layers that match checkboxes for users points
var layerMap;
var layerTrees;
var layerPlants;
var layerAnimals;
var layerBirds;
var layerHistory;
var layerCultural;
var layerArts;
var layerCycling;
var layerDisabled;
var layerHorses;
var layerBus;
var layerTrain;
var layerSport;
var layerLitter;
var layerTourist; // layer for all types of tourist information
var viewSegmentID; // shape showing what can be seen
var layerShops;
var tourists = new Array(); // For hash array
var shops = new Array(); //

function createPLLayer()
  { // moved here from mapcruncher code - more appropriate here - Points and Lines for routes etc
  layerPL = new VEShapeLayer();
  map.AddShapeLayer(layerPL);

  // Leaflet Pins
  var leafletBtn = "<a target='Information' href='http://www.falkirkonline.net/For%20Residents/Community/town_centres_strategy/boness.aspx'>\n\
Townscape Heritage Initiative</a><br /><br /> \n\
<button type='button' onclick='javascript:showLeaflet(\"leaflet\", \"THI\");'>Open THI Leaflet</button>";
  AddPushpin(new VELatLong(56.017569, -3.609164), iconred, leafletBtn);
  AddPushpin(new VELatLong(56.018991, -3.620164), "data/THI_Icon.jpg", leafletBtn);
  AddPushpin(new VELatLong(56.0175, -3.64), "icons/fre_logo_h53.png", "Frontier of the Roman Empire");
  AddPushpin(new VELatLong(56.0215, -3.603), "icons/boness_srps.gif", "<a href='http://www.srps.org.uk/railway/home.html'>Steam Railway Website</a>");
  AddPushpin(new VELatLong(56.016, -3.607), "data/streetfairmini.jpg", "<img src='data/streetfairposter.jpg' width='220' height='300' /><br /><br /><a target='fair' href='http://kinneil.wordpress.com/2009/05/07/meet-the-friends-at-street-fair/'><b>Click for Details</b></a>");
  }

function createRouteAreaLayer() {
  layerRouteArea = new VEShapeLayer();
  map.AddShapeLayer(layerRouteArea);
}

function clustered (veclusSpecs ) {
  // Takes array of VEClusterSpecification
  // So for each Layer there can be many Clusters within each is an array of the actual Shapes
  // match these to the CP Hover/Clicks
  // Only Shapes of the same type get into the Clusters so for GreenIcon Layer highlight just detect
  // type of first Pin would be enough to clarify type of Cluster
  // For Specific WhatWhere
  // Use GetClusterShape to return the Cluster to highlight it 
  if(veclusSpecs.length != 0)
  {
  var cluster1Count = veclusSpecs[0].Shapes.length;
  //alert("There are " + cluster1Count + " pushpins in the first cluster.");
  }
}

function createUsersLayer() {

  layerTourist = new VEShapeLayer();
  layerTourist.SetTitle("Tourist");
  map.AddShapeLayer(layerTourist);
  layerShops = new VEShapeLayer();
  layerShops.SetTitle("Shops");
  map.AddShapeLayer(layerShops);
// Users add their own information to this layer
  layerUL = new VEShapeLayer();
  map.AddShapeLayer(layerUL);

  dataLayer.SetTitle("DataLayer");
  map.AddShapeLayer(dataLayer); // Why was this not Added before yet code appeared to work? Probably due to GPX load

  // ClusteringOptions Class to instruct each layer how to open up contents to Control Panel
  var x = new VEClusteringOptions();
  x.Callback = clustered;

  // Create the layers for the checkboxes of each user creatable layer
  layerDefault = new VEShapeLayer();
  layerDefault.SetTitle("Default");
  layerDefault.SetClusteringConfiguration(VEClusteringType.Grid, x);
  map.AddShapeLayer(layerDefault);
  layerMap = new VEShapeLayer();
  layerMap.SetTitle("Map");
  layerMap.SetClusteringConfiguration(VEClusteringType.Grid, x);
  map.AddShapeLayer(layerMap);
  layerTrees = new VEShapeLayer();
  layerTrees.SetTitle("Trees");
  layerTrees.SetClusteringConfiguration(VEClusteringType.Grid);
  map.AddShapeLayer(layerTrees);
  layerPlants = new VEShapeLayer();
  layerPlants.SetTitle("Plants");
  layerPlants.SetClusteringConfiguration(VEClusteringType.Grid);
  map.AddShapeLayer(layerPlants);
  layerAnimals = new VEShapeLayer();
  layerAnimals.SetTitle("Animals");
  layerAnimals.SetClusteringConfiguration(VEClusteringType.Grid);
  map.AddShapeLayer(layerAnimals);
  layerBirds = new VEShapeLayer();
  layerBirds.SetTitle("Birds");
  layerBirds.SetClusteringConfiguration(VEClusteringType.Grid);
  map.AddShapeLayer(layerBirds);
  layerHistory = new VEShapeLayer();
  layerHistory.SetTitle("History");
  layerHistory.SetClusteringConfiguration(VEClusteringType.Grid);
  map.AddShapeLayer(layerHistory);
  layerCultural = new VEShapeLayer();
  layerCultural.SetTitle("Cultural");
  layerCultural.SetClusteringConfiguration(VEClusteringType.Grid);
  map.AddShapeLayer(layerCultural);

  layerArts = new VEShapeLayer();
  layerArts.SetTitle("Arts");
  layerArts.SetClusteringConfiguration(VEClusteringType.Grid);
  map.AddShapeLayer(layerArts);
  layerCycling = new VEShapeLayer();
  layerCycling.SetTitle("Cycling");
  layerCycling.SetClusteringConfiguration(VEClusteringType.Grid);
  map.AddShapeLayer(layerCycling);
  layerDisabled = new VEShapeLayer();
  layerDisabled.SetTitle("Disabled");
  layerDisabled.SetClusteringConfiguration(VEClusteringType.Grid);
  map.AddShapeLayer(layerDisabled);
  layerHorses = new VEShapeLayer();
  layerHorses.SetTitle("Horses");
  layerHorses.SetClusteringConfiguration(VEClusteringType.Grid);
  map.AddShapeLayer(layerHorses);
  layerBus = new VEShapeLayer();
  layerBus.SetTitle("Bus");
  layerBus.SetClusteringConfiguration(VEClusteringType.Grid);
  map.AddShapeLayer(layerBus);
  layerTrain = new VEShapeLayer();
  layerTrain.SetTitle("Train");
  layerTrain.SetClusteringConfiguration(VEClusteringType.Grid);
  map.AddShapeLayer(layerTrain);

  layerSport = new VEShapeLayer();
  layerSport.SetTitle("Sport");
  layerSport.SetClusteringConfiguration(VEClusteringType.Grid);
  map.AddShapeLayer(layerSport);
  layerLitter = new VEShapeLayer();
  layerLitter.SetTitle("Litter");
  layerLitter.SetClusteringConfiguration(VEClusteringType.Grid);
  map.AddShapeLayer(layerLitter);

}


function createPushPin(e) {
  // This only works in 2d mode - see http://msdn.microsoft.com/en-us/library/bb412560.aspx
  if (e.rightMouseButton) {
    return false; }   // allow the default action to take place - not this one. In this case allows drag with right button
  var x = e.mapX;
  var y = e.mapY;
  var pixel = new VEPixel(x, y);
  var LL = map.PixelToLatLong(pixel);
  LL.Latitude = floatTrim (LL.Latitude, 6);  // store with fewer points
  LL.Longitude = floatTrim (LL.Longitude, 6);
  var shape = new VEShape(VEShapeType.Pushpin, LL);
  layerDefault.AddShape(shape);    // Temporarily store in default layer
  shape.SetTitle('My Pushpin');
//  shape.SetDescription('<div><form method="post" action="php/storepushpin.php"><textarea name="usersnotes" id="usersnotes" rows="3" cols="20">Enter your location specific comments here</textarea><input type="text" name="hidlat" value=' + LL.Latitude + ' /><input type="text" name="hidlong" value=' + LL.Longitude + '><input type="submit" value="Submit Comment" /><input type="reset" value="Restart"/></form></div>');

  // Use a function as this will be used for potentially different popups.
  // Concern that GetID will simply refer to that shape of potentially many for complexgeom that is submitted and
  // possible confusion as many geometries will in fact be part of the one upload and download.
  map.ClearInfoBoxStyles();
  shape.SetDescription(buildPopupWindow (shape.GetID(), LL, true, ""));

  // IS ZOOM STILL FUNCTIONING? THINK SO - REMEMBER DOUBLECLICK AND CLICK MAY GET CONFUSED AND BE DIFFERENT BETWEEN IE AND FF
  return true; // This Event function returns True to STOP the default Action e.g. zoom with doubleclick.
  //In HTML onclick etc return false for same effect
}


function highlightPin (pinID, deltaTime) {
  // Update the CustomIcon to show an image in the other div
  // Originally a simple toggle but use of 1 to 1000 as special flag for hover highlights etc
  // requires a suspension of ordinary toggle when deltaTime is 1 to 1000
  var pinCount = "";
  var shape = map.GetShapeByID( pinID );
  if (shape == null) { return; } // Pin has probably been Cleared. TODO Perhaps warn user that pin is missing or Remove Calendar link.
  var customIcon = shape.GetCustomIcon ();
  if (!customIcon) { return; } // an empty pin - may occur when looping through whole layer 

  // Toggle the Highlight Image stored in the CSS either if background off (ie pretend found) or
  // 1-1000 deltatime forcing.
  if (customIcon.indexOf('pretend') > 0){ // Pretend found so background must be off - Toggle it on
    // replace pretend with actual style but remember state by detecting pretend in some way
    if (deltaTime > 0 && deltaTime < 1000) { // a clever device - deltatime is in milliseconds so always large. If used for pinCount will be small
      pinCount = deltaTime; // using deltatime to pass in number of pin e.g. from touristHandler
     }
    customIcon = customIcon.replace('pretend', 'pinHighLightStyle');
    customIcon = customIcon.replace("alarmflag'>", "alarmflag'>" + pinCount);
    var oneDay = 86400000;
    switch (true)  // Neat Switch if case is true then...    just ensure case order increases so lowest is first
    {
    case (deltaTime < 1000):                          // note lack of single quotes around png - very handy
      customIcon = customIcon.replace('alarmflag', "background-image:url(icons/purplealarm.png); background-repeat: no-repeat;");
      break;
    case (deltaTime < oneDay):                          // note lack of single quotes around png - very handy
      customIcon = customIcon.replace('alarmflag', "background-image:url(icons/redalarm.png); background-repeat: no-repeat;");
      break;
    case (deltaTime < oneDay * 3):
      customIcon = customIcon.replace('alarmflag', "background-image:url(icons/amberalarm.png); background-repeat: no-repeat;");
      break;
    case (deltaTime < oneDay * 7):
      customIcon = customIcon.replace('alarmflag', "background-image:url(icons/greenalarm.png); background-repeat: no-repeat; ");
      break;
    default:
      break;
    }
  }
  else { // Toggle Styling off ie Hide Alarm marker
    customIcon = customIcon.replace('pinHighLightStyle','pretend');
    // Remove AlarmFlag setting (which didn't really matter as class empty (pretend) but also removes any text added to div
    if (customIcon.indexOf('pretend') > 0) { // for start/end flags there is no pretend so don't process those elements
      customIcon = customIcon.substr(0, customIcon.indexOf('pretend')) + "pretend' style='alarmflag'></div></div>";
    }
  }
  shape.SetCustomIcon(customIcon);
}

function hoverPin (pinID, deltaTime) {
  // Update the CustomIcon to show an image in the hover div
  var pinCount = "";
  var shape = map.GetShapeByID( pinID );
  if (shape == null) { return; } // Pin has probably been Cleared. TODO Perhaps warn user that pin is missing or Remove Calendar link.
  var customIcon = shape.GetCustomIcon ();
  if (!customIcon) { return; } // an empty pin - may occur when looping through whole layer

  // Toggle the Highlight Image stored in the CSS either if background off (ie pretend found) or
  // 1-1000 deltatime forcing.
  if (customIcon.indexOf('hover') > 0){ // Pretend found so background must be off - Toggle it on
    // replace pretend with actual style but remember state by detecting pretend in some way
    if (deltaTime > 0 && deltaTime < 1000) { // a clever device - deltatime is in milliseconds so always large. If used for pinCount will be small
      pinCount = deltaTime; // using deltatime to pass in number of pin e.g. from touristHandler (-1 for nothing)
    }
    customIcon = customIcon.replace('hover', 'pinHoverStyle');
    customIcon = customIcon.replace("hoverflag'>", "hoverflag'>" + pinCount);
    var oneDay = 86400000;
    switch (true)  // Neat Switch if case is true then...    just ensure case order increases so lowest is first
    {
    case (deltaTime < 1000):                          // note lack of single quotes around png - very handy
      customIcon = customIcon.replace('hoverflag', "background-image:url(icons/purplealarm.png); background-repeat: no-repeat;");
      break;
    // Other colours probably not used for hover
    case (deltaTime < oneDay):                          // note lack of single quotes around png - very handy
      customIcon = customIcon.replace('hoverflag', "background-image:url(icons/redalarm.png); background-repeat: no-repeat;");
      break;
    case (deltaTime < oneDay * 3):
      customIcon = customIcon.replace('hoverflag', "background-image:url(icons/amberalarm.png); background-repeat: no-repeat;");
      break;
    case (deltaTime < oneDay * 7):
      customIcon = customIcon.replace('hoverflag', "background-image:url(icons/greenalarm.png); background-repeat: no-repeat; ");
      break;
    default:
      break;
    }
  }
  else { // Toggle Styling off ie Hide Alarm marker
    customIcon = customIcon.replace('pinHoverStyle','hover');
    // Remove AlarmFlag setting (which didn't really matter as class empty (pretend) but also removes any text added to div
    if (customIcon.indexOf('hover') > 0) { 
      // // for start/end flags there is no pretend so don't process those elements
      customIcon = customIcon.substr(0, customIcon.indexOf('hover')) + "hover' style='hoverflag'></div><div id='" +
        customIcon.substr(customIcon.indexOf('{2}'), customIcon.length - 1); // preserving real background
    }
  }
  shape.SetCustomIcon(customIcon);
}


function submitPin(pinID, uploadFile) {
  // Use Ajax to send the data to the database

  uploadFile = uploadFile.substring(uploadFile.lastIndexOf("\\") + 1);
  var shape = map.GetShapeByID(pinID);  // get the pin
  var usersnotes = document.getElementById(pinID + "_notes").value;
  //document.getElementById("hiddennotes").value = usersnotes; // save the values in a hidden field
  // update the pin specific DOM element but remember this - // el.setAttribute('value', var);
  //replaceText (document.getElementById(pinID + "_notes"), usersnotes); // from HF text_utils.js
  // now get the entire DOM element that has just been updated with new textarea
  //var updated = (document.getElementById(pinID + "_pin")).innerHTML;
  // and store in the shape with surrounding div reinstated
  //shape.SetDescription ("<div id=" + pinID + "_pin>" + updated + "</div>"); // rebuild html
  // Tried to dynamically pass var LL into here but difficult both due to concept of binding
  // and syntax e.g. tried submitPin(\'LL\') but this simply passed string LL not the object TODO

  // now put together the Ajax
  var LL = shape.GetPoints(); // gets array of points so will work for lines etc
  var lat = floatTrim (LL[0].Latitude, 6);    // always store and test with same precision
  var llong = floatTrim (LL[0].Longitude, 6);
  var commentType = getRadioChecked(pinID + "_pin"); // use Div id to get checked option

  // Flag this pin in Epoch Prime
  //set the attribute values for date1 - if you don't set an attribute value, it will simply default to blank.
  var datetime = new Date(); // use to store time in seconds from 1970 but need to /1000 for PHP
  if (document.getElementById('startdate').value != "today") {
    var sd = document.getElementById('startdate').value.split("-"); // but replace with date set by user
    datetime.setFullYear(sd[2], sd[1]-1, sd[0]);  // watch month starts from 0 - But is this Morning, Noon or Night??? TODO
    }
  // Use the Object to ensure Date is updated with cellHTML etc for Epoch
  var dateCal = new DateCalendar (datetime); // Pass in Specific Date (may not be today)
  var myArray = new Array (dateCal.d); // Add date object updated with Epoch information into the array to send to Calendar

  calendar1.addDates(myArray, true); //add the dates and display them on the calendar! Perhaps delay redraw?
  calendar1.reDraw();
  // Should really add these to dateShapeMap so that little flag is automatically drawn if Pin needs attention

  // Use this code to add link to Date itself - perhaps useful to go straight to HillClimb web page or similar
  //var theCell = calendar1.cells [calendar1.getCellIndex(datetime)];
  //var href1 = "http://www.bbc.co.uk/news";
  //theCell.setURL(href1, 'js');

  var buffer = document.getElementById("buffer").value; // Slight worry that these should be pin specific...

  var request = new Ajax.Request(
  "php/login/storepushpin.php",
  {
    method: 'post',
    parameters: {usersnotes: usersnotes, lat: lat, llong: llong, buffer: buffer, commenttype: commentType, datetime:  Math.floor(datetime.getTime()/1000), picture: uploadFile},
    onSuccess: function (theReturn) {
      var response = theReturn.responseText;
      alert ("Submitted Comment");
      //var loginId = document.getElementById("loginbanner");
      //loginId.innerHTML = response;
      },
    onFailure: function (theReturn) {
      // the Header Response is populated with 400 (200 is success) and "Status: some error stuff"
      // see HFAjax pg292  in php  header ("Status: some text", true, 400);
      var response = theReturn.responseText || "No Error Message";
      alert ("Submit of Pushpin Failed " + theReturn.getResponseHeader("Status")); }
  });
}

function clearPin(pinID) {
  // remove a pin and any comment but only if Pin has not yet been submitted
  var shape = map.GetShapeByID(pinID);  // get the pin
  map.DeleteShape (shape);
}

function deletePin(pinID, day, month, year1) {
  // remove a pin and any comment but only if usernames match
  // Use Ajax to send the data to the database
  var shape = map.GetShapeByID(pinID);  // get the pin
  // Delete using Table and Title #value
  var table;
  var serialpk;
  if (shape.GetType() == VEShapeType.Pushpin) {
    table = 'simple'
  } else {
    table = 'complex';  }
  serialpk = shape.GetTitle().substring(shape.GetTitle().indexOf('#')+1);

  var request = new Ajax.Request(
  "php/login/deletepushpin.php",
  {
    method: 'post',
    parameters: { table: table, serialpk: serialpk },
    onSuccess: function(theReturn){deleteDates(theReturn, pinID, day, month, year1); pinID = null},
    onFailure: function (theReturn) {
      var response = theReturn.responseText || "No Error Message";
      alert ("Delete of Shape Failed. " + theReturn.getResponseHeader("Status")); }
  });
}

function deleteDates (theReturn, pinID, day, month, year1) {
  //alert ("Dates Array " + calendar1.dates);
  //alert ("selectedDates Array " + calendar1.selectedDates);

  var response = theReturn.responseText;
  var shape = map.GetShapeByID(pinID);  // get the pin
  map.DeleteShape (shape);
  // now empty this pin from the dateShapeMap so that only the correct date/pins are highlighted
  //alert ("Delete From DSM pin " + pinID);
  for (var x=0; x < dateShapeMap[year1][month][day].length; x++ ) {
    if (dateShapeMap[year1][month][day][x] == pinID) {
      dateShapeMap[year1][month][day].splice(x,1);  } // remove 1 element at pos x and replace with nothing (can have 1 or more extra params to add strings
  }
  if (dateShapeMap[year1][month][day].length == 0) {// no items on this date so update calendar
    //alert ("Deleting From Calendar " + day + " " + month + " " + year1);
    delDates = [];
    delDates[0] = new Date ();
    delDates[0].setFullYear(parseInt(year1));
    delDates[0].setMonth(parseInt(month) - 1, parseInt(day)); //(0-11 , 1-31)
    //alert ("Deleting " + delDates);
    calendar1.removeDates(delDates,true);
    calendar1.reDraw();
  //alert ("Dates Array " + calendar1.dates);
  //alert ("selectedDates Array " + calendar1.selectedDates);
  }
 }


function removePoints () {
  // Remove old Geometries (not pins) to stop them piling up. Don't use blankett VE function as need to control via handler
  for (var count = 0; count < map.GetShapeLayerCount(); count++) {
   handleLayer(map.GetShapeLayerByIndex(count).GetTitle().toLowerCase(), "delete", "Pushpin");
   // code will ignore non Feature Layer Layers e.g. unsaved collection and lots with no Title
  }
}


function showPoints (whatson) {
 // Use Ajax to get the data from the database
 // Later get a date range and the name of the user to get points for and possibly other criteria
 // as per the radio buttons above
try {
 // but first remove previous layers (if any) so that pins aren't piled up
 for (var count = 0; count < map.GetShapeLayerCount(); count++) {
   if ((map.GetShapeLayerByIndex(count)).GetTitle().toLowerCase().indexOf("datalayer") == -1 ) {
      //     alert ("Deleting Layer is " + (map.GetShapeLayerByIndex(count)).GetTitle().toLowerCase());
     handleLayer((map.GetShapeLayerByIndex(count)).GetTitle().toLowerCase(), "delete", "Pushpin");
   // code will ignore non Feature Layer Layers (dataLayer has waypoints.gpx not Comments)
   }
 }

  var request = new Ajax.Request(
  "php/login/getpushpins.php",
  {
    method: 'post',
    requestHeaders: {Accept: 'application/json'},
    parameters: { },
    onSuccess: function (theReturn) {
      var jscript = theReturn.responseText.evalJSON(false);  // if true the process fails. Due to Badly formed JSON
      processPoints (jscript, whatson);
      document.body.style.cursor = "default";
    },
    onFailure: function (theReturn) {
      var response = theReturn.responseText || "No Error Message";
      document.body.style.cursor = "default";
      alert ("Get of Pushpins Failed " + theReturn.getResponseHeader("Status")); }
  } );
}
catch (e)
  {
  document.body.style.cursor = "default";
  alert ("Error - Show Points: " + e.message);
  }

}



function AddFilledCircle(loc, radius, color, incolor, width)
{   // http://viavirtualearth.com/Wiki/Draw+a+filled+Circle.ashx
  var R = 6371; // earth's mean radius in km
  var lat = (loc.Latitude * Math.PI) / 180; //rad
  var lon = (loc.Longitude * Math.PI) / 180; //rad
  var d = parseFloat(radius)/R;  // d = angular distance covered on earth's surface
  var locs = new Array();
  for (x = 0; x <= 360; x++) {
    var p2 = new VELatLong(0,0);
    brng = x * Math.PI / 180; //rad
    p2.Latitude = Math.asin(Math.sin(lat)*Math.cos(d) + Math.cos(lat)*Math.sin(d)*Math.cos(brng));
    p2.Longitude = ((lon + Math.atan2(Math.sin(brng)*Math.sin(d)*Math.cos(lat), Math.cos(d)-Math.sin(lat)*Math.sin(p2.Latitude))) * 180) / Math.PI;
    p2.Latitude = (p2.Latitude * 180) / Math.PI;
    locs.push(p2);
  }
  var poly = new VEShape(VEShapeType.Polygon, locs);
  poly.SetZIndex(null, 61); // 1 above Convex Hulls - really need to make these constants
  poly.SetLineColor(color);
  poly.SetLineWidth(width);
  poly.SetFillColor(incolor);
  poly.SetTitle("Buffer"); // Use this as a flag for Delete process so that Buffers are not deleted as a Geometry
  poly.HideIcon();
  return poly;
}

function AddSegment(posn, radius, bearing, theta)
{   // http://viavirtualearth.com/Wiki/Draw+a+filled+Circle.ashx
  if (viewSegmentID)
    dataLayer.DeleteShape(dataLayer.GetShapeByID(viewSegmentID));
  if (radius == 0.0) { return; }
  var color = new VEColor(255, 255, 0, 0.8);
  var incolor = new VEColor(255, 255, 0, 0.4)
  var width = 2;
  var loc = posn;
  var R = 6371; // earth's mean radius in km
  var lat = (loc.Latitude * Math.PI) / 180; //rad
  var lon = (loc.Longitude * Math.PI) / 180; //rad
  var d = parseFloat(radius)/R;  // d = angular distance covered on earth's surface
  var locs = new Array();
  locs.push (loc); // Start point
  var start = bearing - theta; // making a segment plus/minus theta
  var end = bearing + theta;
  for (x = start; x <= end; x++) {
    var p2 = new VELatLong(0,0);
    brng = x * Math.PI / 180; //rad
    p2.Latitude = Math.asin(Math.sin(lat)*Math.cos(d) + Math.cos(lat)*Math.sin(d)*Math.cos(brng));
    p2.Longitude = ((lon + Math.atan2(Math.sin(brng)*Math.sin(d)*Math.cos(lat), Math.cos(d)-Math.sin(lat)*Math.sin(p2.Latitude))) * 180) / Math.PI;
    p2.Latitude = (p2.Latitude * 180) / Math.PI;
    locs.push(p2);
  }
  locs.push(loc); // End Point is the center
  var poly = new VEShape(VEShapeType.Polygon, locs);
  poly.SetZIndex(null, 61); // 1 above Convex Hulls - really need to make these constants
  poly.SetLineColor(color);
  poly.SetLineWidth(width);
  poly.SetFillColor(incolor);
  poly.SetTitle("Buffer"); // Use this as a flag for Delete process so that Buffers are not deleted as a Geometry
  poly.HideIcon();
  dataLayer.AddShape (poly);
  viewSegmentID = poly.GetID();
}

function handleWhatsOn(e) {
  // Event automatically turns up, 1st param sent from bindAsEventListener becomes this
  hoverPin (this, 1); // Toggle the highlighter class - time for Red
}

function handleWhatsOff(e, deltaTime) {
  // Event automatically turns up, 1st param sent from bindAsEventListener becomes this
  hoverPin (this, 1); // Toggle the highlighter class - turn off
  //highlightPin(this, deltaTime); // now work out what default colour it should have
}

function buildWhatsOn (ip, dateCal, pinID, datetime, whatsonsArr) {
  //aWhat = new Array(); // Array or Object - this line isn't even required due to { } createing Object
  var aWhat;
  // can be ip: ip or 'ip':ip - appears to be no difference - or 0: ip, 1: dateCal
  aWhat = {ip:ip, dateCal:dateCal, pinID:pinID, datetime: datetime };
  whatsonsArr.push (aWhat);
}

function moveToWhatsOn(e) {
  //ZOOMLEVEL=16; // Do not change ZoomLevel
  // this is the 1st parameter passed in by BAEL
  map.SetCenterAndZoom(map.GetShapeByID(this).GetPoints()[0], ZOOMLEVEL);
}

function whatsOn (pinID, notes, dateOn, deltaTime) {
    // code from handleTourists
    dateOn = dateOn.replace (' for ', ' '); // but this means for cannot be used in Titles...
    var whatsOnContent = notes.substring(0, notes.indexOf('<br />') ) + dateOn;
    var whatsOnElement = new Element ('div', {'class': '', 'id': 'whatsOn' + pinID } ).update(whatsOnContent);
    whatsOnElement.setStyle({'cursor':'pointer'});
    $('whatson').insert({'top': whatsOnElement});
    // now that the element exists in the DOM we can add Observers
    // (http://www.prototypejs.org/api/event/observe) & http://www.prototypejs.org/api/function/bindAsEventListener
    whatsOnElement.observe('mouseover', handleWhatsOn.bindAsEventListener(pinID));
    whatsOnElement.observe('mouseout', handleWhatsOff.bindAsEventListener(pinID, deltaTime));
    // Oddly there is no need to BindAsEventListener or Bind the following. Perhaps due to changes in 1.6 prototype
    // The following approach works as well ie inline function without bindAEL
    //whatsOnElement.observe('click', function() {ZOOMLEVEL=16; map.SetCenterAndZoom(map.GetShapeByID(pinID).GetPoints()[0], ZOOMLEVEL);} );  // jump to the pin
    // Changing Zoom in any way appears to cause VE to get out of sync with ZOOMLEVEL
    whatsOnElement.observe('click', moveToWhatsOn.bindAsEventListener(pinID) );
    // jump to the pin
}

function processPoint (jsonData, ip, dateCal, dateArray) {
  // Process a Single Point - for whatsOn mode only some points are processed
  var showpoint;
  var shape;
  var deleteBtn;
  showpoint = new VELatLong(jsonData[ip].latitude, jsonData[ip].longitude);
  shape = new VEShape(VEShapeType.Pushpin, showpoint);
  var shapeLayer = handleLayer(jsonData[ip].commenttype, "return", ""); // getShapeLayer
  shape.SetTitle('Loaded Pushpin #' + jsonData[ip].serialpk); // Add the key to the Title
  if (jsonData[ip].buffer > 0.0) {
    shapeLayer.AddShape(AddFilledCircle(showpoint,(jsonData[ip].buffer)/1000, new VEColor(0,0,255,0.6), new VEColor(90,255,40,0.4), 2));
  }
  // Add the point
  shapeLayer.AddShape(shape);
  var pinID = shape.GetID();
  shape.SetCustomIcon ( buildPinIcon (pinID, jsonData[ip].commenttype) );

  // Add a button to delete the shape if the username matches who is logged in
  // but watch for "\r\nuser". This makes == fail - however this approach could confuse
  // if "bob" and "bob 1"  are used or similar... TODO
  if (currentUser.match(jsonData[ip].username) != null ) {
     deleteBtn = '<input type="button" value="Delete Pin" id="clearPin" onclick="deletePin(\'' + pinID + '\',\'' + dateCal.day + '\',\'' +  dateCal.month + '\',\'' +  dateCal.year1 + '\');" />';
  } else {
     deleteBtn = "";
  }

  var picBtn = "";
  if (jsonData[ip].picture != "") {
    picBtn = '<input type="button" value="Show Picture" onclick=showPicture(\''+ jsonData[ip].username + '\',\'' + jsonData[ip].picture + '\'); />';
    }

  // Convert HTTP to a real link - should also be done for Geometries. and is this the best place. Perhaps in PHP
  // or even when created. Consider use of []() notation.
  var comment = jsonData[ip].comment;
  var starthttp = comment.indexOf("[");
  var endhttp = comment.indexOf("]");
  if (starthttp >= 0 && endhttp > 0) {
    jsonData[ip].comment = "".concat (comment.substr(0,starthttp), "<a target='_blank' href='", comment.substring(starthttp+1, endhttp), "'>Link</a>", comment.substr(endhttp+1));
}
  // When the shape is added the GetID will be populated
  shape.SetDescription('<div id="' + pinID + '_pin" >' + jsonData[ip].comment + '<br />Posted by ' + jsonData[ip].username + dateCal.theDate + ' <br />' +  picBtn + ' <br />\n\
  <input readonly="readonly" size="11" type="text" value=' + showpoint.Latitude + ' autocomplete="off" />\n\
  <input readonly="readonly" size="11" type="text" value=' + showpoint.Longitude + ' autocomplete="off" />' + deleteBtn + ' </div>');

  dateUsage (dateCal, pinID, dateArray); // update the calendar + highlight if req. - dateArray modified
  return pinID;
}

function processPoints (jscript, whatson) {
  // Will this now show Error Messages in a way it didn't before docref_root etc?
  //create each point from the list
  //var jsonData = JSON.parse (jscript);  // var markers = [ ... text converted to real code with var markers
  //var jsonData = Object.toJSON (jscript); //
  //var evalD = eval (jscript);
  var dateArray = new Array();
  //alert (jscript);
  var jsonData = jscript;
  var pinID;
  // Empty the Calendar - Possibly not - now updating dates with Complex Geometries so cannot empty the calendar
  //dateShapeMap = null; // attempt to restart the mapping for what should be highlighted on each date.
  //calendar1.resetSelections(); // Clear out everything and goto initial month. TODO Appears to FAIL!!!!!!!
  //calendar1.reDraw(); // can send int of index to redraw that cell - could be good for clear

  var whatsonsArr = [];  // [] or new Array (); but {} doesn't work

  for (var ip = 0;  ip < jsonData.length; ip++) {
    //alert (jsonData[ip].comment);
    var dateCal = new DateCalendar (jsonData[ip].datetime * 1000); // PHP uses seconds from 1970 - JS needs milliseconds
    if (whatson) { // Run at start to load POI due within 30, 60, 90 days
      if (dateCal.deltaTime > 0 && dateCal.deltaTime < dateCal.days90) {
        switch (true) { // with true go through each in order until hit - order of cases vital
          case (dateCal.deltaTime < dateCal.oneWeek):
            // Populate CP whatson
            pinID = processPoint (jsonData, ip, dateCal, dateArray);
            buildWhatsOn (ip, dateCal, pinID, jsonData[ip].datetime, whatsonsArr);
            break;
          case (dateCal.deltaTime < dateCal.days30):
            pinID = processPoint (jsonData, ip, dateCal, dateArray);
            buildWhatsOn (ip, dateCal, pinID, jsonData[ip].datetime, whatsonsArr);
            break;
          case (dateCal.deltaTime < dateCal.days60):
            pinID = processPoint (jsonData, ip, dateCal, dateArray);
            buildWhatsOn (ip, dateCal, pinID, jsonData[ip].datetime, whatsonsArr);
            break;
          case (dateCal.deltaTime < dateCal.days90):
            pinID = processPoint (jsonData, ip, dateCal, dateArray);
            buildWhatsOn (ip, dateCal, pinID, jsonData[ip].datetime, whatsonsArr);
            break;
          default:
            break;
          }
      } // outside 90 day limit
    } else {
      // Process All Points
      processPoint (jsonData, ip, dateCal, dateArray);
    }
   } // for points
   if (whatson) {
     // Now sort the array of whatsons by the datetime and create whatsons with the newly ordered data
     whatsonsArr.sort (function (a,b) {return b.datetime - a.datetime });
     // loop through the sub list of re-ordered active dates to create whatson elements
     for (loop = 0; loop < whatsonsArr.length; loop++) {
       whatsOn(whatsonsArr[loop].pinID, jsonData[whatsonsArr[loop].ip].comment, whatsonsArr[loop].dateCal.theDate, whatsonsArr[loop].dateCal.deltaTime);
     }
   } // whatson

   //
 // Now Add to the Calendar - do not reset all dates (even if it worked)
 //calendar1.resetSelections(); // Clear out everything and goto initial month. TODO Appears to FAIL!!!!!!!
 calendar1.addDates(dateArray);
 calendar1.reDraw(); // can send int of index to redraw that cell - could be good for clear
}


function deleteAllPointsOrGeometries (activeLayer, removeThese) {
  // when Showing Comments or Geometries remove old ones to stop them pilling up and to remove deleted stuff(??)
try {
  var count = activeLayer.GetShapeCount();
  var pin = count - 1; // Every Delete shifts the array of shapes to the left so start from end and count down
  for(var i=0; i < count; ++i) {
    var shape = activeLayer.GetShapeByIndex(pin); // As each item is deleted the index falls so just keep deleting 1st item!!!
    var type = shape.GetType();
    var title = shape.GetTitle();
    switch (removeThese)
    {
    case "Pushpin":
        // Removing anything that has a point type except those with blank titles (start/end etc) but also the Buffer Polygons.
        if (( (type == VEShapeType.Pushpin) && (title != "")) || (title == "Buffer" ) ) {
          activeLayer.DeleteShape(shape);
        }
        break;
    default:
            // Must by Polyline and Polygon OR Non Titled Pins (Start/End etc) that must be removed ie NOT Pushpin
            // but don't get rid of buffer Polygons - these are removed with pins above
            if (( (type != VEShapeType.Pushpin) || (title == "") ) && (title != "Buffer" ) ) {
              activeLayer.DeleteShape(shape);
            }
            break;
    }
    pin = pin - 1; // with each pass move back through index of features
  }
} catch (e){alert ("Delete Problem - " + e.message) }
}

function handleLayer (commenttype, functype, geometryType) {
  // handle lots of different commands on layers - geometryType is optional: only for delete
  //alert ("HL " + commenttype);
  if (commenttype.indexOf ('#') > 0 ) {
    alert ("HL " + commenttype);
    commentype = commentype.substring (0, commenttype.indexOf('#') - 2); // history #25 - remove the space#25
    alert ("HL2 " + commenttype);
  }
  switch (commenttype)
  {
  case dataLayer.GetTitle().toLowerCase(): // use lowerC as worry over upperC in Icon.png etc
    if (functype == "show") { dataLayer.Show(); } //showCheckedFeatures
    if (functype == "hide") { dataLayer.Hide(); } //clearCheckedFeatures
    if (functype == "delete") { deleteAllPointsOrGeometries (dataLayer, geometryType); }
    if (functype == "return") { return dataLayer; } // getShapeLayer
    break;
  case layerDefault.GetTitle().toLowerCase(): // use lowerC as worry over upperC in Icon.png etc
    if (functype == "show") { layerDefault.Show(); } //showCheckedFeatures
    if (functype == "hide") { layerDefault.Hide(); } //clearCheckedFeatures
    if (functype == "delete") { deleteAllPointsOrGeometries (layerDefault, geometryType); }
    if (functype == "return") { return layerDefault; } // getShapeLayer
    break;
  case layerMap.GetTitle().toLowerCase():
    if (functype == "show") { layerMap.Show(); }
    if (functype == "hide") { layerMap.Hide(); }
    if (functype == "delete") { deleteAllPointsOrGeometries (layerMap, geometryType); }
    if (functype == "return") { return layerMap; }
    break;
  case layerTrees.GetTitle().toLowerCase():
    if (functype == "show") { layerTrees.Show(); }
    if (functype == "hide") { layerTrees.Hide(); }
    if (functype == "delete") { deleteAllPointsOrGeometries (layerTrees, geometryType); }
    if (functype == "return") { return layerTrees; }
    break;
  case layerPlants.GetTitle().toLowerCase():
    if (functype == "show") { layerPlants.Show(); }
    if (functype == "hide") { layerPlants.Hide(); }
    if (functype == "delete") { deleteAllPointsOrGeometries (layerPlants, geometryType); }
    if (functype == "return") { return layerPlants; }
    break;
  case layerAnimals.GetTitle().toLowerCase():
    if (functype == "show") { layerAnimals.Show(); }
    if (functype == "hide") { layerAnimals.Hide(); }
    if (functype == "delete") { deleteAllPointsOrGeometries (layerAnimals, geometryType); }
    if (functype == "return") { return layerAnimals; }
    break;
  case layerBirds.GetTitle().toLowerCase():
    if (functype == "show") { layerBirds.Show(); }
    if (functype == "hide") { layerBirds.Hide(); }
    if (functype == "delete") { deleteAllPointsOrGeometries (layerBirds, geometryType); }
    if (functype == "return") { return layerBirds; }
    break;
  case layerHistory.GetTitle().toLowerCase():
    if (functype == "show") { layerHistory.Show(); }
    if (functype == "hide") { layerHistory.Hide(); }
    if (functype == "delete") { deleteAllPointsOrGeometries (layerHistory, geometryType); }
    if (functype == "return") { return layerHistory; }
    break;
  case layerCultural.GetTitle().toLowerCase():
    if (functype == "show") { layerCultural.Show(); }
    if (functype == "hide") { layerCultural.Hide(); }
    if (functype == "delete") { deleteAllPointsOrGeometries (layerCultural, geometryType); }
    if (functype == "return") { return layerCultural; }
    break;
  // New added Oct 2008
  case layerArts.GetTitle().toLowerCase():
    if (functype == "show") { layerArts.Show(); }
    if (functype == "hide") { layerArts.Hide(); }
    if (functype == "delete") { deleteAllPointsOrGeometries (layerArts, geometryType); }
    if (functype == "return") { return layerArts; }
    break;
  case layerCycling.GetTitle().toLowerCase():
    if (functype == "show") {layerCycling.Show();}
    if (functype == "hide") {layerCycling.Hide();}
    if (functype == "delete") {deleteAllPointsOrGeometries (layerCycling, geometryType);}
    if (functype == "return") {return layerCycling;}
    break;
  case layerDisabled.GetTitle().toLowerCase():
    if (functype == "show") {layerDisabled.Show();}
    if (functype == "hide") {layerDisabled.Hide();}
    if (functype == "delete") {deleteAllPointsOrGeometries (layerDisabled, geometryType);}
    if (functype == "return") {return layerDisabled;}
    break;
  case layerHorses.GetTitle().toLowerCase():
    if (functype == "show") {layerHorses.Show();}
    if (functype == "hide") {layerHorses.Hide();}
    if (functype == "delete") {deleteAllPointsOrGeometries (layerHorses, geometryType);}
    if (functype == "return") {return layerHorses;}
    break;
  case layerBus.GetTitle().toLowerCase():
    if (functype == "show") {layerBus.Show();}
    if (functype == "hide") {layerBus.Hide();}
    if (functype == "delete") {deleteAllPointsOrGeometries (layerBus, geometryType);}
    if (functype == "return") {return layerBus;}
    break;
  case layerTrain.GetTitle().toLowerCase():
    if (functype == "show") {layerTrain.Show();}
    if (functype == "hide") {layerTrain.Hide();}
    if (functype == "delete") {deleteAllPointsOrGeometries (layerTrain, geometryType);}
    if (functype == "return") {return layerTrain;}
    break;

  case layerSport.GetTitle().toLowerCase():
    if (functype == "show") {layerSport.Show();}
    if (functype == "hide") {layerSport.Hide();}
    if (functype == "delete") {deleteAllPointsOrGeometries (layerSport, geometryType);}
    if (functype == "return") {return layerSport;}
    break;
  case layerLitter.GetTitle().toLowerCase():
    if (functype == "show") {layerLitter.Show();}
    if (functype == "hide") {layerLitter.Hide();}
    if (functype == "delete") {deleteAllPointsOrGeometries (layerLitter, geometryType);}
    if (functype == "return") {return layerLitter;}
    break;

default:
    return null;
  }
}


function touristPoint(layer, hashMarkers, icontype, point1, icon1, titled, infobox1) {
// hashMarkers is global tourists or shops hash
  var marker = new VEShape(VEShapeType.Pushpin, point1);
  marker.SetTitle("<h3>" + titled + "</h3>");
  marker.SetDescription(infobox1);
  layer.AddShape(marker);

  var currentPinHTML = pinHTML.replace('dummy', icontype + 'Icons ' + icon1); // add the 2 classes for the icon
  currentPinHTML = currentPinHTML.replace ('{0}', marker.GetID()); // give it a unique ID
  marker.SetCustomIcon(currentPinHTML);

  //Build out sidebar - but only one of each type. A 1 to Many - original code is 1 to 1.
  var currentMarkerID = marker.GetID();
  // Add to Hash Table an entry for each tourist type (using icon1) an array of markers to be flagged
  //if (hashMarkers == null) {  // test if hash created already
  //  hashMarkers = new Array (); }    // Perhaps Object is better
  // For each tourist type string add a new associative array
  if (hashMarkers[icon1] == null) {
    hashMarkers[icon1] = new Array(); }  // Add another dimension
  // add an extra marker for that icon tourist type
  (hashMarkers[icon1]).push( currentMarkerID );
}


function handleIcons(markers, toggle, layer, icontype){
  // on mouseover and checkbox handling for icons (tourist and shops)
  var currentShape;
  var detail;
  var detailContent;

  //var currentIcon;
  if (!markers) {
    return false; // there may no ruins e.g.
  }
  for(var marker=0; marker < markers.length; marker++ )
    {
    currentShape = layer.GetShapeByID(markers[marker]);
    // toggle may be '' to indicate that no toggle required. e.g. when hovering only need highlight
    if (toggle == 'show') {
      currentShape.Show(); continue; // Don't Highlight if user clicks Checkbox (side effect of adding to list of features)
    }
    if (toggle == 'hide') {
      currentShape.Hide(); continue;
    }
    // Now highlight all of the markers Titles so a list of what museums etc can be seen
    highlightPin (markers[marker], marker + 1); // Toggle the highlighter class with marker used as an indicator in place of deltaTime - time for Red
    // Add the description of this pin to the list
    if ($(icontype + markers[marker]) == null) { // id created from e.g. tourists +
        titleH = currentShape.GetTitle();
        // add text 1. ShopNameFromTitle
        detailContent = (marker+1) + ". " + titleH.substring(titleH.indexOf('>')+1, titleH.indexOf('</') );
        detail = new Element ('div', {'class': '', 'id': icontype + markers[marker] }).update(detailContent);
        $(icontype + 'Details').appendChild(detail);
    } else {
      $(icontype + markers[marker]).remove();
    }

/*     currentIcon = currentShape.GetCustomIcon();
      if (over) {
        currentShape.SetCustomIcon(currentIcon.replace('pinStyle', 'pinHoverStyle'));
        //map.ShowInfoBox(currentShape);
      } else {
        currentShape.SetCustomIcon(currentIcon.replace('pinHoverStyle', 'pinStyle'));
        //map.HideInfoBox(currentShape);
        }
   */
  } // for loop
      // If no childElements of holder loop through and add them (repeat loop probably slightly easier/quicker

  return false; // Stop default Action
}



function touristPoints () {
  // Do the work  - match with side bar in Control panel using - http://garzilla.net/vemaps/SideBar.aspx
  // Replace these with data from XML for flexibility
  layerTourist.Hide();
  var layer = layerTourist;
  //var hash = tourists; // holds hash of icons - called from HTML on mouseover handleTourists
  touristPoint (layer, tourists, 'tourist', createPoint (56.0188, -3.6), "osm_museum", "Bo\'ness and Kinneil Railway Exhibition", "Scottish Railway Exhibition<br />See the Engines and Wagons up close - just cross the bridge from the station<br />Bo'ness Station, Union Street, Bo'ness, West Lothian EH51 9AQ <br />01506 822298<br /> <a target='_blank' href='http://www.srpsmuseum.org.uk/'>www.srpsmuseum.org.uk</a>");
  touristPoint (layer, tourists, 'tourist', createPoint (56.01812, -3.59836), "osm_museum", "Bo\'ness and Kinneil Railway", "Scottish Railway Preservation Society<br />Bo'ness Station, Union Street, Bo'ness, West Lothian EH51 9AQ <br />01506 822298<br /> <a target='_blank' href='http://www.srps.org.uk/'>www.srps.org.uk</a>");
  touristPoint (layer, tourists, 'tourist', createPoint (56.01735, -3.58500), "osm_museum", "Bo\'ness Motor Museum", "Motor Museum, Childrens Soft Play Area<br >Bridgeness Road, Bo\'ness<br />(01506) 827007<br /><a target='_blank' href='http://www.motor-museum.bo-ness.org.uk/'>www.motor-museum.bo-ness.org.uk</a>");
  touristPoint (layer, tourists, 'tourist', createPoint (56.00752, -3.63482), "osm_monument", "Kinneil House, Estate and Museum", "Museum, Roman Fortlet, Walks, Play Area<br /><a target='_blank' href='http://www.kinneil.org.uk/'>www.kinneil.org.uk</a>");
  touristPoint (layer, tourists, 'tourist', createPoint (56.00533,-3.64124), "osm_ruin", "Antonine Wall, Kinneil Fortlet", "Remains of excavated Roman Fortlet, Walks<br /><a target='_blank' href='http://www.kinneil.org.uk/'>www.kinneil.org.uk</a>");
  touristPoint (layer, tourists, 'tourist', createPoint (56.00577, -3.60783), "osm_theatre", "Barony Theatre", "Amateur Dramatics<br />Borrowstoun Road, Bo\'ness <br />contact Wendy Turner on 01506 824414 <br /><a target='_blank' href='http://www.baronyplayers.co.uk/'>www.baronyplayers.co.uk</a>");
  touristPoint (layer, tourists, 'tourist', createPoint (56.00848, -3.59798), "osm_swim", "Recreation Centre", "Sports Centre, Fitness Suite, Swimming Pool etc.<br />01506 778510<br /><a target='_blank' href='http://www.falkirk.gov.uk/services/community/sport_and_activities/leisure_facilities/boness_recreation_centre/boness_recreation_centre.aspx'>Website</a>");
  touristPoint (layer, tourists, 'tourist', createPoint (56.0191, -3.59154), "osm_sailing", "Upper Forth Boat Club", "Haney\'s Way, Grangepans, EH51 9TD<br /><a target='_blank' href='http://www.ufbc.co.uk/'>www.ufbc.co.uk</a>");
  touristPoint (layer, tourists, 'tourist', createPoint (56.02074, -3.60729), "osm_marina", "Bo\'ness Harbour", "Tidal, Muddy bottom, Ladders, Access to shops and amenities");

  touristPoint (layer, tourists, 'tourist', createPoint (56.00782, -3.63288), "osm_museum", "Kinneil Museum", "Museum, Roman Fortlet, Walks, Play Area<br /><a target='_blank' href='http://www.kinneil.org.uk/'>www.kinneil.org.uk</a>");
  touristPoint (layer, tourists, 'tourist', createPoint (56.01756, -3.59979), "osm_information", "Tourist Information", "Bo\'ness & Kinneil Railway Bo\'ness Station, Union Street, Bo\'ness, West Lothian, EH51 9AQ<br />(8452) 255121<br /> <a target='_blank' href='http://guide.visitscotland.com/vs/guide/5,en,SCH1/objectId,INF51908Svs,curr,GBP,season,at1,selectedEntry,home/home.html'>Visit Scotland Website</a>");
  touristPoint (layer, tourists, 'tourist', createPoint (55.99679, -3.59699), "osm_golf", "West Lothian Golf Club<br />", "Airngath Hill, Bo\'ness, West Lothian, EH49 7RH<br />01506 826030<br /><a target='_blank' href='http://www.westlothiangc.com/'>www.westlothiangc.com</a>");
  touristPoint (layer, tourists, 'tourist', createPoint (56.00944, -3.61156), "osm_football", "Bo\'ness United Football Club", "<a target='_blank' href='http://www.bonessunited.co.uk/homepage.htm'>www.bonessunited.co.uk</a>");
  touristPoint (layer, tourists, 'tourist', createPoint (56.00488, -3.63317), "osm_forest", "Kinneil Woodland", "Museum, Roman Fortlet, Walks, Play Area<br /><a target='_blank' href='http://www.kinneil.org.uk/'>www.kinneil.org.uk</a>");
  touristPoint (layer, tourists, 'tourist', createPoint (56.01765, -3.60820), "osm_cinema", "Hippodrome Cinema<br />",
    "This fantastic pre-art deco picture palace opened in 1912. Newly restored and open 7 days a week showing the latest movies and classic gems.<br />10 Hope Street Bo\'ness EH51 0AA<br />01324 506850<br /> <a target='_blank' href='http://www.falkirk.gov.uk/hippodrome'>www.falkirk.gov.uk/hippodrome</a>");
  touristPoint (layer, tourists, 'tourist', createPoint (56.01765, -3.605), "osm_museum", "Victorian Steet Fair 24thMay<br />", "<div><img src='data/streetfairposter.jpg' width='220' height='300' /><br /><br /><a target='fair' href='http://kinneil.wordpress.com/2009/05/07/meet-the-friends-at-street-fair/'><b>Click for Details</b></a></div>");

  touristPoint (layer, tourists, 'tourist', createPoint (56.0022, -3.60911), "osm_bus", "HippoBus from Linlithgow - South", 'Click the button to open up the Traveline control.<br /><input type="button" value="Goto Hippodrome" onclick="verticalAccordion.activate($(\'hippobus\')); $(\'destinationLocation\').value=\'EH510AA\'; $(\'originLocation\').value=\'Linlithgow, West Lothian\';   "></input>');
  touristPoint (layer, tourists, 'tourist', createPoint (56.01047, -3.63278), "osm_bus", "HippoBus from Grangemouth - West", 'Click the button to open up the Traveline control.<br /><input type="button" value="Goto Hippodrome" onclick="verticalAccordion.activate($(\'hippobus\')); $(\'destinationLocation\').value=\'EH510AA\'; $(\'originLocation\').value=\'Grangemouth\';   "></input>');
  touristPoint (layer, tourists, 'tourist', createPoint (56.00562, -3.5757), "osm_bus", "HippoBus from Edinburgh - East", 'Click the button to open up the Traveline control.<br /><input type="button" value="Goto Hippodrome" onclick="verticalAccordion.activate($(\'hippobus\')); $(\'destinationLocation\').value=\'EH510AA\'; $(\'originLocation\').value=\'Edinburgh\';   "></input>');

  touristPoint (layer, tourists, 'tourist', createPoint (56.0096, -3.632), "osm_steamtrain", "SteamTrain Viewpoints in Kinneil Estate", '<a target="movies" href="images/kinneil2008_0812train.wmv">Click to Play Steam Train Video</a>');
  // FF process - http://forums.mozillazine.org/viewtopic.php?f=8&t=16497&st=0&sk=t&sd=a&sid=1466d0cd6266510fe33c28ed6ca600ed&start=160
  touristPoint (layer, tourists, 'tourist', createPoint (56.018, -3.601), "osm_steamtrain", "SteamTrain At Kinneil Station", '<a target="movies" href="data/SteamThruAges.wmv">Click to Play Steam Train Video</a>');
}


function touristView () {
  // show Icons in special layer only shown when button pressed - Toggle?
  if (layerTourist.IsVisible()) {
    layerTourist.Hide();
  } else {
    layerTourist.Show();
    map.SetCenterAndZoom(new VELatLong(56.008, -3.609), TOURISTZOOM); 
  }
}

function shopPoints () {
  // Do the work  - match with side bar in Control panel using - http://garzilla.net/vemaps/SideBar.aspx
  // Replace these with data from XML for flexibility
  layerShops.Hide();
  var layer = layerShops;
  touristPoint (layer, shops, 'shop', createPoint (56.01746, -3.60748), "osm_clothes", "Mary Ann\'s Ladies Fashions", "");
  touristPoint (layer, shops, 'shop', createPoint (56.01727, -3.6075), "osm_store", "Convenience Store", "More info");
  touristPoint (layer, shops, 'shop', createPoint (56.01732, -3.60639), "osm_library", "Inkspot and Silverleaf Bookshop", "<a target='_blank' href='http://www.inkspotandsilverleaf.co.uk/home.html'>Inkspot Home Page</a>");
  touristPoint (layer, shops, 'shop', createPoint (56.0180, -3.6088), "osm_library", "Bo\'ness Library", "A very beautiful library situated in the historic Scotlands Close");
  touristPoint (layer, shops, 'shop', createPoint (56.0184, -3.6069), "osm_postoffice", "Bo\'ness Post Office", "");
  touristPoint (layer, shops, 'shop', createPoint (56.0172, -3.6093), "osm_chemists", "Drummond Chemists", "");
  touristPoint (layer, shops, 'shop', createPoint (56.01746, -3.6084), "osm_chemists", "Moss Chemists", "");
  touristPoint (layer, shops, 'shop', createPoint (56.01735, -3.6071), "osm_artscrafts", "Art Shop", "Oils, acrylics etc");

  touristPoint (layer, shops, 'shop', createPoint (56.01705, -3.60943), "osm_opticians", "Hendry\'s Opticians", "");
  touristPoint (layer, shops, 'shop', createPoint (56.01734, -3.607477), "osm_store", "Bo\'ness Toy Shop", "Ben10 stockists (whatever that is)");

  touristPoint (layer, shops, 'shop', createPoint (56.01756, -3.60728), "osm_store", "Branches Gift Shop", "");
  touristPoint (layer, shops, 'shop', createPoint (56.0177, -3.60895), "osm_hardware", "McFarlane Electrical/Lighting", "");
  touristPoint (layer, shops, 'shop', createPoint (56.01726, -3.60883), "osm_store", "Pet & Aquatic Supplies", "");
  touristPoint (layer, shops, 'shop', createPoint (56.0173, -3.60803), "osm_hardware", "PJ Robertson Electrical & TV Repairs", "Don't scrap it! Repair it");
  touristPoint (layer, shops, 'shop', createPoint (56.01749, -3.60769), "osm_store", "Butchers and Deli", "Burns the Butcher - Country Harvest Deli");

  touristPoint (layer, shops, 'shop', createPoint (56.01709,-3.6088), "osm_hardware", "Lyons Hardware", "DIY");
  touristPoint (layer, shops, 'shop', createPoint (56.01779, -3.60865), "osm_hardware", "Bo\'ness Car & Cycle Parts", "");
  touristPoint (layer, shops, 'shop', createPoint (56.01742, -3.60859), "osm_hardware", "T Homewood", "Flooring - Karndean");

  // Cafes
  touristPoint (layer, shops, 'shop', createPoint (56.0173, -3.60667), "osm_cafe", "Craigview Cafe", "");
  touristPoint (layer, shops, 'shop', createPoint (56.01749, -3.6086), "osm_cafe", "Brian\'s Cafe", "");
  touristPoint (layer, shops, 'shop', createPoint (56.01726, -3.60865), "osm_cafe", "Country Fayre", "");
  touristPoint (layer, shops, 'shop', createPoint (56.01686, -3.60986), "osm_cafe", "Corvi\'s Cafe", "Fish and Chips");
  touristPoint (layer, shops, 'shop', createPoint (56.01734, -3.6077), "osm_cafe", "Mathiesons", "");

  // Jewellery - Needs Icon - perhaps share with Gifts
  touristPoint (layer, shops, 'shop', createPoint (56.01739, -3.60834), "osm_store", "Pebbles Jewellery", "");
  touristPoint (layer, shops, 'shop', createPoint (56.01727, -3.6097), "osm_store", "Ross the Jewellers", "");

} //

function shopView () {
  // show Icons in special layer only shown when button pressed - Toggle?
  if (layerShops.IsVisible()) {
    layerShops.Hide();
  } else {
    layerShops.Show();
    map.SetCenterAndZoom(new VELatLong(56.01735, -3.6073), SHOPZOOM);
  }
}


function showSpecificFeatures () {
  // show the features selected by user from Select Options box
  // Perhaps should do boundingboxsearch but subset already created so keep simple for now
  var searcher = document.getElementById('searcher');
  for(var i=0;i<dataLayer.GetShapeCount();i++)
  {
    var shape = dataLayer.GetShapeByIndex(i);
    if (shape.GetTitle() == searcher.options[searcher.selectedIndex].text) {
      shape.Show(); }
  }
  dataLayer.Show();
}

function boundingBoxSearch() // From Import Callback - should take a layer param but call ignored if param exists????
{
  //hide previous results
  hideEachShape();

  //clear the search shape layer
  searchShapeLayer.DeleteAllShapes();

  //bounding box coordinates
  view = map.GetMapView();
  var topLeft = view.TopLeftLatLong;
  var bottomRight = view.BottomRightLatLong;

  //doesn't checks to see if bounding box crosses 180 degrees
    //iterate through shapes in dataLayer
    for(var i=0; i < dataLayer.GetShapeCount() ;i++)
    {
      var shape = dataLayer.GetShapeByIndex(i);
      var latlong = shape.GetPoints()[0];
      var lat = latlong.Latitude;
      var lon = latlong.Longitude;

      if(topLeft.Longitude <= lon && bottomRight.Longitude >= lon
        && topLeft.Latitude >= lat && bottomRight.Latitude <= lat)
      {

      //alert ("Type: " + shape.GetType() + "<br />" +
      //   "Title: " + shape.GetTitle() + "<br />" +
      //   "Description: " + shape.GetDescription());

//      if ((shape.GetTitle()).charAt(0) != "0")
//      if (document.getElementById('txtWhat').value == shape.GetTitle()) {
        //shape.Show();
        // And Add the Option to the Selection Form Element
        //var optionHTML = '<option value ="' + shape.GetTitle() + '">' + shape.GetTitle() + '</option>';
        var opt=document.createElement('option');
        opt.text=shape.GetTitle();
        var searcher = document.getElementById('searcher');
        try
          {
          searcher.add(opt,null); // standards compliant - from w3schools HTML DOM Select
          }
        catch(ex)
          {
          searcher.add(opt); // IE only
          }
        }
    }
   //draw search area
  //drawBox(topLeft,bottomRight); -- This appears to interfere with the videos -
  //map.SetMapView([topLeft,bottomRight]);
}

function drawBox(topLeft,bottomRight)
{
  var topRight = new VELatLong(topLeft.Latitude,bottomRight.Longitude);
  var bottomLeft = new VELatLong(bottomRight.Latitude,topLeft.Longitude);
  var box = new VEShape(VEShapeType.Polyline,[topLeft,topRight,bottomRight,bottomLeft,topLeft]);
  box.HideIcon();
  searchShapeLayer.AddShape(box);
}



/* Example of Prototype Event Binding
    var obj = { name: 'A nice demo' }; // a Hash Key-Value Associative Array
    function handler(e) { // http://www.prototypejs.org/api/function/bindAsEventListener
      var tag = Event.element(e).tagName.toLowerCase();  // Prototype Event Object - set its element
      var data = $A(arguments);
      data.shift(); // move onto next in array
      // oddly this is set to 1st parameter (why not something to do with e?)
      alert(this.name + '\nClick on a ' + tag + '\nOther args: ' + data.join(', '));
    }
*/

function toggleRouteAreaLayer(e) { // Prototype bindAsEventListener on Event - Observer
  // 1st param in bindAsEventListener() becomes this. Event turns up as e. Other params tag along
  //alert ("Event " + Event.element(e).innerHTML);
  var showMode = true;
  if (Event.element(e).className == "routeOn" ) {
    showMode = false;
    Event.element(e).className='routeOff';
  } else {
    Event.element(e).className='routeOn';
  }

  // but need to be read using $A(arguments). If Event not required could use bind instead
  for (var shapeIdx = 0; shapeIdx < layerRouteArea.GetShapeCount(); shapeIdx++ ) {
    if (layerRouteArea.GetShapeByIndex(shapeIdx).GetTitle() == this) {
      // now hide all the Pins and (perhaps Geometries?) within the RouteArea
      for (var count = 0; count < map.GetShapeLayerCount(); count++) {
        var filterLayerTitle = map.GetShapeLayerByIndex(count).GetTitle().toLowerCase();
        var shapeLayer = handleLayer(filterLayerTitle, "return", "");
        // A Match so iterate through shapes in dataLayer
        if (shapeLayer == null) {
          continue; }  // ignore non Handled Layers

        for(var i=0;i<shapeLayer.GetShapeCount();i++)
        {
          var shape = shapeLayer.GetShapeByIndex(i);
          var latlong = shape.GetPoints()[0]; // for geometries this would simply check the start - perhaps should be better query
          var lat = latlong.Latitude;
          var lon = latlong.Longitude;

          // arguably limit the area selection to current view bounds - good for speed but... TODO
          //if(topLeft.Longitude <= lon && bottomRight.Longitude >= lon
          //  && topLeft.Latitude >= lat && bottomRight.Latitude <= lat) {
          // Shape is within view area so ensure it is within the selected polygon
            if (GeoHelper.IsInPolygon(layerRouteArea.GetShapeByIndex(shapeIdx).GetPoints(), latlong)) {
               if (showMode) {
                 shape.Show();
               } else {
                 shape.Hide();
               }
            }
        }
        // Now check toggle checkbox for hiding areas
        /*
        if ($("checkbox:RouteArea").checked) {
          layerRouteArea.GetShapeByIndex(shapeIdx).Hide();
        } else {
          layerRouteArea.GetShapeByIndex(shapeIdx).Show();
        }
        */
      // Finally, hide the Hull to show that there are no pins available
      }
      if (showMode) {
        layerRouteArea.GetShapeByIndex(shapeIdx).Show();
      } else {
        layerRouteArea.GetShapeByIndex(shapeIdx).Hide();
      }
    }
  }
}

function showPolygonFeatures () {
  // user selects route area to search in
  hideEachShape();

  //clear the search shape layer
  searchShapeLayer.DeleteAllShapes();

  //bounding box coordinates
  view = map.GetMapView();
  var topLeft = view.TopLeftLatLong;
  var bottomRight = view.BottomRightLatLong;

  var searcher = document.getElementById('polygonsearch');

  //doesn't checks to see if bounding box crosses 180 degrees

  // look in specified polygon(s) via selected option
  for (var loop = 0; loop < searcher.length; loop++ ) {
    if (searcher.options[loop].selected) {
      // now we know one of the selected areas find its index in layerRouteArea
      for (var shapeIdx = 0; shapeIdx < layerRouteArea.GetShapeCount(); shapeIdx++ ) {
        if (layerRouteArea.GetShapeByIndex(shapeIdx).GetTitle() == searcher.options[loop].text) {
          // A Match so iterate through shapes in dataLayer
          for(var i=0;i<dataLayer.GetShapeCount();i++)
          {
            var shape = dataLayer.GetShapeByIndex(i);
            var latlong = shape.GetPoints()[0];
            var lat = latlong.Latitude;
            var lon = latlong.Longitude;

            // arguably limit the area selection to current view bounds - good for speed but... TODO
            if(topLeft.Longitude <= lon && bottomRight.Longitude >= lon
              && topLeft.Latitude >= lat && bottomRight.Latitude <= lat) {
              // Shape is within view area so ensure it is within the selected polygon
              if (GeoHelper.IsInPolygon(layerRouteArea.GetShapeByIndex(shapeIdx).GetPoints(), latlong)) {
                 shape.Show();
              }
            }
          }
          // Now check toggle checkbox for hiding areas
          if (document.getElementById("checkbox:RouteArea").checked) {
            layerRouteArea.GetShapeByIndex(shapeIdx).Hide();
          } else {
            layerRouteArea.GetShapeByIndex(shapeIdx).Show();
          }

        }
      }
    }
  }
}


function AddPushpin(point1, icon1, infobox1)
  {
  // Do the work
  var shape = new VEShape(VEShapeType.Pushpin, point1 );
  //Set the icon
  shape.SetCustomIcon(icon1);
  //Set the info box
  // map.ClearInfoBoxStyles(); FF3.02 - z-index problem when this is used
  shape.SetTitle("<h3>Hot Spot</h3>");
  shape.SetDescription(infobox1);
  //Add the shape to the map
  map.AddShape(shape);
  pinid++;
  }

function addLocation (location, icon1, infobox1)
  {
    currentLocation = new VEShape(VEShapeType.Pushpin, location  );
    //Set the icon
    currentLocation.SetCustomIcon(icon1);
    //Set the info box
    // map.ClearInfoBoxStyles(); FF3.02 - z-index problem when this is used
    currentLocation.SetTitle("<h3>Custom HotSpot</h3>");
    currentLocation.SetDescription(infobox1);
    //Add the shape to the map
    map.AddShape(currentLocation);
  }

function moveLocation (location)
  {
  map.SetZoomLevel(ZOOMLEVEL);
  currentLocation.SetPoints(location);
  //map.PanToLatLong(location); // prevent jumps in location if next spot well offscreen but too jerky
  map.SetCenterAndZoom(location, ZOOMLEVEL); // currentLocation.GetPoints()[0]just the last pointStart (should really be first)
}

function moveToForeshore()
{
  map.SetCenterAndZoom(new VELatLong(56.0179, -3.597), ZOOMLEVEL); // just the last pointStart (should really be first)
}

function moveToLibrary() {
  map.SetCenterAndZoom(new VELatLong(56.01806, -3.60876), ZOOMLEVEL); // just the last pointStart (should really be first)
  }


function moveToKinneilCarPark() {
  map.SetCenterAndZoom(new VELatLong(56.00581, -3.63379), ZOOMLEVEL); // just the last pointStart (should really be first)
  }


function importGPXCallback (gpx) {
  // Importing causes the layer to be renamed after the name of the 1st <name>ed waypoint
  // perhaps there should be another level to the GPX to contain a name for the title
  // rename here
  gpx.SetTitle("DataLayer");
  var count = gpx.GetShapeCount();
// alert ("importGPX Count " + count);
  for(var i=0; i < count; ++i) {
    var shape = gpx.GetShapeByIndex(i);
    shape.SetCustomIcon("<img src='icons/default.png'/>");
    var LL = shape.GetPoints()[0];
    LL.Latitude = floatTrim (LL.Latitude, 6);  // store with fewer points
    LL.Longitude = floatTrim (LL.Longitude, 6);

    try {
      map.ClearInfoBoxStyles(); // required due to Styling changes in HTML returned by buildPopupWindow uses customInfoBox-body
      shape.SetDescription(buildPopupWindow (shape.GetID(), LL, false, "" ));
      UpdateSQLQuery(shape.GetID());
      //alert (sqlBuilder);
    } catch (err) {alert ("Import GPX Error - " + err.message); }

  }
}

function AddGPXPushpins (filename) {

//  if (browser == "FF") {
//    alert ("Unfortunately this operation may not work when using FireFox");
//  }
  // Use GPSBabel with input as USB Garmin and output Waypoints as GPX
  // GPSBabel GUI-2 (in my Downloads/GPS) 2.15.0
  //gpsbabel.exe -p "" -w -i garmin -f usb: -x sort -o gpx -F "C:\Garmin\Tracks\Testing\waypoints.gpx"
/*
<?xml version="1.0" encoding="UTF-8"?>
<gpx
 version="1.0"
creator="GPSBabel - http://www.gpsbabel.org"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.topografix.com/GPX/1/0"
xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd">
<time>2008-08-01T11:56:57Z</time>
<bounds minlat="25.061783362" minlon="-94.799016668" maxlat="57.478145622" maxlon="121.640266674"/>
<wpt lat="56.008756505" lon="-3.590723835">
  <name>001</name>
  <cmt>001</cmt>
  <desc>001</desc>
  <sym>Flag, Blue</sym>
</wpt>
<wpt lat="57.290786197" lon="-6.175745521">
  <ele>26.081177</ele>
  <name>Sliga</name>
  <cmt>11-AUG-07 8:46:55</cmt>
  <desc>11-AUG-07 8:46:55</desc>
  <sym>Short Tower</sym>
</wpt>
</gpx>
*/
  try {
  //map.AddShapeLayer (searchShapeLayer); // Just holds draw box
  var importLayer = new VEShapeLayer(); // A weird timing issue? need this line or code won't work TODO
  //   2. Create a shape source specification with type VEDataType.ImportXML, the url to the XML file, and a reference to the shape layer:

  // MADNESS - FF returns only the filename part while IE returns full path as expected but with backslashes\
  if (filename.indexOf("\\") > - 1) {
    filename = filename.substr(filename.lastIndexOf("\\")+1); }  // just use the filname and junk the path if provided

 // Perhaps forbid upload of GPX and Route where the user is not logged in... but see this as a trial.
  var filepath;
  if (currentUser) {   // if someone logged in - do this to avoid data//route if currentUser empty
    filepath = DATAPATH + currentUser + "/" + filename;
  } else {
    filepath = DATAPATH + filename;
  }
  //filepath = (window.location.href).substring(0, (window.location.href).lastIndexOf("/")) + "/data/" + filename; 

  //var shapeSpec = new VEShapeSourceSpecification(VEDataType.ImportXML, filename, dataLayer);
  // To get a Collection - Send To Email to get this http://maps.live.com/?v=2&encType=1&cid=D495B97A9705D66A!108
  // Collection draws in IE but not FF3.02 but callback doesn't run for IE Collection
  // Callback doesn't run for XML in FF3.02 unless the callback does NOT have the parameter....
  //alert (filename);
  // importLayer works on IE only (6and7) but only from parthian not from localhost. datalayers fails
  // Perhaps Import only works with a new layer. POSSIBLY RELATIVE PATH WITH DATA ON SAME DOMAIN.
  var shapeSpec = new VEShapeSourceSpecification(VEDataType.ImportXML, filepath, dataLayer);
  //var shapeSpec = new VEShapeSourceSpecification(VEDataType.VECollection, "D495B97A9705D66A!108", dataLayer);
  //   3. Import the data into shape layer by calling ImportShapeLayerData on the map with the specification:
  map.ImportShapeLayerData(shapeSpec, importGPXCallback, false); //was boundingBoxSearch
    } catch(e) {
        alert("Error " + e.message);
    }
}


function AddPolyline()
{
  // Artificially add the line to make the Wheelchair route
  // TODO Automate from OSM Database tags
  var layerPL = new VEShapeLayer();
  map.AddShapeLayer (layerPL);

  var ll = map.GetCenter();
  var lat = ll.Latitude;
  var lon = ll.Longitude;

  lat = 56.00213;
  lon = -3.628334;

  var shape = new VEShape(VEShapeType.Polyline, [new VELatLong(lat,lon),
                                           new VELatLong(56.0034089,-3.6435199),
                                           new VELatLong(56.0028765,-3.6371198),
                                           new VELatLong(56.0030049,-3.6368470),
                                           new VELatLong(56.0039106,-3.6316067),
                                           new VELatLong(56.0022726,-3.6303099),
                                           new VELatLong(56.0028765,-3.6371198),
                                           new VELatLong(56.0025831,-3.6365993),
                                           new VELatLong(56.0019211,-3.6349271)]);

  //Set the line color
  var lineColor = new VEColor(Math.round(254*Math.random()),Math.round(254*Math.random()),Math.round(254*Math.random()),Math.random());
  lineColor = new VEColor(254,254,0, 1); // Math.random());
  shape.SetLineColor(lineColor);

  //Set the line width
  var lineWidth = 4;
  shape.SetLineWidth(lineWidth);

  // Set the icon
  // shape.SetCustomIcon(icon);
  // Set the info box
  // map.ClearInfoBoxStyles(); FF3.02 - z-index problem when this is used
  shape.SetTitle("<h2>Custom HotSpot</h2>");
  shape.SetDescription("Wheelchair or Buggy Access<br />This information might be seasonal - some paths may become too muddy in winter");

  //Add the shape the the map
  layerPL.AddShape(shape);
  pinid++;
  }

function AddLine(point1, point2, iconinfo, route, hotspots, tooltip, startNode, startOrder, relRef)
  {

  var shape = new VEShape(VEShapeType.Polyline, [point1, point2]);
  shape.SetZIndex(null, 61); // Suggest Mapcruncher layers are at z59 and z60 for ConvexH see http://msdn.microsoft.com/en-us/library/bb877869.aspx
  var lineColor;
  lineColor = routeInfo [route];
  //Set the line color
  shape.SetLineColor(lineColor);
  //Set the line width
  var lineWidth = 4;
  shape.SetLineWidth(lineWidth);

  //Set the info box - eventually this information will be effectively stored in the Image Map
  // map.ClearInfoBoxStyles(); FF3.02 - z-index problem when this is used
  shape.SetTitle("<h3>HotSpot</h3>");
/*  Potentially useful for DEBUG - list of all 4 photos
for (i in iconinfo) {    FIXME ########  prototype does not like array looping like this
    if (iconinfo [i] != undefined) {
        theImage = "images/" + iconinfo[i];
        links = links + "<a id='image" + i + "' href='" + theImage + "' onclick='photoViewer(event); return false;' /a>" + " Terrestrial Image " + i + "<br />";
        }
    }  // for iconinfo
*/
  var hash;
  hash = parseHotSpot (hotspots);
  map.ClearInfoBoxStyles();
  shape.SetDescription(hash['hotHtml']);
  // Buld the instructions to create the Icon based on Keywords from hotspot
  if (hash['iconName']) { // use default if no name provided
    shape.SetCustomIcon ( buildPinIcon (shape.GetID(), hash['iconName']) ); }

  if (hotspots ["hotspot1"] == null) {
    shape.HideIcon(); }  // SRSM 27thMay - Remove Pins to Declutter - all lines have a Pin

  layerPL.AddShape(shape);

  // if there is a start add a pushpin with node information as 'name'
  if (startNode) { //shouldn't have 2 elements with same id/name but unlikely to co-exist with icon
    if (startOrder == "1st") {
      startPoint = point1;
    } else {
      startPoint = point2;
    }
    var buttonData = "<p>Entrance - " + tooltip + "</p><input id=" + startNode + " name=" + relRef + " value='Start from Here' type='button' onclick='distanceTravelled=0; moveHandler(event);'/><br />";
    AddPushpin(startPoint, iconblue, buttonData);
    }
  }


  function AddPolygon(points)
  {
  // Not used just now
  var ll = map.GetCenter();
  var lat = ll.Latitude;
  var lon = ll.Longitude;

  var shape = new VEShape(VEShapeType.Polygon, points);

  //Set the line color
  var lineColor = new VEColor(Math.round(254*Math.random()),Math.round(254*Math.random()),Math.round(254*Math.random()),Math.random());
  shape.SetLineColor(lineColor);

  //Set the line width
  var lineWidth = Math.round(5*Math.random()+1);
  shape.SetLineWidth(lineWidth);

  //Set the fill color
  var fillColor = new VEColor(Math.round(254*Math.random()),Math.round(254*Math.random()),Math.round(254*Math.random()),0.8);
  shape.SetFillColor(fillColor);

  //Set the icon
  // shape.SetCustomIcon(icon);

  //Set the info box
  map.ClearInfoBoxStyles();
  shape.SetTitle("<h2>Custom HotSpot</h2>");
  shape.SetDescription(infobox);

  //Add the shape to the map
  map.AddShape(shape);
  pinid++;
  }



function setZoomLevel () {
  ZOOMLEVEL = map.GetZoomLevel(ZOOMLEVEL);  // best to run this from button and get data from text box
}


function createFeedback (theDIV, theHTML, theId, thePosn, theText, startNode, endNode) {
  // Start of function to add information to screen. Will be adapted to have optional parameters and test etc.
  // compassLoad (startNode, endNode); unreliable in reverse
  if (endNode != 0) { // Must be a Reverse Arrow - don't highlight - too many problems
    // Update 14/1/09 - Highlight the line - drawing Correct compass too difficult
    var snsIndex = Math.abs(startNode) - 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(endNode) - 1;
    var sneLat = parseFloat(nodes[sneIndex].getAttribute("lat"));
    var sneLon = parseFloat(nodes[sneIndex].getAttribute("lon"));

    var point1 = new VELatLong(snsLat,snsLon);
    var point2 = new VELatLong(sneLat,sneLon);
    var shape = new VEShape(VEShapeType.Polyline, [point1, point2]);
    shape.SetZIndex(null, 60); // Draw behind Layer in addLine    - MAGIC NUMBER TODO
    shape.SetLineColor(new VEColor(255,255,0, 0.9));
    shape.SetLineWidth(8); // bigger than current line in addLine
    shape.SetTitle("feedback");
    shape.HideIcon(); // SRSM 27thMay - Remove Pins to Declutter - all lines have a Pin
    layerPL.AddShape(shape);
    // another global var
    feedbackPinID = shape.GetID(); // know which one to delete
    }
}


function removeLocation () {
  return function () { // scopePreserver
    hidefeedbackBox();
    // Remove the Feedback Line Route Highlighter
    if (feedbackPinID) {
      var shape = map.GetShapeByID(feedbackPinID);  // get the pin
      map.DeleteShape (shape);
      feedbackPinID = "";
    }
  }
}

function getRouteColour (route) {
  var lineColor;
  switch (route) {
    case 'west': lineColor = new VEColor(254,254,0, 1); // Yellow
      break;
    case 'house': lineColor = new VEColor(254,0,0, 1); // Red
      break;
    default:
      lineColor = new VEColor(254,0,254, 1); //
      break;
  }
  return lineColor;
}

function createColour (red, green, blue, transparency)
{ // Adaptor
  return new VEColor(red, green, blue, transparency);
}


function createPoint (lat,lon)
{ // Adaptor
  return new VELatLong(lat,lon);
}
