// ----------------------------------------------------------------------------
// Written by: J. C. Parker, 2009 - 2010
// Copyright:  J. C. Parker, 2009 - 2010
// ----------------------------------------------------------------------------

if (typeof OpenLayers === 'undefined') alert ('The Openlayers API is off-line. Please try again later.');

var map = null;

// base layers
var osmcBL = null;
var gPhyBL = null;
var gMapBL = null;
var gSatBL = null;
var gHybBL = null;
var osmBL  = null;
var nearBL = null;

// overlays
var osmpL = null;
var mgspL = null;
var extpL = null;
var propL = null;
var sydcL = null;
var gKMLL = null;
var dseMpiL       = null;
var melNewsL      = null;
var adlCmpgnL     = null;
var sydCmpgnL     = null;
var sydCCTVL      = null;
var timeByBikeL   = null;
var mgsnL         = null;
var radarL        = null;
var melBikeShareL = null;

// controls
var selectControl   = null;
var selectedFeature = null;
var layerCtrl       = null;
var permaLink       = null;

var km_5_to_2     = 0.000006154;  //     5km:2km boundary = 1/((217000 - 108000)/2 + 108000)
var km_10_to_5    = 0.000003077;  //    10km:5km boundary = 1/((433000 - 217000)/2 + 217000)
var km_20_to_10   = 0.000001538;  //   20km:10km boundary = 1/((867000 - 433000)/2 + 433000)
var km_50_to_20   = 0.000000667;  //   50km:20km boundary = 1/((2000000 - 1000000)/2 + 1000000)
var km_100_to_50  = 0.000000200;  //  100km:50km boundary = 1/((7000000 - 3000000)/2 + 3000000)
var km_200_to_100 = 0.000000095;  // 200km:100km boundary = 1/((14000000 - 7000000)/2 + 7000000)
var km_500_to_200 = 0.000000048;  // 500km:200km boundary = 1/((28000000 - 14000000)/2 + 14000000)

// MELBOURNE
// Melbourne's GPO is located at Australian Map Grid (AMG) 320 725 E, 5812 900 N, Zone 55H
var MELB_CENTER_LAT = -37.8135784679527;
var MELB_CENTER_LNG = 144.963341474327;

// holds the last state that the map was centered on
var lastState = 'Unknown';

var cookieName    = 'Bike Trails';
var cookieVersion = 1;

// the default permalink variables
// set the default to Melbourne's GPO
var mapCenterLat = MELB_CENTER_LAT;
var mapCenterLng = MELB_CENTER_LNG;

var mapZoom      = 8;  // higher is more closer in
var mapTypeText  = "p";

var trailIdx = 0;

// time by bike parms
var bikeTimeLat = mapCenterLat;
var bikeTimeLng = mapCenterLng;
var speedIdx    = 2;     // 18 kph
var speed       = 18.0;
var ringsLocked = false;

var showAboutMsg = true;
var showLayerSw  = true;

var timerID;

// ----------------------------------------------------------------------------

var places = [

'U',                                      // -- Select a Place --
'-34.927013,138.599520,12',               // Adelaide - GPO at 2 Franklin St
'-37.561562,143.858216,12',               // Ballarat
'-36.758123,144.281561,12',               // Bendigo - old GPO at Sturt & Lydiard St
'-27.468174,153.028173,12',               // Brisbane - GPO between Queen St & Elizabeth St
'-16.922104,145.775784,12',               // Cairns Mall
'-35.278104,149.128058,12',               // Canberrra - GPO 53-73 Alinga Street
'-12.460969,130.841802,12',               // Darwin - GPO 48 Cavenagh St
'-38.147655,144.362116,12',               // Geelong
'-28.001970,153.428626,12',               // Gold Coast - the old pub
'-33.429724,151.341425,12',               // Gosford - old GPO at 27 Mann St
'-42.882594,147.330243,12',               // Hobart - GPO at 9 Elizabeth St
'-37.8135784679527,144.963341474327,12',  // Melbourne - old GPO at Elzabeth St & Bourke St
'-32.927469,151.783447,12',               // Newcastle - old GPO at Hunter St & Bolton St
'-31.952265,115.859198,12',               // Perth - old GPO at St George's Terrace & Barrack St
'-26.599309,153.051897,12',               // Sunshine Coast - some arbitary spot
'-33.867716,151.207699,12',               // Sydney - GPO at No. 1 Martin Place
'-27.561586,151.956181,12',               // Toowoomba - old GPO at 136 Margaret St 
'-19.258434,146.818357,12',               // Townsville - old GPO at 252-270 Flinders St
'-34.424621,150.900976,12'                // Wollongong - old GPO at 11 Market St
];

// ----------------------------------------------------------------------------

var trailLocations = [

'U',                         // -- Select a Trail --
'-37.832715,145.071588,13',  // Anniversary Trail
'-37.970139,145.011789,13',  // Bay Trail (east)
'-37.872225,144.903244,13',  // Bay Trail (west)
'-37.910415,145.352471,13',  // Belgrave Rail Trail
'-37.868041,145.248813,13',  // Blind Creek Trail
'-37.635253,144.923308,13',  // Broadmeadows Valley Trail
'-37.807538,145.131675,13',  // Bushy Creek Trail
'-37.798311,144.936368,13',  // Capital City Trail
'-37.956692,145.233783,13',  // Dandenong Creek Trail
'-38.001919,145.200545,13',  // Dandenong South Trail
'-37.710844,145.034580,13',  // Darebin Creek Trail - lower
'-37.665273,145.036412,13',  // Darebin Creek Trail - upper
'-37.793211,144.807904,13',  // Derrimut Trail
'-37.679732,145.150621,13',  // Diamond Creek Trail
'-37.981885,145.194236,12',  // East Link Trail
'-37.675207,145.007629,13',  // Edgars Creek Trail
'-37.835729,144.788326,13',  // Federation Trail
'-37.856216,145.073365,13',  // Ferndale Park Trail
'-37.895651,145.269548,13',  // Ferny Creek Trail
'-37.584220,144.940991,13',  // Galada Tamboore Trail (Craigieburn Bypass)
'-37.849402,145.049064,13',  // Gardiners Creek Trail
'-37.808670,145.101521,13',  // Gawler Chain Trail
'-37.754993,145.163655,13',  // Greengully Trail
'-37.694840,145.093110,13',  // Greensborough Bypass Trail
'-38.025707,145.318984,13',  // Hallam Bypass Trail
'-38.052175,145.319813,13',  // Hallam Main Drain Trail
'-37.641263,145.069181,13',  // Hendersons Rd Drain Trail
'-37.796974,145.141340,13',  // Koonung Creek Trail
'-37.856274,144.837549,13',  // Kororoit Creek Trail - lower
'-37.789345,144.826536,13',  // Kororoit Creek Trail - upper
'-37.862868,144.781970,13',  // Laverton Creek Trail
'-37.891138,144.620410,13',  // Lollypop Creek Trail
'-37.774906,144.860942,13',  // Maribyrnong River Trail
'-37.871151,145.085321,13',  // Markham Reserve Trail
'-37.692872,145.168295,13',  // Maroondah Aqueduct Trail
'-37.712621,144.978831,13',  // Merri Creek Trail
'-37.717442,144.905936,13',  // Moonee Ponds Creek Trail
'-37.774055,145.189256,13',  // Mullum Mullum Creek Trail - lower
'-37.785538,145.260890,13',  // Mullum Mullum Creek Trail - upper
'-37.716199,145.113325,13',  // Plenty River Trail
'-37.738102,145.080214,13',  // River Gum Walk Trail
'-37.762532,145.127176,13',  // Ruffey Creek Trail
'-37.833171,144.943112,13',  // Sandridge Trail
'-37.890826,145.103716,13',  // Scotchmans Creek Trail
'-37.874348,144.744424,13',  // Skeleton Creek Trail
'-37.737210,145.000238,13',  // St Georges Rd Trail
'-37.926571,145.122745,13',  // Station Trail
'-37.743348,144.882481,13',  // Steele Creek Trail
'-37.830595,145.019781,15',  // Survey Paddock Trail
'-37.861987,145.154742,13',  // Syndal Heatherdale Pipe Reserve Trail
'-37.806323,145.279868,13',  // Tarralla Creek Trail
'-37.704083,144.795016,13',  // Taylors Creek Trail
'-37.675834,144.593079,13',  // Toolern Creek Trail
'-37.752132,144.962217,13',  // Upfield Rail Trail
'-37.875500,145.122736,13',  // Waverley Rail Trail
'-37.767754,144.744595,13',  // Wellness Trail
'-37.910426,144.652706,13',  // Werribee River Trail
'-37.965537,145.135870,13',  // Westall Rd Trail
'-37.700772,144.734695,13',  // Western Plains Heritage Trail
'-38.316037,145.186908,12',  // Western Port Bay Trail
'-37.693029,144.941914,12',  // Western Ring Rd Trail
'-37.843564,145.155239,13',  // Wurundjeri Walk Trail
'-37.768150,145.071370,13',  // Yarra River Trail
'U',                         // -----
'-38.164267,144.350092,14',  // Barwon River Trail
'-38.143312,144.361901,14',  // Corio Bay Trail
'-38.060372,144.412514,14',  // Hovell Creek Trail
'-38.089037,144.327281,13',  // Ted Wilson Trail
'-38.124340,144.330753,14',  // Tom McKean Trail
'-38.197840,144.324484,14',  // Waurn Ponds Trail
'U',                         // -----
'-38.191007,144.578136,11',  // Bellarine Peninsula Rail Trail
'-38.409781,142.979888,11',  // Coast to Crater Rail Trail
'-37.708968,147.835187,11',  // East Gippsland Rail Trail
'-38.600535,146.047489,11',  // Great Southern Rail Trail
'-36.173218,147.035217,11',  // High Country Rail Trail
'-37.798178,147.925789,11',  // Mississippi Creek Trail
'-36.647017,146.866421,11',  // Murray to the Mountains Rail Trail
'-36.380750,146.681744,11',  // Murray to the Mountains - Beechworth spur
'-37.769556,145.632565,11',  // The Centenary Trail
'-38.308626,145.196751,11'   // Western Port Bay Trail
];

// ----------------------------------------------------------------------------

// a default radar located in the "Great Australian Bight"
var radar = {'lat':-45.0,'lng':135.0,'state':'xyz','name':'Default radar','ranges':{'range512':[],'range256':[],'range128':[],'range64':[]}};

// default to 512 km composites, which are available for all sites
var radarRange     = 'range512';
var lastRadarRange = '';

// ----------------------------------------------------------------------------
// Display an object's properties and functions for debugging purposes
// ----------------------------------------------------------------------------

function examineObject(theObject)
{
   // loop through the object and show all the properties
   var tmp = [];
   for (var propertyName in theObject)
   {
      if (!(theObject[propertyName] instanceof Function))
         tmp.push(propertyName+' = '+theObject[propertyName]);
   }

   alert('Properties:\n\n' + tmp.join('\n'));

   // loop through the object and show all the functions
   tmp = [];
   for (var functionName in theObject)
   {
      if (theObject[functionName] instanceof Function)
         tmp.push(functionName+'()');
   }

   alert('Functions:\n\n' + tmp.join('\n'));
}

// ----------------------------------------------------------------------------
//
// Open the about window - Z-index is set to 1001
//
// OL FAQ: What is the maximum amount of layers I can have in my OpenLayers Map?
//
// The limit is about 75. After that, layers can appear above popups. This has to
// do with the z-index in CSS (determines what is 'above' what). Layers (overlay)
// start at a z-index of 325. Popups start at 750. Controls start at 1000. 
//
// Every layer 'takes up' about 5 indexes, so it will reach it's limit at around
// 75 layers. You cannot have more than 250 popups for the same reason. 
//
// If you need more than 75 layers, consider destroying the ones you don't show
// instead of hiding them and recreate them when needed. 
//
// ----------------------------------------------------------------------------

function aboutMsgOpenBtnClick()
{
   // make the window visible
   document.getElementById("aboutMsg").style.display = "inline";

   document.getElementById("aboutMsgBtn").value   = "Close About";
   document.getElementById("aboutMsgBtn").onclick = aboutMsgCloseBtnClick;

   showAboutMsg = true;
}

// ----------------------------------------------------------------------------
// Close the about window
// ----------------------------------------------------------------------------

function aboutMsgCloseBtnClick()
{
   // make the window invisible
   document.getElementById("aboutMsg").style.display = "none";

   document.getElementById("aboutMsgBtn").value   = "About";
   document.getElementById("aboutMsgBtn").onclick = aboutMsgOpenBtnClick;

   showAboutMsg = false;
}

// ----------------------------------------------------------------------------
// Get the map set up parameters from the cookie. If no cookie, use the defaults supplied here.
// ----------------------------------------------------------------------------

function getCookieParms()
{
   // set the defaults in case there is no cookie
   mapCenterLat = MELB_CENTER_LAT;  // set the default to Melbourne's GPO
   mapCenterLng = MELB_CENTER_LNG;  // set the default to Melbourne's GPO
   mapZoom      = 8;
   mapTypeText  = "p";
   bikeTimeLat  = mapCenterLat;
   bikeTimeLng  = mapCenterLng;
   speedIdx     = 2;                 // 18 kph
   showAboutMsg = true;
   showLayerSw  = true;
/*
   // can the map be centered by estimating the user's location from their IP address?
   if (google.loader.ClientLocation)
   {
      // set the default to client's estimated location
      mapCenterLat = google.loader.ClientLocation.latitude;
      mapCenterLng = google.loader.ClientLocation.longitude;
      bikeTimeLat  = mapCenterLat;
      bikeTimeLng  = mapCenterLng;

      mapZoom = 8;
   }
*/
   // got a cookie? - if not RETURN
   var results = document.cookie.match (cookieName + '=(.*?)(;|$)');
   if (!results) return false;

// ----------------------------------------------------------------------------
// a cookie was found - so continue
// ----------------------------------------------------------------------------

   var cookiePrms = unescape(results[1]).split(',');

   // is the cookie up to date?
   if (cookiePrms[0] != cookieVersion)
      return false;

   // a cookie was found - load the data from it
   mapCenterLat = parseFloat(cookiePrms[1]);
   mapCenterLng = parseFloat(cookiePrms[2]);

   mapZoom = parseInt(cookiePrms[3],10);

   mapTypeText = cookiePrms[4];

   bikeTimeLat = parseFloat(cookiePrms[5]);
   bikeTimeLng = parseFloat(cookiePrms[6]);
   speedIdx = cookiePrms[7];

   var ringsLockedStr = cookiePrms[8];
   var tmp = ringsLockedStr.toLowerCase().indexOf("true");

   // shouldn't need the temp var but it doesn't work without it
   ringsLocked = (tmp === 0);

   var showAboutMsgStr = cookiePrms[9];
   tmp = showAboutMsgStr.toLowerCase().indexOf("true");

   // shouldn't need the temp var but it doesn't work without it
   showAboutMsg = (tmp === 0);

   var showLayerSwStr = cookiePrms[10];
   tmp = showLayerSwStr.toLowerCase().indexOf("true");

   // shouldn't need the temp var but it doesn't work without it
   showLayerSw = (tmp === 0);

   return true;
}

// ----------------------------------------------------------------------------
// Set the map parameters in the cookie
// ----------------------------------------------------------------------------

function setCookieParmeters(years)
{
   // total hack but that's too bad
   showLayerSw = layerCtrl.minimizeDiv.style.display !== "none";

   // the cookie holds the map layout and the home location
   var cookieValue = cookieVersion +','
                   + mapCenterLat +','+ mapCenterLng +','
                   + mapZoom +','
                   + mapTypeText +','
                   + bikeTimeLat +','+ bikeTimeLng +','
                   + speedIdx +','
                   + ringsLocked +','
                   + showAboutMsg +','
                   + showLayerSw;

   // set the expiry time
   var date = new Date();
   date.setTime(date.getTime()+(years*365*24*60*60*1000));

   // set the cookie
   document.cookie = cookieName+'='+escape(cookieValue)
                   + '; expires='+date.toGMTString()
                   + '; path=/';

   // if the year is -1 the cookie is destroyed
   return (years != -1);
}

// ----------------------------------------------------------------------------
// Returns the URL parameters for the permalink
// ----------------------------------------------------------------------------

function getPermaLinkParms()
{
   var parameters = {};

   // round to 6 decimal places
   var lat = Math.round(mapCenterLat*1000000)/1000000;
   var lng = Math.round(mapCenterLng*1000000)/1000000;

   //parms styled to match google maps
   parameters.ll = lat + ',' + lng;
   parameters.z  = mapZoom;
   parameters.t  = mapTypeText;

   return parameters;
} 

// ----------------------------------------------------------------------------
// Extract the parms from the URL - these will override the matching parms in the cookie, if it's present
// ----------------------------------------------------------------------------

function getUrlParms()
{
   var parms = OpenLayers.Util.getParameters();

   // does parm exist?
   if (parms.ll)
   {
      // parms.ll is an array
      // the lat/lng must come as a pair else the url is malformed
      if (parms.ll.length == 2)
      {
         var lat = parseFloat(parms.ll[0]);
         var lng = parseFloat(parms.ll[1]);

         // check that they are valid numbers
         if (!(isNaN(lat) || isNaN(lng)))
         {
            mapCenterLat = lat;
            mapCenterLng = lng;
            //alert('ll '+mapCenterLat+','+mapCenterLng);
         }
      }
   }

   // does parm exist?
   if (parms.z)
   {
      var zoom = parseInt(parms.z,10);

      // if it's valid use it
      if (!isNaN(zoom))
      {
         mapZoom = zoom;
         //alert('zoom'+zoom);
      }
   }

   // does parm exist?
   if (parms.t)
   {
      mapTypeText = parms.t;
   }
}

// ----------------------------------------------------------------------------
// Determine which state this lat/lng belongs to - this is approximate
// Vic, NSW and Canberra are pretty rough approximations
// ----------------------------------------------------------------------------

function identifyState(lat,lng)
{
   var state = 'State not found';

   if ((lat >= -39.2) && (lat <= -35.5) && (lng >= 140.9736) && (lng <= 150))
      state = 'Vic';   // do Vic before NSW and SA for best approximation
   else if ((lat >= -35.925) && (lat <= -35.125) && (lng >= 148.76) && (lng <= 149.4))
      state = 'ACT';   // do ACT before NSW for best approximation
   else if ((lat >= -37.5) && (lat <= -29) && (lng >= 140.9736) && (lng <= 154))
      state = 'NSW';
   else if ((lat >= -38.5) && (lat <= -25.996) && (lng >= 129) && (lng <= 141))
      state = 'SA';    // do SA before Qld for best approximation
   else if ((lat >= -29) && (lat <= -11) && (lng >= 138) && (lng <= 154))
      state = 'Qld';
   else if ((lat >= -36) && (lat <= -13.5) && (lng >= 112.5) && (lng <= 129))
      state = 'WA';
   else if ((lat >= -44) && (lat <= -39.2) && (lng >= 143) && (lng <= 149))
      state = 'Tas';
   else if ((lat >= -25.996) && (lat <= -11) && (lng >= 129) && (lng <= 138))
      state = 'NT';
   else if ((lat >= -48) && (lat <= -32) && (lng >= 165) && (lng <= 179))
      state = 'NZ';

   return state;
}

// ----------------------------------------------------------------------------
// Center and zoom - zoom is optional
// ----------------------------------------------------------------------------

function centerAndZoom(lon,lat,zoom)
{
   // a missing zoom is set to null
   zoom = zoom || null;

   // use setCenter with the appropriate projection and zoom level to center the map
   var proj = new OpenLayers.Projection("EPSG:4326");
   var point = new OpenLayers.LonLat(lon,lat);
   map.setCenter(point.transform(proj, map.getProjectionObject()), zoom);
}

// ----------------------------------------------------------------------------
// Look through the html for image tags, then add a uniqueID to the source URL
// in order that the server will think each request for the same image is a
// different request. Need this for webcams that update the image but not the URL
// Only handles jpg and png in lower case (although that could clearly be changed)
// This whole thing is a bit crude but it appears to work OK
// ----------------------------------------------------------------------------

function deCacheImageURL(html)
{
   // get an array of image tags. This includes the tags themselves
   var matches = html.match(/<img (.*?)\/>/g);

   if (matches === null) return html;

   var uniqueID = OpenLayers.Util.createUniqueID();

   // check all the matches
   var replacement = '';
   for (i=0; i<matches.length; i++)
   {
      // URL already got some parameters?
      if (matches[i].indexOf('?') !== -1)
      {
         // URL already has existing parameters
         // string stays the same if no match is found
         // jpg and png should be mutually exclusive
         replacement =  matches[i].replace('.jpg', '.jpg&'+uniqueID);
         replacement = replacement.replace('.png', '.png&'+uniqueID);
      }
      else // URL has no existing parameters
      {
         replacement =  matches[i].replace('.jpg', '.jpg?'+uniqueID);
         replacement = replacement.replace('.png', '.png?'+uniqueID);
      }

      // make the replacements
      html = html.replace(matches[i], replacement);
   }

   return html;
}

// ----------------------------------------------------------------------------
// The popup's close button has been clicked
// ----------------------------------------------------------------------------

function onPopupClose(evt)
{
   if (selectedFeature === null) return;

   selectControl.unselect(selectedFeature);
}

// ----------------------------------------------------------------------------
// Before popping up the info bubble on the feature
// ----------------------------------------------------------------------------

function onBeforeFeatureSelect(feature)
{
   if (feature === null) return;

   // Any BBOX strategies need to be disabled as opening
   // the popup may cause the map to pan causing BBOX to 
   // return new features and destroying the selected popup
   var layerTot = map.layers.length;
   for (var i=0; i<layerTot; i++)
   {
      if ((map.layers[i].strategies !== undefined) && (map.layers[i].strategies !== null))
      {
         var len = map.layers[i].strategies.length;
         for (var j=0; j<len; j++)
         {
            // deactivate only the BBOX strategies
            if (map.layers[i].strategies[j].CLASS_NAME == 'OpenLayers.Strategy.BBOX')
               map.layers[i].strategies[j].deactivate();
         }
      }
      //else if (map.layers[i].strategies === undefined)
      //   alert ('Undefined: '+map.layers[i].name);
      //else
      //   alert ('Null: '+map.layers[i].name);
   }
}    

// ----------------------------------------------------------------------------
// Popup an info bubble
// ----------------------------------------------------------------------------

function onFeatureSelect(feature)
{
   // precautionary only
   selectedFeature = null;

   if (feature === null) return;

   //examineObject(permaLink);

   // convert features to KML when a feature is clicked
   //vectorLayer2KML(propL);

   var heading = '';

   // GeoRSS
   if (feature.attributes.title !== undefined)
      heading = feature.attributes.title;

   // KML
   if (feature.attributes.name !== undefined)
      heading = feature.attributes.name;

   // must have a heading
   if (heading === '')
      return;

   // keep track of the currently selected feature
   selectedFeature = feature;

   var html = '<div style="font-size:0.9em"><b>' + heading + "</b>";

   // got a description? If so, add it in
   if (feature.attributes.description !== undefined)
      html += "<br/>" + feature.attributes.description;

   html += "</div>";

   // add on a unique ID to image URLs to decache them
   html = deCacheImageURL(html);

   var popup = new OpenLayers.Popup.FramedCloud("the clicked icon", 
      feature.geometry.getBounds().getCenterLonLat(),
      null, // rather than suppling a size the popup will autosize
      html,
      null, true, onPopupClose
      );

   feature.popup = popup;
   map.addPopup(popup);

   // is this the time by bike popup?
   if (feature.attributes.isTimeByBikeIcon !== undefined)
   {
      // set the speed in the pulldown list
      document.getElementById("speedID").selectedIndex = speedIdx;

     if (ringsLocked)
         document.getElementById("lockButton").value = 'Unlock rings';
      else
         document.getElementById("lockButton").value = 'Lock rings';
   }
}

// ----------------------------------------------------------------------------
// The feature was unselected by clicking on the map or on the popup close button
// ----------------------------------------------------------------------------

function onFeatureUnselect(feature)
{
   // was a popup used?
   if (feature.popup !== null)
   {
      map.removePopup(feature.popup);
      feature.popup.destroy();
      feature.popup = null;
   }

   // Any BBOX strategies need to be disabled as opening
   // the popup may cause the map to pan causing BBOX to 
   // return new features and destroying the selected popup
   // Here we turn it back on
   var layerTot = map.layers.length;
   for (var i=0; i<layerTot; i++)
   {
      if ((map.layers[i].strategies !== undefined) && (map.layers[i].strategies !== null))
      {
         var len = map.layers[i].strategies.length;
         for (var j=0; j<len; j++)
         {
            // deactivate only the BBOX strategies
            if (map.layers[i].strategies[j].CLASS_NAME == 'OpenLayers.Strategy.BBOX')
               map.layers[i].strategies[j].activate();
         }
      }
   }

   // the feature is no longer selected
   selectedFeature = null;
}

// ----------------------------------------------------------------------------
// Build a tile URL query
// ----------------------------------------------------------------------------

function makeTileURL(bounds, pathType, layerContext)
{
   var res = this.map.getResolution();
   var x   = Math.round ((bounds.left   - this.map.maxExtent.left) / (res * this.map.tileSize.w));
   var y   = Math.round ((this.map.maxExtent.top - bounds.top)     / (res * this.map.tileSize.h));
   var z   = this.map.getZoom();

   var path = '';

   switch (pathType)
   {
   case 0:  // OSM
      path = z + "/" + x + "/" + y + "." + layerContext.type;  // type of image eg png
      break;
   case 1:  // NearMap
      path = 'x=' + x + '&y=' + y + '&z=' + z + '&hl=en&nml=vert&s=ga';
      break;
   case 2:  // Google KML tiler
      path = 'x=' + x + '&y=' + y + '&z=' + z + '&lyrs=kml:cj9S8tMLDs6AMLPIwVSb5QrpE-RQPU2WSL6RLlE8sD3WDAA|kv:3|kp:';
      break;
   case 3:  // Google MMAPs tiler
      // Alfred Hitchcock's the birds by DSE
      path = 'x=' + x + '&y=' + y + '&z=' + z + '&lyrs=msid:102039560396402603008.00048c55e42c5df4be696|t:1283809332&gl=au&hl=en';  // &w=256&h=256
      break;
   default:
      break;
   }

   var url = layerContext.url;
   if (url instanceof Array)
   {
      url = layerContext.selectUrl(path, url);
   }
   return url + path;
}

// ----------------------------------------------------------------------------
// Make OSM tile URL query
// eg  http://b.andy.sandbox.cloudmade.com/tiles/cycle/13/7393/5028.png
// ----------------------------------------------------------------------------

function makeTileURLa(bounds)
{
   return makeTileURL(bounds, 0, this);
}

// ----------------------------------------------------------------------------
// Make NearMap tile URL query
// eg  http://www.nearmap.com/maps/x=7394&y=5026&z=13&hl=en&nml=vert&s=ga
// ----------------------------------------------------------------------------

function makeTileURLb(bounds)
{
   return makeTileURL(bounds, 1, this);
}

// ----------------------------------------------------------------------------
// Make Google KML tiler URL query
// eg   http://mt2.google.com/mapslt?lyrs=kml:cj9S8tMLDs6AMLPIwVSb5QrpE-RQPU2WSL6RLlE8sD3WDAA|kv:3|kp:&x=459&y=315&z=9&w=256&h=256
// To force KML to be tiled:  add an invisible lineString with 500 plus points - may need to play around with the size
// ----------------------------------------------------------------------------

function makeTileURLc(bounds)
{
   return makeTileURL(bounds, 2, this);
}

// ----------------------------------------------------------------------------
// Make Google Icon tiler URL query
// http://mt1.google.com/mapslt?lyrs=msid:102039560396402603008.00048c55e42c5df4be696|t:1283809332&x=925&y=628&z=10&w=256&h=256&gl=au&hl=en
// ----------------------------------------------------------------------------

function makeTileURLd(bounds)
{
   return makeTileURL(bounds, 3, this);
}

// ----------------------------------------------------------------------------
// Convert the features on a layer to KML and send to a html text area
// ----------------------------------------------------------------------------

function vectorLayer2KML(layerPtr)
{
   if (layerPtr === null) return;

   var format = new OpenLayers.Format.KML({
      extractStyles:      true, 
      extractAttributes:  true,
      internalProjection: map.getProjectionObject(),
      externalProjection: new OpenLayers.Projection("EPSG:4326")  // lat/lng
      });

   var features = layerPtr.features;
   
   //map.zoomToExtent(features[0].geometry.getBounds());

   document.getElementById("features").value = format.write(features);
}

// ----------------------------------------------------------------------------
// Find the bounds of a BOM image
// image size is 512,512 pixels
// ----------------------------------------------------------------------------

function getRadarImageBounds(radar, radarRange)
{
   var kmFromRadar = 64;

   switch (radarRange)
   {
   case 'range512':
      kmFromRadar = 512;
      break;
   case 'range256':
      kmFromRadar = 256;
      break;
   case 'range128':
      kmFromRadar = 128;
      break;
   case 'range64':
      kmFromRadar = 64;
      break;
   default:
      break;
   }

   // deg2rad = 2*pi/360.0   rad2Deg = 1/deg2rad
   var deg2rad = 0.017453293;
   var rad2Deg = 57.29577951;

   var radarLat = radar.lat * deg2rad;
   var radarLng = radar.lng * deg2rad;

   // Radius = WGS84 ellipsoid a = 6,378,137 m at the equator
   // one degree at equator in km = (2*Pi*R)/(360*100.0)
   // BOM use 111.11 for this figure
   //var oneDegInKm = 111.3194908;
   var oneDegInKm = 111.11;
   var latDegFromRadar = (kmFromRadar/oneDegInKm)*deg2rad;

   // find the bounds
   var botLat   = radarLat - latDegFromRadar;
   var topLat   = radarLat + latDegFromRadar;
   //var leftLng  = radarLng - (latDegFromRadar/Math.cos(botLat));
   //var rightLng = radarLng + (latDegFromRadar/Math.cos(topLat));
   var leftLng  = radarLng - (latDegFromRadar/Math.cos(radarLat));
   var rightLng = radarLng + (latDegFromRadar/Math.cos(radarLat));
 
   leftLng  = leftLng  * rad2Deg;
   botLat   = botLat   * rad2Deg;
   rightLng = rightLng * rad2Deg;
   topLat   = topLat   * rad2Deg;

   // left bottom corner and right top corner
   var bounds = new OpenLayers.Bounds(leftLng, botLat, rightLng, topLat);
   bounds.transform(map.displayProjection, map.getProjectionObject());  // EPSG:4326 --> EPSG:900913

   return bounds;
}

// ----------------------------------------------------------------------------
// Refresh the image filenames every 6 minutes
// ----------------------------------------------------------------------------

function updateRadarInfoEverySixMinutes()
{
   var sixMinutes = 360000; // 1000*60*6;

   // get the time
   var d = new Date();
   var time = d.getTime();

   // static variable initialized?
   if (typeof this.previousTime == 'undefined')
      // first time round we will force an update
      this.previousTime = 0;

   // has 6 minutes passed?
   if (time < (this.previousTime+sixMinutes)) return;

   // update the time and the radar info
   this.previousTime = time;

   refreshRadar();

   return;
}

// ----------------------------------------------------------------------------
// This will animate the URL
//
// List of radars:     http://www.bom.gov.au/weather/radar/index.shtml
// and their details:  http://www.bom.gov.au/weather/radar/about/radar_site_info.shtml
//
// This is the source of the files - coming as json from BigYak: 
//    ftp://ftp2.bom.gov.au/anon/gen/radar/
// ----------------------------------------------------------------------------

function nextURL(radar, radarRange)
{
   // note that this url is case sensitive
   var urlBase = 'http://www.bom.gov.au/radar/';

   var imageFeedUrl = '';

   // has radar been initialized?
   var numberOfImages = radar.ranges[radarRange].length;
   if (numberOfImages === 0)
      return imageFeedUrl;

   // update the image array for the radar as time goes on
   updateRadarInfoEverySixMinutes();

   // static variable initialized?
   if (typeof this.count == 'undefined')
      this.count = 0;

   // get the next URL to display
   imageFeedUrl = urlBase + radar.ranges[radarRange][this.count];

   this.count++;

   // increment through all the images

   if (this.count >= numberOfImages)
      this.count = 0;

   return imageFeedUrl;
}

// ----------------------------------------------------------------------------
// The map was moved or zoomed - update the radar layer
// This is only called if the range has changed or a new radar is within range
// It sets up the image location and extent but has nothing to do with the actual images presented
// ----------------------------------------------------------------------------

function updateRadarImageSizeAndPosition()
{
   // change the bounds of the radar image
   var imageBounds = getRadarImageBounds(radar, radarRange);

   // this appears to fix a bug where the bounds passed into the function below are not assigned to extent
   radarL.extent = imageBounds;
   radarL.moveTo(imageBounds, true, false);

   //var imageFeedUrl = nextURL(radar, radarRange);
   //var imageSize = new OpenLayers.Size(512,512);
   //radarL.initialize("BOM Weather Radar", imageFeedUrl, imageBounds, imageSize, {});
} 

// ----------------------------------------------------------------------------
// If the map was moved - see if the closest radar has changed - if so update the
// image position and size on the radar layer. Also get the latest images.
// ----------------------------------------------------------------------------

function refreshRadar()
{
   // if the layer is not showing then no action is required
   if (!radarL.getVisibility()) return;

   // reproject: EPSG:900913 --> EPSG:4326
   var center = map.getCenter().transform(map.getProjectionObject(), map.displayProjection);

   // stick on the end of the URL so it's not cached
   // the url is the same each time the 6 minute timer fires
   var uniqueID = OpenLayers.Util.createUniqueID();

   var jsonFeedUrl = 'http://www.bigyak.net.au/trails/cd/getradarimageurls.php?fnc=g0&ll='+center.lat+','+center.lon+'&'+uniqueID;

   var protocol = new OpenLayers.Protocol.HTTP
   ({
      url: jsonFeedUrl,
      format: new OpenLayers.Format.JSON
        ({
        }),
      callback : function(response)
      {
         if(!response.success()) return;

         //examineObject(response.features);

         // If the map was moved - is the closest radar now a different radar?
         var newRadar = ((radar.lat !== response.features.lat) || (radar.lng !== response.features.lng));
         
         // update the radar filenames - whether this be a new or old radar
         radar = response.features;

         // this will (re)position the image at the radar location - the image size will depend on the range
         if (newRadar)
            updateRadarImageSizeAndPosition();
      }
   });

   protocol.read();
}

// ----------------------------------------------------------------------------
// Weather radar animation updater - updates once per second
// ----------------------------------------------------------------------------

function updateRadarAnimation()
{
   //alert(radarL.tile.imgDiv.style.width);
   //alert(radarL.tile.imgDiv.style.height);

   // The browser may or may not cache the images - if not, the animation will continually 
   // download them. For example: Firefox version from portableapps, sets this by default:
   //   browser.cache.disk.capacity default integer 50000 - refer web page: "about:config"
   // so watch out for that download limit on your plan!

   // get the new image for the animation
   var imageFeedUrl = nextURL(radar, radarRange);

   //radarL.setUrl(imageFeedUrl);          // correct method but flickers
   radarL.tile.imgDiv.src = imageFeedUrl;  // this is a hack but stops flickering
}

// ----------------------------------------------------------------------------
// Given a center point, an angle and a radius, find the point on the circle
// This function expects a lat,lng centre and returns a lat,lng point
// ----------------------------------------------------------------------------

function findRadialTip(center, angleDeg, radiuskm)
{
   var latDeg = center.lat;
   var lngDeg = center.lon;

   var d2r   = Math.PI/180.0;  // degrees to radians
   var r2d   = 180.0/Math.PI;  // radians to degrees
   var twoPI = 2*Math.PI;

   var latCenter = latDeg   * d2r;
   var lngCenter = lngDeg   * d2r;
   var angle     = angleDeg * d2r;
   var dRad      = radiuskm/6378.137;  // WGS84 ellipsoid a = 6,378,137 m at the equator

   var latRad  = 0.0;
   var dlngRad = 0.0;
   var lngRad  = 0.0;

   var sinLatCenter = Math.sin(latCenter);
   var cosLatCenter = Math.cos(latCenter);
   var sindRad      = Math.sin(dRad);
   var cosdRad      = Math.cos(dRad);

   latRad  = Math.asin (sinLatCenter * cosdRad + cosLatCenter * sindRad * Math.cos(angle));

   // javascript order --> atan2(y,x)
   dlngRad = Math.atan2(Math.sin(angle) * sindRad * cosLatCenter,  cosdRad - sinLatCenter * Math.sin(latRad));
   lngRad  = ((lngCenter + dlngRad + Math.PI) % twoPI) - Math.PI;

   var point = new OpenLayers.Geometry.Point(lngRad * r2d, latRad * r2d);

   return point;
}

// ----------------------------------------------------------------------------
// Draw circle of radius specified (in km) at the lat lng specified
// This function expects the center to be in lat,lng units
//
//   Could have used this:  OpenLayers.Geometry.Polygon.createRegularPolygon
//   It appears to have projection problems - couldn't get the radius right. Out by cosine the latitude?
//   It appears to use a straight-line (planar) distance between the points on a projected plane instead
//   of the shortest distance between the two points on a spheroid.
//
//   Also it acts as a filled polygon, so you can't pan the map when the mouse is inside the circle.
//   Get the same problem when using:  OpenLayers.Geometry.LinearRing  as opposed to a closed LineString
//
// ----------------------------------------------------------------------------

function drawCircle(layerPtr, center, radiuskm)
{
   var totalSectors = 36;
   var sectorAngle  = 360.0/totalSectors;

   // loop through the array and write path linestrings
   var i        = 0;
   var angleDeg = 0.0;
   var point    = null;
   var points   = [];

   for (i=0; i<=totalSectors; i++)
   {
      // next point on circle
      angleDeg = i * sectorAngle;

      // get circle point - this function expects a lat,lng centre and returns a lat,lng point
      point = findRadialTip(center, angleDeg, radiuskm);

      // lat,lng to SM
      point.transform(map.displayProjection, map.getProjectionObject());  // EPSG:4326 --> EPSG:900913

      points.push(point);
   }

   var theStyle = {
      //strokeColor: '#C000C0',
      strokeColor: 'red',
      strokeWidth: 2,
      strokeOpacity: 1.0,
      strokeDashstyle: 'solid'
      };

   var theCircle = new OpenLayers.Geometry.LineString(points);
   var ring = new OpenLayers.Feature.Vector(theCircle, null, theStyle);
   layerPtr.addFeatures([ring]);
}

// ----------------------------------------------------------------------------
// A different speed was selected or the map was moved
// ----------------------------------------------------------------------------

function updateRings(layerPtr, timeByBikeCenter, speed)
{
   var efficiency    = 0.80;  // guesstimate
   var outerRingTime = 30;    // in 5 minute intervals

   // this needs to be done backwards to make it work
   var total = layerPtr.features.length - 1;
   for (var j=total; j>=0; j--)
   {  //alert (layerPtr.features.length);
      if (layerPtr.features[j].attributes.isTimeByBikeIcon === undefined)
         layerPtr.destroyFeatures(layerPtr.features[j]);
   }

   var i = 0;
   for (i=5; i<=outerRingTime; i+=5)
      drawCircle (layerPtr, timeByBikeCenter, i/60.0 * speed * efficiency);
}

// ----------------------------------------------------------------------------
// Add in the home marker - the map center needs to be set before calling this
// ----------------------------------------------------------------------------

function makeHomeMarker(layerPtr, timeByBikeCenter)
{
   var title = 'How long by bike?';

   var description = '<p>How long will it take to get there by bicycle? The rings increment by 5 minute intervals - the outer ring represents half an hour.<br/><br/>The times are based on: the average bicycle speed selected, the line of site distance and an efficiency factor of 80%, compensating for the fact that all routes are indirect to some degree.<br/><br/>Select speed to suit. The ring center can be locked to a location or track the map center.<br/><br/><select id="speedID" name="bicyclesAverageSpeed" onchange="speedChanged()"><option value="10">10 kph</option><option value="15">15 kph</option><option value="18">18 kph</option><option value="20">20 kph</option><option value="22">22 kph</option><option value="24">24 kph</option><option value="26">26 kph</option><option value="28">28 kph</option><option value="30">30 kph</option><option value="32">32 kph</option><option value="34">34 kph</option></select>&nbsp;<input id="lockButton" type="button" value="" onclick="lockButtonClicked()"/><br/><br/>Fight heart disease, obesity, traffic congestion, peak oil, climate change and save money - ride a bike.</p>';

   // arbitrary properties that describe the feature
   var attributes = {isTimeByBikeIcon: timeByBikeCenter, title: title, description: description};

   var theStyle = {
        externalGraphic: "http://maps.google.com/mapfiles/kml/shapes/cycling.png",
        graphicWidth: 32,
        graphicHeight: 32,
        graphicYOffset: -16
      };

   var homeMarker = new OpenLayers.Geometry.Point(timeByBikeCenter.lon, timeByBikeCenter.lat);

   homeMarker.transform(map.displayProjection, map.getProjectionObject());  // EPSG:4326 --> EPSG:900913

   var theCenter = new OpenLayers.Feature.Vector(homeMarker, attributes, theStyle);

   layerPtr.addFeatures([theCenter]);
}

// ----------------------------------------------------------------------------
// A new bike speed was selected
// ----------------------------------------------------------------------------

function speedChanged()
{
   speedIdx = document.getElementById("speedID").selectedIndex;
   speed    = document.getElementById("speedID").value;

   // (re)draw rings at the center point
   updateRings(timeByBikeL, selectedFeature.attributes.isTimeByBikeIcon, speed);
}

// ----------------------------------------------------------------------------
// The Lock center/Track center button was toggled
// ----------------------------------------------------------------------------

function lockButtonClicked()
{
   if (!ringsLocked)  // lock reference point
   {
      ringsLocked = true;
      document.getElementById("lockButton").value = 'Unlock rings';
   }
   else  // change reference point
   {
      ringsLocked = false;
      document.getElementById("lockButton").value = 'Lock rings';
   }
}

// ----------------------------------------------------------------------------
// Redraw all the rings at the specified center
// ----------------------------------------------------------------------------

function updateTimeByBike()
{
   // layer transitioned to not showing? then no action required
   if (!timeByBikeL.getVisibility()) return;

   // don't update while the user is looking at a selected feature
   if (selectedFeature !== null) return;

   // track center?
   if (!ringsLocked)
   {
      // reproject: EPSG:900913 --> EPSG:4326
      var center = map.getCenter().transform(map.getProjectionObject(), map.displayProjection);

      bikeTimeLat = center.lat;
      bikeTimeLng = center.lon;
   }

   var timeByBikeCenter = new OpenLayers.LonLat(bikeTimeLng, bikeTimeLat);

   // remove old features - in this case just the center icon
   timeByBikeL.destroyFeatures();

   // (re)draw rings at center point
   updateRings(timeByBikeL, timeByBikeCenter, speed);

   // draw this last so it is on top of the rings so it can be selected
   makeHomeMarker(timeByBikeL, timeByBikeCenter);
}

// ----------------------------------------------------------------------------
// NearMap BASE layer
//
// http://www.nearmap.com/maps/hl=en&x=53843&y=38889&z=16&nml=Vert&s=Ga
//
// The x, y, and z are the image location and zoom level, with the same values used by
// Google or Bing (ie, standard slippymap tile co-ordinates).
//
// The nml parameter is the NearMap layer, and can be one of: 
//    *Vert for PhotoMap 'Vertical' images; N, S, E or W for MultiView images looking
//       North, South, East or WestMap for NearMap StreetMap images
//    *Dem for terrain images
//    *The hl is the Host Language; only English is currently supported
//    *The s parameter is ignored; it can be used as with Google Maps, to work around
//       issues in FireFox's caching - Google randomizes its length from G to Galileo
// ----------------------------------------------------------------------------

function nearMapLayer()
{
   var layerPtr = new OpenLayers.Layer.TMS("NearMap",
//      "http://www.nearmap.com/maps/",
    [
      "http://web0.nearmap.com/maps/",
      "http://web1.nearmap.com/maps/",
      "http://web2.nearmap.com/maps/",
      "http://web3.nearmap.com/maps/"
    ],
    { type: 'png',
      getURL: makeTileURLb,  // replace the URL maker function with one of our own
      numZoomLevels: 22,
      buffer: 1,
      attribution: 'Photography by NearMap'
   });

   return layerPtr;
}

// ----------------------------------------------------------------------------
// OSM cycle BASE layer - see also http://www.openstreetmap.org/OpenStreetMap.js
// ----------------------------------------------------------------------------

function osmcLayer()
{
   //var attrib = 'OpenCycleMap.org - the <a href="http://www.openstreetmap.org">OpenStreetMap</a> Cycle Map<br/>'
   //                + 'Created by <a href="http://www.gravitystorm.co.uk">Andy Allan</a> and Dave Stubbs<br/>'
   //                + 'Sponsored by <a href="http://www.cloudmade.com">CloudMade</a>';

   var attrib = '<a href="http://www.openstreetmap.org">OpenStreetMap</a> Cycle Map';

   var layerPtr = new OpenLayers.Layer.TMS("OSM Cycle Routes", [
      "http://a.andy.sandbox.cloudmade.com/tiles/cycle/",
      "http://b.andy.sandbox.cloudmade.com/tiles/cycle/",
      "http://c.andy.sandbox.cloudmade.com/tiles/cycle/"],
    { type: 'png',
      getURL: makeTileURLa,  // replace the URL maker function with one of our own
      buffer: 1,
      attribution: attrib
   });

   return layerPtr;
}

// ----------------------------------------------------------------------------
// News around Melbourne - using GeoRSS
// ----------------------------------------------------------------------------

function melNewsLayerrss()
{
   var GeoRSSFeedUrl = "../trails/plc/vic/kml/melNews.xml";

// ----------------------------------------------------------------------------

   var style = new OpenLayers.Style({
      //externalGraphic: "http://maps.google.com/mapfiles/kml/shapes/info.png",
      // http://openlayers.org/api/img/marker.png
      externalGraphic: OpenLayers.Util.getImagesLocation() + "marker.png",
      graphicHeight: 25,
      graphicWidth: 21,
      graphicXOffset: -10.5,
      graphicYOffset: -12.5
   });

   var theStyleMap = new OpenLayers.StyleMap({'default':style});

 // ----------------------------------------------------------------------------
/*
           // create a styleMap with a custom default symbolizer
            var theStyleMap = new OpenLayers.StyleMap({
                fillOpacity: 1,
                pointRadius: 10
            });

            // create a lookup table with different symbolizers for 0, 1, 2 and 3
            var lookup = {
                0: {externalGraphic: "http://maps.google.com/mapfiles/kml/shapes/info.png"},
                1: {externalGraphic: "http://maps.google.com/mapfiles/kml/shapes/bars.png"},
                2: {externalGraphic: "http://maps.google.com/mapfiles/kml/shapes/caution.png"},
                3: {externalGraphic: "http://maps.google.com/mapfiles/kml/shapes/info_circle.png"}
            }
            
            // add rules from the above lookup table, with the keys mapped to
            // the "type" property of the features, for the "default" intent
            theStyleMap.addUniqueValueRules("default", "type", lookup);
*/
// ----------------------------------------------------------------------------
/*
   var layerPtr = new OpenLayers.Layer.GML("News around Melbourne", GeoRSSFeedUrl, {
      minScale: km_5_to_2,
      format: new OpenLayers.Format.GeoRSS ({
         internalProjection: new OpenLayers.Projection("EPSG:900913"), 
         externalProjection: new OpenLayers.Projection("EPSG:4326")
      }),
      styleMap: theStyleMap
   });
*/
   var layerPtr = new OpenLayers.Layer.Vector("News around Melbourne", {
      projection: new OpenLayers.Projection("EPSG:4326"),
      minScale: km_5_to_2,
      strategies: [new OpenLayers.Strategy.Fixed()],
      protocol: new OpenLayers.Protocol.HTTP({
         url: GeoRSSFeedUrl,
         format: new OpenLayers.Format.GeoRSS({})
       }),
      styleMap: theStyleMap,
      attribution: 'Big Yak - News'
   });

   return layerPtr;
}

// ----------------------------------------------------------------------------
// News around Melbourne
// ----------------------------------------------------------------------------

function melNewsLayer()
{
   var kmlFeedUrl = "../trails/plc/vic/kml/melNews.kml";

// ----------------------------------------------------------------------------

   var layerPtr = new OpenLayers.Layer.Vector("Developments in Melbourne", {
      projection: new OpenLayers.Projection("EPSG:4326"),
      minScale: km_10_to_5,
      strategies: [new OpenLayers.Strategy.Fixed()],
      protocol: new OpenLayers.Protocol.HTTP({
         url: kmlFeedUrl,
         format: new OpenLayers.Format.KML({
         maxDepth: 0,
         extractStyles: true,
         extractAttributes: true
         })
       }),
      attribution: 'Big Yak - Developments in Melbourne'
   });

   return layerPtr;
}

// ----------------------------------------------------------------------------
// Melbourne's bike share stations
// ----------------------------------------------------------------------------

function melBikeShareLayer()
{
   var kmlFeedUrl = "http://www.bigyak.net.au/trails/cd/melbikeshare.php?fnc=k0";

// ----------------------------------------------------------------------------

   var layerPtr = new OpenLayers.Layer.Vector("Melbourne Bike Share", {
      projection: new OpenLayers.Projection("EPSG:4326"),
      minScale: km_10_to_5,
      //strategies: [new OpenLayers.Strategy.Fixed()],
      strategies: [new OpenLayers.Strategy.BBOX({resFactor: 1, ratio: 1})],
      protocol: new OpenLayers.Protocol.HTTP({
         url: kmlFeedUrl,
         format: new OpenLayers.Format.KML({
         maxDepth: 0,
         extractStyles: true,
         extractAttributes: true
         })
       }),
      attribution: "Melbourne Bike Share"
   });

   return layerPtr;
}

// ----------------------------------------------------------------------------
// Sydney counters
// ----------------------------------------------------------------------------

function sydcLayer()
{
   var kmlFeedUrl = "../trails/cd/counters.php?fnc=k0&st=nsw";

// ----------------------------------------------------------------------------

   var layerPtr = new OpenLayers.Layer.Vector("Sydney counters", {
      projection: new OpenLayers.Projection("EPSG:4326"),
      minScale: km_10_to_5,
      strategies: [new OpenLayers.Strategy.Fixed()],
      protocol: new OpenLayers.Protocol.HTTP({
         url: kmlFeedUrl,
         format: new OpenLayers.Format.KML({
         maxDepth: 0,
         extractStyles: true,
         extractAttributes: true
         })
       }),
      attribution: "Premier's Council for Active Living, NSW"
   });

   return layerPtr;
}

// ----------------------------------------------------------------------------
// Sydney RTA CCTV
// ----------------------------------------------------------------------------

function sydCCTVLayer()
{
   var kmlFeedUrl = "../trails/cd/cctv.php?fnc=k0&st=nsw";

// ----------------------------------------------------------------------------

   var layerPtr = new OpenLayers.Layer.Vector("Sydney RTA CCTV", {
      projection: new OpenLayers.Projection("EPSG:4326"),
      minScale: km_10_to_5,
      strategies: [new OpenLayers.Strategy.Fixed()],
      protocol: new OpenLayers.Protocol.HTTP({
         url: kmlFeedUrl,
         format: new OpenLayers.Format.KML({
         maxDepth: 0,
         extractStyles: true,
         extractAttributes: true
         })
       }),
      attribution: "Road Traffic Authority, NSW"
   });

   return layerPtr;
}

// ----------------------------------------------------------------------------
// Melbourne and Geelong's shared paths layer
// ----------------------------------------------------------------------------

function mgspLayer()
{
   var kmlFeedUrl = "../vicbiketrails/kml/vicbiketrailsroutesgmap.kml";

// ----------------------------------------------------------------------------

   var rules = [new OpenLayers.Rule({
      symbolizer: {strokeColor:"lime",strokeWidth: 3,strokeOpacity: 1.0},
      elseFilter: true
   })];
         
   // Melbourne and Geelong's shared paths style
   var theStyleMap = new OpenLayers.StyleMap();            

   theStyleMap.styles["default"].addRules(rules);

// ----------------------------------------------------------------------------

   var layerPtr = new OpenLayers.Layer.Vector("Melb & Geelong's shared paths", {
      projection: new OpenLayers.Projection("EPSG:4326"),
      minScale: km_20_to_10,
      strategies: [new OpenLayers.Strategy.Fixed()],
      protocol: new OpenLayers.Protocol.HTTP({
         url: kmlFeedUrl,
         format: new OpenLayers.Format.KML({
         maxDepth: 0,
         extractStyles: false,
         extractAttributes: false
         })
       }),
      styleMap: theStyleMap,
      attribution: 'Big Yak - Melb. and Gee. paths'
   });

   return layerPtr;
}

// ----------------------------------------------------------------------------
// Navigation around Melbourne
// ----------------------------------------------------------------------------

function mgsnLayer()
{
   var kmlFeedUrl = "../vicbiketrails/vicbiketrails.php";

// ----------------------------------------------------------------------------

   var layerPtr = new OpenLayers.Layer.Vector("Navigation around Melbourne", {
      projection: new OpenLayers.Projection("EPSG:4326"),
      //minScale: km_10_to_5,
      strategies: [new OpenLayers.Strategy.BBOX({resFactor: 1, ratio: 1})],
      protocol: new OpenLayers.Protocol.HTTP({
         url: kmlFeedUrl,
         format: new OpenLayers.Format.KML({
         maxDepth: 0,
         extractStyles: true,
         extractAttributes: true
         })
       }),
      attribution: 'Big Yak - Melb. and Gee. nav'
   });

   // could use this in the constructor instead:  eventListeners: {'loadstart': onPopupClose}
   layerPtr.events.register('loadstart', null, onPopupClose);

   return layerPtr;
}

// ----------------------------------------------------------------------------
// Adelaide campaigns layer
// ----------------------------------------------------------------------------

function adlCampaignsLayer()
{
   var kmlFeedUrl = "../trails/plc/sa/kml/adelaidecampaigns.kml";

// ----------------------------------------------------------------------------

   var layerPtr = new OpenLayers.Layer.Vector("Adelaide campaigns", {
      projection: new OpenLayers.Projection("EPSG:4326"),
      minScale: km_5_to_2,
      strategies: [new OpenLayers.Strategy.Fixed()],
      protocol: new OpenLayers.Protocol.HTTP({
         url: kmlFeedUrl,
         format: new OpenLayers.Format.KML({
         maxDepth: 1,
         extractStyles: true,
         extractAttributes: true
         })
       }),
      attribution: 'Adelaide Cyclists - Adelaide campaigns'
   });

   return layerPtr;
}

// ----------------------------------------------------------------------------
// Sydney campaigns layer
// ----------------------------------------------------------------------------

function sydCampaignsLayer()
{
   var kmlFeedUrl = "../trails/plc/nsw/kml/sydneycampaigns.kml";

// ----------------------------------------------------------------------------

   var layerPtr = new OpenLayers.Layer.Vector("Sydney campaigns", {
      projection: new OpenLayers.Projection("EPSG:4326"),
      minScale: km_5_to_2,
      strategies: [new OpenLayers.Strategy.Fixed()],
      protocol: new OpenLayers.Protocol.HTTP({
         url: kmlFeedUrl,
         format: new OpenLayers.Format.KML({
         maxDepth: 1,
         extractStyles: true,
         extractAttributes: true
         })
       }),
      attribution: 'Big Yak - Sydney campaigns'
   });

   return layerPtr;
}

// ----------------------------------------------------------------------------
// Open Street Map paths layer
// ----------------------------------------------------------------------------

function osmpLayer()
{
   var featureNSURL = "http://www.bikey.com.au/";
   var serviceURL   = featureNSURL + "cgi-bin/tinyows";
   var schemaURL    = serviceURL + "?service=WFS&version=1.0.0&request=DescribeFeatureType&TypeName=s_l_bp_osmBL";

   //var featureNSURL = "http://www.bikey.com.au/";
   //var serviceURL   = "http://www.bigyak.net.au/bike/redirector.php";
   //var schemaURL    = "http://www.bikey.com.au/cgi-bin/tinyows?service=WFS&version=1.0.0&request=DescribeFeatureType&TypeName=s_l_bp_osmBL";

// ----------------------------------------------------------------------------

   var rules = [new OpenLayers.Rule({
      symbolizer: {strokeColor:"purple",strokeWidth: 3,strokeOpacity: 1.0},
      elseFilter: true
   })];
         
   // Open Street Map style
   var theStyleMap = new OpenLayers.StyleMap();            

   theStyleMap.styles["default"].addRules(rules);

// ----------------------------------------------------------------------------

   var layerPtr = new OpenLayers.Layer.Vector("OSM Trails", {
      projection: new OpenLayers.Projection("EPSG:4326"),
      minScale: km_10_to_5,
      strategies: [new OpenLayers.Strategy.BBOX({ratio:1.5})],
      protocol: new OpenLayers.Protocol.WFS({
         version:       "1.0.0",
         url:           serviceURL,
         featureType:   "s_l_bp_osmBL",
         srsName:       "EPSG:4326",
         featureNS:     featureNSURL,
         geometryName:  "the_geom",
         schema:        schemaURL
      }),
      styleMap: theStyleMap,
      attribution: 'Open Street Map'
   });

   return layerPtr;
}

// ----------------------------------------------------------------------------
// Returns the styles for the road types
// ----------------------------------------------------------------------------

function getRoadTypeStyles(existing)
{
   var offRdColor = "green";
   var onRdColor  = "blue";
   var wklRdColor = "#FFAA00";

   if (existing)
   {
      lineStyle= "solid";
   }
   else // proposed
   {
      lineStyle  = "dash";
      offRdColor = "red";
      onRdColor  = "cyan";
   }

   lineStyle= "solid";   // HACK

   // Style for VicRoads - existing & proposed
   roadTypeStyles = {
      "Shared path":    { text: "On-road",  strokeColor:offRdColor, strokeWidth: 3, strokeOpacity: 0.9, strokeDashstyle: lineStyle },
      "Off Road":       { text: "Off-road", strokeColor:offRdColor, strokeWidth: 3, strokeOpacity: 0.9, strokeDashstyle: lineStyle },
      "Separated path": { text: "Off-road", strokeColor:offRdColor, strokeWidth: 3, strokeOpacity: 0.9, strokeDashstyle: lineStyle },
      "Bicycle path":   { text: "Off-road", strokeColor:offRdColor, strokeWidth: 3, strokeOpacity: 0.9, strokeDashstyle: lineStyle },

      "Road shoulder":  { text: "On-road",  strokeColor:onRdColor,  strokeWidth: 3, strokeOpacity: 0.9, strokeDashstyle: lineStyle },
      "On Road":        { text: "On-road",  strokeColor:onRdColor,  strokeWidth: 3, strokeOpacity: 0.9, strokeDashstyle: lineStyle },
      "Bicycle lane":   { text: "On-road",  strokeColor:onRdColor,  strokeWidth: 3, strokeOpacity: 0.9, strokeDashstyle: lineStyle },
      "Mixed traffic":  { text: "On-road",  strokeColor:onRdColor,  strokeWidth: 3, strokeOpacity: 0.9, strokeDashstyle: lineStyle },

      "Wide Kerbside Lane": { text: "Wide Kerbside Lane", strokeColor:wklRdColor, strokeWidth: 3, strokeOpacity: 0.9, strokeDashstyle: lineStyle }
   };

   return roadTypeStyles;
}

// ----------------------------------------------------------------------------
// Existing VicRoads paths layer
// ----------------------------------------------------------------------------

function extVRpLayer()
{
   var featureNSURL = "http://www.bikey.com.au/";
   var serviceURL   = featureNSURL + "cgi-bin/tinyows";
   var schemaURL    = serviceURL + "?service=WFS&version=1.0.0&request=DescribeFeatureType&TypeName=s_l_bp_exist";

   //var featureNSURL = "http://www.bikey.com.au/";
   //var serviceURL   = "http://www.bigyak.net.au/bike/redirector.php";
   //var schemaURL    = "http://www.bikey.com.au/cgi-bin/tinyows?service=WFS&version=1.0.0&request=DescribeFeatureType&TypeName=s_l_bp_exist";

// ----------------------------------------------------------------------------

    var rules = [new OpenLayers.Rule({
      symbolizer: {strokeColor:"blue",strokeWidth: 3,strokeOpacity: 1.0},
      elseFilter: true
   })];
      
   // Existing VicRoads style
   var theStyleMap = new OpenLayers.StyleMap();            

   theStyleMap.styles["default"].addRules(rules);
   theStyleMap.addUniqueValueRules("default", "type", getRoadTypeStyles(true));

// ----------------------------------------------------------------------------

   var layerPtr = new OpenLayers.Layer.Vector("VicRoads - existing", {
      projection: new OpenLayers.Projection("EPSG:4326"),
      minScale: km_10_to_5,
      strategies: [new OpenLayers.Strategy.BBOX({ratio:1.5})],
      protocol: new OpenLayers.Protocol.WFS({
         version:       "1.0.0",
         url:           serviceURL,
         featureType:   "s_l_bp_exist",
         srsName:       "EPSG:4326",
         featureNS:     featureNSURL,
         geometryName:  "the_geom",
         schema:        schemaURL
      }),
      styleMap: theStyleMap,
      attribution: 'Routes courtesy of VicRoads'
   });

   return layerPtr;
}

// ----------------------------------------------------------------------------
// Proposed VicRoads paths layer
// ----------------------------------------------------------------------------

function proVRpLayer()
{
   var featureNSURL = "http://www.bikey.com.au/";
   var serviceURL   = featureNSURL + "cgi-bin/tinyows";
   var schemaURL    = serviceURL + "?service=WFS&version=1.0.0&request=DescribeFeatureType&TypeName=s_l_bp_prop";

   //var featureNSURL = "http://www.bikey.com.au/";
   //var serviceURL   = "http://www.bigyak.net.au/bike/redirector.php";
   //var schemaURL    = "http://www.bikey.com.au/cgi-bin/tinyows?service=WFS&version=1.0.0&request=DescribeFeatureType&TypeName=s_l_bp_prop";

// ----------------------------------------------------------------------------

    var rules = [new OpenLayers.Rule({
      symbolizer: {strokeColor:"blue",strokeWidth: 3,strokeOpacity: 1.0},
      elseFilter: true
   })];
      
   // Existing VicRoads style
   var theStyleMap = new OpenLayers.StyleMap();            

   theStyleMap.styles["default"].addRules(rules);
   theStyleMap.addUniqueValueRules("default", "type", getRoadTypeStyles(false));

// ----------------------------------------------------------------------------

   var layerPtr = new OpenLayers.Layer.Vector("VicRoads - proposed", {
      projection: new OpenLayers.Projection("EPSG:4326"),
      minScale: km_10_to_5,
      strategies: [new OpenLayers.Strategy.BBOX({ratio:1.5})],
      protocol: new OpenLayers.Protocol.WFS({
         version:       "1.0.0",
         url:           serviceURL,
         featureType:   "s_l_bp_prop",
         srsName:       "EPSG:4326",
         featureNS:     featureNSURL,
         geometryName:  "the_geom",
         schema:        schemaURL
      }),
      styleMap: theStyleMap,
      attribution: 'Routes courtesy of VicRoads'
   });

   return layerPtr;
}

// ----------------------------------------------------------------------------
// Google KML layer
//
// http://mt2.google.com/mapslt?lyrs=kml:cj9S8tMLDs6AMLPIwVSb5QrpE-RQPU2WSL6RLlE8sD3WDAA|kv:3|kp:&x=459&y=315&z=9&w=256&h=256
//
// The x, y, and z are the image location and zoom level, with the same values used by
// Google or Bing (ie, standard slippymap tile co-ordinates).
//
// ----------------------------------------------------------------------------

function googleKMLLayer()
{
   var layerPtr = new OpenLayers.Layer.TMS("Melb & Geelong's shared paths", [
      "http://mt0.google.com/mapslt/",
      "http://mt1.google.com/mapslt/",
      "http://mt2.google.com/mapslt/",
      "http://mt3.google.com/mapslt/"],
    { type: 'png',
      getURL: makeTileURLc,  // replace the URL maker function with one of our own
      numZoomLevels: 18,
      minScale: km_100_to_50,
      isBaseLayer: false,    // defaults to true for TMS layers as they are typically baselayers
      buffer: 1,
      attribution: 'Big Yak - Mel & Geel paths - Google tiler'
   });

   return layerPtr;
}

// ----------------------------------------------------------------------------
// Google MMAP layer
//
// http://mt1.google.com/mapslt?lyrs=msid:102039560396402603008.00048c55e42c5df4be696|t:1283809332&x=925&y=628&z=10&w=256&h=256&gl=au&hl=en
//
// The x, y, and z are the image location and zoom level, with the same values used by
// Google or Bing (ie, standard slippymap tile co-ordinates).
//
// ----------------------------------------------------------------------------

function dseMagpieLayer()
{
   var layerPtr = new OpenLayers.Layer.TMS("DSE's magpies", [
      "http://mt0.google.com/mapslt/",
      "http://mt1.google.com/mapslt/",
      "http://mt2.google.com/mapslt/",
      "http://mt3.google.com/mapslt/"],
    { type: 'png',
      getURL: makeTileURLd,  // replace the URL maker function with one of our own
      numZoomLevels: 18,
      minScale: km_100_to_50,
      isBaseLayer: false,    // defaults to true for TMS layers as they are typically baselayers
      buffer: 1,
      attribution: 'Vic Gov DSE - Google tiler'
   });

   return layerPtr;
}

// ----------------------------------------------------------------------------
// Weather radar layer
// ----------------------------------------------------------------------------

function radarLayer()
{
   // filenames generated by BOM program --> write.free.sat.radar.js.sh
   // http://mirror.bom.gov.au/radar/IDR023.gif
   // http://www.bom.gov.au/radar/IDR023.observations.200912300907.png
   // http://www.bom.gov.au/radar/IDR023.observations.200912301908.png
   // http://www.bom.gov.au/products/radar_transparencies/IDR023.background.png
   // http://www.bom.gov.au/products/radar_transparencies/IDR023.range.png
   // http://www.bom.gov.au/radar/IDR023.T.200912310336.png

   var imageFeedUrl = nextURL(radar, radarRange);
   var imageBounds  = getRadarImageBounds(radar, radarRange);
   var imageSize    = new OpenLayers.Size(512,512);

// ----------------------------------------------------------------------------

   var layerPtr = new OpenLayers.Layer.Image("BOM Weather Radar", imageFeedUrl, imageBounds, imageSize,
    { isBaseLayer: false,    // defaults to true for image layers as they are typically baselayers
      minScale: km_500_to_200,
      transitionEffect: 'resize', // appears to have no useful effect in this case
      attribution: 'Bureau of Meteorology, Australia'
   });

   // control the animation interval timer
   layerPtr.events.register('visibilitychanged', null, function()
   {
      // layer visibility transitioned?
      if (layerPtr.getVisibility())
      {
         // not visible to visible
         // animate each frame in 1 sec intervals
         timerID = window.setInterval(updateRadarAnimation, 1000);
      }
      else
      {  // visible to not visible
         window.clearInterval(timerID);
      }
   });

   return layerPtr;
}

// ----------------------------------------------------------------------------
// A layer of concentric rings representing time by bike
// ----------------------------------------------------------------------------

function timeByBikeLayer()
{
   var layerPtr = new OpenLayers.Layer.Vector("Time by bike - click on icon", {
      projection: new OpenLayers.Projection("EPSG:4326"),
      attribution: 'Big Yak - Time by Bike'
   });

   layerPtr.events.register('visibilitychanged', null, updateTimeByBike);

   return layerPtr;
}

// ----------------------------------------------------------------------------
// Jump to the location selected in the pulldown list
// ----------------------------------------------------------------------------

function jumpToPlace()
{
   var place = places[document.getElementById("places").selectedIndex];
   document.getElementById("places").selectedIndex = 0;

   // is this an invalid selection such as a list spacer?
   if (place == 'U')
      return;

   // get the coordinates and zoom level for the selected place
   var placeView  = place.split(",");
   mapCenterLat   = parseFloat(placeView[0]);
   mapCenterLng   = parseFloat(placeView[1]);
   mapZoom        = parseInt  (placeView[2],10);

   // update the map centre and zoom level
   centerAndZoom(mapCenterLng,mapCenterLat,mapZoom);
}

// ----------------------------------------------------------------------------
// Focus on the trail selected in the pulldown list
// ----------------------------------------------------------------------------

function jumpToTrail()
{
   var trail = trailLocations[document.getElementById("trails").selectedIndex];
   document.getElementById("trails").selectedIndex = 0;

   // is this an invalid selection such as a list spacer?
   if (trail == 'U')
      return;

   // get the coordinates and zoom level for the selected trail
   var trailView  = trail.split(",");
   mapCenterLat   = parseFloat(trailView[0]);
   mapCenterLng   = parseFloat(trailView[1]);
   mapZoom        = parseInt  (trailView[2],10);

   // update the map centre and zoom level
   centerAndZoom(mapCenterLng,mapCenterLat,mapZoom);
}

// ----------------------------------------------------------------------------
// The user moved the map
// ----------------------------------------------------------------------------

function mapWasMoved()
{
   // reproject: EPSG:900913 --> EPSG:4326
   var center = map.getCenter().transform(map.getProjectionObject(), map.displayProjection);

   mapCenterLat = center.lat;
   mapCenterLng = center.lon;

   updateTimeByBike();

   refreshRadar();

   // get the state
   var state = identifyState(center.lat, center.lon);

   // do nothing if the state has not changed
   if (lastState == state) return;

   //alert(lastState + ' ----> ' + state);

//   for(var key in layerCtrl.layerStates)
//      examineObject(layerCtrl.layerStates[key]);
//      alert(layerCtrl.layerStates[key].name);

   // stop all the overlays from showing in the layer switcher
   // note this does not affect the layers visibility
   // note this count includes a hidden layer called:
   //    OpenLayers.Control.SelectFeature_459_container
   var len = map.layers.length;
   for (var i=0; i<len; i++)
   {
      if (!map.layers[i].isBaseLayer)
         map.layers[i].displayInLayerSwitcher = false;
   }

   // these always show in the layer switcher
   timeByBikeL.displayInLayerSwitcher = true;
   radarL.displayInLayerSwitcher = true;

   // now shows the layers associated with this location
   // z setup - left to right equals bottom to top
   switch (state)
   {
   case 'ACT':
      break;
   case 'NSW':
            sydcL.displayInLayerSwitcher = true;
         sydCCTVL.displayInLayerSwitcher = true;
        sydCmpgnL.displayInLayerSwitcher = true;
      break;
   case 'NT':
      break;
   case 'Qld':
      break;
   case 'SA':
      adlCmpgnL.displayInLayerSwitcher = true;
      break;
   case 'Tas':
      break;
   case 'Vic':
            mgspL.displayInLayerSwitcher = true;
            gKMLL.displayInLayerSwitcher = true;
          dseMpiL.displayInLayerSwitcher = true;
            mgsnL.displayInLayerSwitcher = true;
         melNewsL.displayInLayerSwitcher = true;
    melBikeShareL.displayInLayerSwitcher = true;
      break;
   case 'WA':
      break;
   default:
      // not in location where there is a radar
      radarL.displayInLayerSwitcher = false;
      break;
   }

   // http://trac.openlayers.org/ticket/1684
   // clearing the array forces the redraw to work
   layerCtrl.layerStates = [];
   layerCtrl.redraw();

   lastState = state;
}

// ----------------------------------------------------------------------------
// The user zoomed the map
// ----------------------------------------------------------------------------

function mapWasZoomed()
{
   mapZoom = map.getZoom();

   // remap the zoom to the radar range
   // a bigger zoom number is more close in
   switch (mapZoom)
   {
   // zoom levels 1 to 5 - the radar layer is off
   case 1:
   case 2:
   case 3:
   case 4:
   case 5:
   case 6:
   case 7:  //6
      radarRange = 'range512';
      break;
   case 8:  //7
      radarRange = 'range256';
      break;
   case 9:  //8
      radarRange = 'range128';
      break;
   default:
      radarRange = 'range64';

      // only some radars have the 64 km range but they all have 128 km
      if (radar.ranges[radarRange].length === 0)
         radarRange = 'range128';
      break;
   }

   // has the range changed after the zoom?
   if (lastRadarRange === radarRange) return;

   updateRadarImageSizeAndPosition();

   lastRadarRange = radarRange;
} 

// ----------------------------------------------------------------------------
// The user changed the base layer
// ----------------------------------------------------------------------------

function baselayerWasChanged()
{
   // update the url name to reflect the new baselayer
   var baseLayer = map.baseLayer;
   if (baseLayer.urlName !== undefined)
      mapTypeText = baseLayer.urlName;
   else
      alert('Baselayer url name is undefined');

   // and update the permalink to match
   if (permaLink !== null)
       permaLink.updateLink();
} 

// ----------------------------------------------------------------------------
// Replacement for the default ArgParser. If we use the permalink control it
// will call the argparser, which in turn starts to set layers based on the url
// parms. We don't want that, as we use google style map selection in the url.
// This routine basically does nothing but disable the original.
// ----------------------------------------------------------------------------

OpenLayers.Control.ArgParser1 = OpenLayers.Class(OpenLayers.Control,
{
  displayProjection: null,  

  initialize: function(options) { 
      OpenLayers.Control.prototype.initialize.apply(this, arguments); 
  }, 

  setMap: function(map) { 
      OpenLayers.Control.prototype.setMap.apply(this, arguments); 

      //make sure we dont already have an arg parser attached 
      for(var i=0, len=this.map.controls.length; i<len; i++) { 
          var control = this.map.controls[i]; 
          if ( (control != this) && 
               (control.CLASS_NAME == "OpenLayers.Control.ArgParser") ) { 
               
              // If a second argparser is added to the map, then we  
              // override the displayProjection to be the one added to the 
              // map.  
              if (control.displayProjection != this.displayProjection) { 
                  this.displayProjection = control.displayProjection; 
              }     
               
              break; 
          } 
      } 
      if (i == this.map.controls.length)
      {
          var args = OpenLayers.Util.getParameters();
          //alert ('Arg parser');
      } 
   }, 

   CLASS_NAME: "OpenLayers.Control.ArgParser" 
}); 

// ----------------------------------------------------------------------------
// Change the base layer according to the url parameters
// ----------------------------------------------------------------------------

function selectBaseLayer()
{
   // select the layer based on the url param
   var len = map.layers.length;
   for (var i=0; i<len; i++)
   {
      // only look at the base layers
      var theLayer = map.layers[i];
      if (theLayer.isBaseLayer)
      {
         // does this one match the url parm?
         if (theLayer.urlName == mapTypeText)
            map.setBaseLayer(theLayer);
      }
   }
} 

// ----------------------------------------------------------------------------
// Don't name this 'onload' as it's a reserved word in Firefox
// ----------------------------------------------------------------------------

function load()
{
   // get the cookie if there is one, otherwise defaults are set up
   getCookieParms();

   // Extract the parms from the URL - these will override the matching parms in the cookie if it's present
   getUrlParms();

   if (showAboutMsg)
      aboutMsgOpenBtnClick();
   else
      aboutMsgCloseBtnClick();

// ----------------------------------------------------------------------------

   // set the map options
   // 40075016.68 = 20037508.34 * 2 = circumference of the Earth in metres: WGS 84
   // 156543.0339 = (20037508.34 - (-20037508.34)) / 256 pixels
   var options = {
      projection: new OpenLayers.Projection("EPSG:900913"),         // Spherical Mercator - coords x/y metres
      displayProjection: new OpenLayers.Projection("EPSG:4326"),    // x/y as latitude and longitude
      units: "m",
      numZoomLevels: 18,
      maxResolution: 156543.0339,
      maxExtent: new OpenLayers.Bounds(-20037508.34, -20037508.34, 20037508.34, 20037508.34),
      controls: []
      };

   map = new OpenLayers.Map('map', options);

   // missing tiles come up in pink in IE7 - make them transparent
   OpenLayers.Util.onImageLoadErrorColor = "transparent";

   layerCtrl = new OpenLayers.Control.LayerSwitcher();
   map.addControl(layerCtrl);

   map.addControl(new OpenLayers.Control.ZoomBox());
   map.addControl(new OpenLayers.Control.PanZoomBar());

   // allow dragging when outside the viewport
   map.addControl(new OpenLayers.Control.Navigation({zoomWheelEnabled: true, documentDrag: true}));      
   map.addControl(new OpenLayers.Control.MousePosition({numDigits: 6}));
   map.addControl(new OpenLayers.Control.ScaleLine());
   map.addControl(new OpenLayers.Control.Scale());
   map.addControl(new OpenLayers.Control.Attribution({separator: '<br/>'}));

// ----------------------------------------------------------------------------

   nearBL = nearMapLayer();
   osmcBL = osmcLayer();

   // the Google and OSM layers are built in base layers
   gPhyBL = new OpenLayers.Layer.Google("Google Terrain",   {type: G_PHYSICAL_MAP,  sphericalMercator: true});
   gMapBL = new OpenLayers.Layer.Google("Google Streets",   {type: G_NORMAL_MAP,    sphericalMercator: true});
   gSatBL = new OpenLayers.Layer.Google("Google Satellite", {type: G_SATELLITE_MAP, sphericalMercator: true, numZoomLevels: 22});
   gHybBL = new OpenLayers.Layer.Google("Google Hybrid",    {type: G_HYBRID_MAP,    sphericalMercator: true});

   osmBL  = new OpenLayers.Layer.OSM   ("Open Street Map");
   //osmBL  = new OpenLayers.Layer.OSM("t@h", "http://tah.openstreetmap.org/Tiles/tile/${z}/${x}/${y}.png");

   // these will be placed in the permalink
   nearBL.urlName = 'nr';
   osmcBL.urlName = 'cy';
   gPhyBL.urlName = 'p';
   gMapBL.urlName = 'm';
   gSatBL.urlName = 'k';
   gHybBL.urlName = 'h';
   osmBL.urlName  = 'os';

   // in menu order - remember to set baselayerWasChanged to match
   map.addLayers([nearBL,osmcBL,gPhyBL,gMapBL,gSatBL,gHybBL,osmBL]);

// ----------------------------------------------------------------------------

   // URL must be relative - using a full URL results in all calls going via the Proxy
   // This is only required by some services that use AJAX such as WFS & WMS
   // Tile servers do not require it. Indications the proxy is not working - AJAX response:
   // "The resource from this URL is not text:"
   // "XML Parsing Error: no element found Location: moz-nullprincipal: ....""

   //OpenLayers.ProxyHost = "cd/redirector.php?url=";

   // these are all overlays, not base layers
   osmpL   = osmpLayer();      // OSM cycle routes as vector overlay
   extpL   = extVRpLayer();
   propL   = proVRpLayer();
   gKMLL   = googleKMLLayer();
   dseMpiL = dseMagpieLayer();
   radarL  = radarLayer();

   mgspL         = mgspLayer();
   timeByBikeL   = timeByBikeLayer();
   mgsnL         = mgsnLayer();
   melNewsL      = melNewsLayer();
   melBikeShareL = melBikeShareLayer();

   sydcL       = sydcLayer();
   sydCCTVL    = sydCCTVLayer();
   adlCmpgnL   = adlCampaignsLayer();
   sydCmpgnL   = sydCampaignsLayer();

   // layers are actually loaded when they are set to visible
   // these are not available for use
   osmpL.setVisibility(false);
   extpL.setVisibility(false);
   propL.setVisibility(false);
   mgspL.setVisibility(false);

   // these are available for use
   gKMLL.setVisibility(true);
   dseMpiL.setVisibility(false);
   timeByBikeL.setVisibility(false);
   mgsnL.setVisibility(true);
   melNewsL.setVisibility(false);
   melBikeShareL.setVisibility(false);
   sydcL.setVisibility(false);
   sydCCTVL.setVisibility(false);
   adlCmpgnL.setVisibility(true);
   sydCmpgnL.setVisibility(true);
   radarL.setVisibility(false);  // minor bug - if true it will not animate at start up

   // z setup - left to right equals bottom to top
   map.addLayers([gKMLL,timeByBikeL,mgsnL,dseMpiL,melNewsL,melBikeShareL,adlCmpgnL,sydCmpgnL,sydcL,sydCCTVL,radarL]);

   map.addControl(new OpenLayers.Control.ArgParser1);

   // this needs to be set up after all the layers have been added
   // It reads the URL and sets the overlay checkboxes in the layer switcher
   //map.addControl(new OpenLayers.Control.Permalink());
   //permaLink = new OpenLayers.Control.Permalink({eventListeners: [testpLink]});
   permaLink = new OpenLayers.Control.Permalink();

   // replace the default permalink code with our code
   // createParams in permaLink is overidden by getPermaLinkParms
   OpenLayers.Util.extend(permaLink,
   {
   updateLink: function()
   { 
      var href = this.base; 

      if (href.indexOf('?') != -1)
         href = href.substring( 0, href.indexOf('?') ); 
 
      // get parms, then encode
      href += '?' + OpenLayers.Util.getParameterString(getPermaLinkParms());
 
      this.element.href = href; 
   }
   });

   map.addControl(permaLink);

   // compensate for existing permalinks
   //        012345
   // layers=B00000TFTTT
   osmBL.setVisibility(false);
   gHybBL.setVisibility(false);

   // change the base layer according to the url parameters
   selectBaseLayer();

   // this needs to be set up after all the layers have been created
   // set up the on click handler
   selectControl = new OpenLayers.Control.SelectFeature([mgspL,timeByBikeL,mgsnL,melNewsL,melBikeShareL,sydcL,sydCCTVL,adlCmpgnL,sydCmpgnL], {
      onBeforeSelect: onBeforeFeatureSelect,
      onSelect:   onFeatureSelect,
      onUnselect: onFeatureUnselect});

   map.addControl(selectControl);
   selectControl.activate();

// ----------------------------------------------------------------------------

   map.events.register('changebaselayer', null, baselayerWasChanged);
   map.events.register('moveend', null, mapWasMoved);
   map.events.register('zoomend', null, mapWasZoomed);

   // nothing gets drawn till this is executed or the URL has a center parameter
   // this will trigger the map move and zoom events
   centerAndZoom(mapCenterLng,mapCenterLat,mapZoom);

// ----------------------------------------------------------------------------

   // show the layer switcher panel?
   if (showLayerSw)
      layerCtrl.maximizeControl();

   document.getElementById("loadingMsg").innerHTML = '';

   if (OpenLayers.Layer.Google.getWarningHTML !== undefined)
      alert(OpenLayers.Layer.Google.getWarningHTML);
}

// execute this
window.onload = load;

window.onunload = function()
{
   if (map === null) return;

   setCookieParmeters(3);
   map.unloadDestroy;
};

// ----------------------------------------------------------------------------
