WME OpenData

Provides access to certain OS OpenData products within the WME environment

目前為 2020-09-13 提交的版本,檢視 最新版本

// ==UserScript==
// @name                WME OpenData
// @namespace           http://greasemonkey.chizzum.com
// @description         Provides access to certain OS OpenData products within the WME environment
// @include             https://*.waze.com/*editor*
// @include             https://editor-beta.waze.com/*
// @include             https://beta.waze.com/*
// @include             https://one.network/*
// @include             http://public.londonworks.gov.uk/roadworks/*
// @include             http://streetworks.wiltshire.gov.uk/*
// @include             https://openspacewmb.ordnancesurvey.co.uk/osmapapi/mapbuilder*
// @grant               GM_setValue
// @grant               GM_getValue
// @grant               GM_addValueChangeListener
// @grant               unsafeWindow
// @version             3.10
// ==/UserScript==

// Contains Ordnance Survey data Crown copyright and database right 2012-2020
//
// Contents of the locatorData_*.js files are derived under the
// Open Government Licence from the OS Open Names and Open Roads datasets
//
// Contents of the gazetteer.js file are derived under the
// Open Government Licence from the OS Open Names dataset

/*
=======================================================================================================================
DONE FOR THIS RELEASE
=======================================================================================================================
Uses gazetteer data derived from the Open Names dataset
Fixes startup error on one.network page

=======================================================================================================================
Bug fixes - MUST BE CLEARED BEFORE RELEASE
=======================================================================================================================

=======================================================================================================================
Things to be checked
=======================================================================================================================


=======================================================================================================================
Proposed functionality
=======================================================================================================================
Restrict city name uniqueness testing to within each county rather than country-wide


=======================================================================================================================
New functionality in progress
=======================================================================================================================
Mark on map where the OS city names are located

*/

/* JSHint Directives */
/* globals OpenLayers: true */
/* globals osMap: true */
/* globals OpenSpace: true */
/* globals Elgin: true */
/* globals google: true */
/* globals gazetteerDataA: true */
/* globals gazetteerDataB: true */
/* globals oslRoadNameMatches: true */
/* globals map: true */
/* globals GM_setValue: true */
/* globals GM_getValue: true */
/* globals GM_addValueChangeListener: true */
/* globals unsafeWindow: true */
/* jshint bitwise: false */
/* jshint evil: true */
/* jshint esversion: 6 */

const oslVersion = '3.10';
const oslUpdateURL = 'https://greasyfork.org/scripts/1941-wme-to-os-link';
const oslBlockPath = 'https://greasemonkey.chizzum.com/osl_v3.0/';
const oslGazetteerURL = 'https://chizzum.com/greasemonkey/gaz_v5/gazetteer.js';
const oslPi = 3.14159265358979;
const oslPiDiv180 = (oslPi / 180);
const osl180DivPi = (180 / oslPi);
const oslLocatorBlockSize = 1000;
const oslCacheDecayPeriod = 60;
const oslMinUIWidth = 360;

const GAZ_ELM =
{
   Name: 0,
   CEast: 1,
   CNorth: 2,
   Type: 3,
   Area: 4,
   LEast: 5,
   LNorth: 6,
   REast: 7,
   RNorth: 8
};
const OSL_ELM =
{
   RoadName: 0,
   RoadNumber: 1,
   BoundW: 2,
   BoundE: 3,
   BoundS: 4,
   BoundN: 5,
   Geometry: 6,
   AreaName: 7,
   AltName: 8,
   Classification: 9,
   Function: 10,
   Form: 11,
   Structure: 12,
   IsPrimary: 13,
   IsTrunk: 14,
   MAX: 15
};
const OSL_MODE =
{
   Conversion: 0,
   OpenNames: 1,
   NameCheck: 2,
   OpenRoads: 3,
};

const OSL_ROADCLASSIFICATIONS =
{
   Undefined: 0,
   Motorway: 1,
   A_Road: 2,
   B_Road: 3,
   Classified_Unnumbered: 4,
   Unclassified: 5,
   Not_Classified: 6,
   Unknown: 7
};
const oslRoadClassifications = new Array
(
   'Undefined',
   'Motorway',
   'A Road',
   'B Road',
   'Classified Unnumbered',
   'Unclassified',
   'Not Classified',
   'Unknown'
);

const OSL_ROADFUNCTIONS =
{
   Undefined: 0,
   Motorway: 1,
   A_Road: 2,
   B_Road: 3,
   Minor_Road: 4,
   Local_Road: 5,
   Local_Access_Road: 6,
   Restricted_Local_Access_Road: 7,
   Secondary_Access_Road: 8
};
const oslRoadFunctions = new Array
(
   'Undefined',
   'Motorway',
   'A Road',
   'B Road',
   'Minor Road',
   'Local Road',
   'Local Access Road',
   'Restricted Local Access Road',
   'Secondary Access Road'
);
const oslStrokeColoursByFunction = new Array
(
   "red",
   "deepskyblue",
   "limegreen",
   "darkorange",
   "yellow",
   "white",
   "grey",
   "tan",
   "grey"
);

const OSL_ROADSTRUCTURES =
{
   Undefined: 0,
   Road_In_Tunnel: 1,
   Road_On_Bridge: 2
};
const oslRoadStructures = new Array
(
   'Undefined',
   'Road In Tunnel',
   'Road On Bridge'
);

const OSL_FORMSOFWAY =
{
   Undefined: 0,
   Single_Carriageway: 1,
   Dual_Carriageway: 2,
   Slip_Road: 3,
   Roundabout: 4,
   Collapsed_Dual_Carriageway: 5,
   Guided_Busway: 6,
   Shared_Use_Carriageway: 7
};
const oslFormsOfWay = new Array
(
   'Undefined',
   'Single Carriageway',
   'Dual Carriageway',
   'Slip Road',
   'Roundabout',
   'Collapsed Dual Carriageway',
   'Guided Busway',
   'Shared Use Carriageway'
);

// include names that don't get abbreviated, so that for names such as "Somewhere Green Way", the
// abbreviation function is able to detect the "Way" part of the name as the part that ought to
// be abbreviated, rather than treating it as a non-abbreviatable suffix and instead abbreviating
// the "Green" part instead...
const oslNameAbbreviations = new Array
(
   'Avenue','Ave',
   'Boulevard','Blvd',
   'Broadway','Bdwy',
   'Circus','Cir',
   'Close','Cl',
   'Court','Ct',
   'Crescent','Cr',
   'Drive','Dr',
   'Garden','Gdn',
   'Gardens','Gdns',
   'Green','Gn',
   'Grove','Gr',
   'Lane','Ln',
   'Mews','Mews',
   'Mount','Mt',
   'Place','Pl',
   'Park','Pk',
   'Ridge','Rdg',
   'Road','Rd',
   'Square','Sq',
   'Street','St',
   'Terrace','Ter',
   'Valley','Val',
   'By-pass','Bypass',
   'Way','Way'
);


var oslUserPrefs = {};

var oslGazetteerData = [];

var oslAdvancedMode = false;
var oslEvalString = '';
var oslLoadingMsg = false;
var oslMLCDiv = null;
var oslOSLDiv = null;
var oslBBDiv = null;
var oslPrevHighlighted = null;
var oslSegmentHighlighted = false;
var oslPrevMouseX = null;
var oslPrevMouseY = null;
var oslDivDragging = false;
var oslPrevSelected = null;
var oslDoOSLUpdate = false;
var oslMousepos = null;
var oslMousePixelpos = null;
var oslDoneOnload = false;
var oslRefreshAutoTrack = false;
var oslOSOD_url = '';
var oslOSMC_url = '';
var oslRWO_url = '';
var oslLRR_url = '';
var oslSWG_url = '';
var oslPrevStreetName = '';
var oslMergeGazData = false;
var oslOSLMaskLayer = null;
var oslOSLNameCheckTimer = 0;
var oslOSLNCSegments = [];
var oslInUK = false;
var oslInLondon = false;
var oslInWiltshire = false;

var oslNorthings = null;
var oslEastings = null;
var oslLatitude = null;
var oslLongitude = null;
var oslHelmX = null;
var oslHelmY = null;
var oslHelmZ = null;

var oslBlocksToLoad = [];
var oslBlocksToTest = [];

// 50.06574112187924 -5.699894626953322
// 135261,25033
// 135256,25014
// +6, +19

// 51.35363338966115 1.4443961072966522
// 639795,167306
// 639800,167284
// -5, +22

// 60.15795987870581 -1.1466271562283679
// 447367,1141743
// 447362,1141733
// +5, +10

var oslVPLeft = 0;
var oslVPRight = 0;
var oslVPBottom = 0;
var oslVPTop = 0;
var oslBBDivInnerHTML = '';

var oslEvalEBlock = 0;
var oslEvalNBlock = 0;
var oslBlockData = null;
var oslBlockCacheList = [];
var oslBlockCacheTestTimer = (oslCacheDecayPeriod * 10);
var oslSegGeoDivInnerHTML = '';

var oslONC_E = null;
var oslONC_N = null;
var oslEBlock_min = null;
var oslEBlock_max = null;
var oslNBlock_min = null;
var oslNBlock_max = null;

var oslWazeBitsPresent = 0;

var oslOSLDivLeft;
var oslOSLDivTop;
var oslSegGeoDiv;
var oslWazeMapElement;
var oslDragBar;
var oslWindow;
var oslOSLDivTopMinimised;
var oslNCDiv;
var oslSegGeoUIDiv;

var oslOffsetToolbar = false;
var oslMOAdded = false;
var oslMTEMode = false;

var oslLocatorElements = null;
var oslUsingNewName = false;
var oslUseName = false;
var oslCityName = '';
var oslCountyName = '';
var oslUseAlt = false;
var oslAltNamesClearing = false;

var oslRORCenter = null;
var oslRORZoom = null;

var W = null;


function oslBootstrap()
{
   if(document.location.host == 'one.network')
   {
      hlp_ONE.init();
   }
   else if(document.location.host == 'openspacewmb.ordnancesurvey.co.uk')
   {
      hlp_ODM.init();
   }
   else if(document.location.host == 'public.londonworks.gov.uk')
   {
      hlp_LRR.init();
   }
   else if(document.location.host == 'streetworks.wiltshire.gov.uk')
   {
      hlp_WSW.init();
   }
   else
   {
      // something in this version prevents WME from starting up properly if we initialise early in the WME startup.
      // since I haven't yet figured out what it is we need to wait for, using a fixed delay of 2s seems to work OK...
      window.setTimeout(oslInitialise,2000);
   }
}
function oslAddLog(logtext)
{
   console.log('WMEOpenData: '+logtext);
}

//-----------------------------------------------------------------------------------------------------------------------------------------
// all code between here and the next ------------- marker line is a stripped down version of the original from Paul Dixon
//
// * GeoTools javascript coordinate transformations
// * http://www.nearby.org.uk/tests/geotools2.js
// *
// * This file copyright (c)2005 Paul Dixon ([email protected])
// *
// * This program is free software; you can redistribute it and/or
// * modify it under the terms of the GNU General Public License
// * as published by the Free Software Foundation; either version 2
// * of the License, or (at your option) any later version.
// *
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU General Public License for more details.
// *
// * You should have received a copy of the GNU General Public License
// * along with this program; if not, write to the Free Software
// * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
// *
// * ---------------------------------------------------------------------------
// *
// * Credits
// *
// * The algorithm used by the script for WGS84-OSGB36 conversions is derived
// * from an OSGB spreadsheet (www.gps.gov.uk) with permission. This has been
// * adapted into Perl by Ian Harris, and into PHP by Barry Hunter. Conversion
// * accuracy is in the order of 7m for 90% of Great Britain, and should be
// * be similar to the conversion made by a typical GPSr
// *
// * See accompanying documentation for more information
// * http://www.nearby.org.uk/tests/GeoTools2.html
function oslOSGBtoWGS(oseast, osnorth)
{
   const a = 6377563.396;
   const b = 6356256.910;
   const e0 = 400000;
   const n0 = -100000;
   const f0 = 0.999601272;
   const PHI0 = 49.00000;
   const LAM0 = -2.00000;
   const RadPHI0 = PHI0 * oslPiDiv180;
   const RadLAM0 = LAM0 * oslPiDiv180;

   //Compute af0, bf0, e squared (e2), n and Et
   var af0 = a * f0;
   var bf0 = b * f0;
   var e2 = (Math.pow(af0,2) - Math.pow(bf0,2)) / Math.pow(af0,2);
   var n = (af0 - bf0) / (af0 + bf0);
   var Et = oseast - e0;

   //Compute initial value for oslLatitude (PHId) in radians
   var PHI1 = ((osnorth - n0) / af0) + RadPHI0;
   var M = oslMarc(bf0, n, RadPHI0, PHI1);
   var PHId = ((osnorth - n0 - M) / af0) + PHI1;
   while (Math.abs(osnorth - n0 - M) > 0.00001)
   {
      PHId = ((osnorth - n0 - M) / af0) + PHI1;
      M = oslMarc(bf0, n, RadPHI0, PHId);
      PHI1 = PHId;
   }

   //Compute nu, rho and eta2 using value for PHId
   var nu = af0 / (Math.sqrt(1 - (e2 * ( Math.pow(Math.sin(PHId),2)))));
   var rho = (nu * (1 - e2)) / (1 - (e2 * Math.pow(Math.sin(PHId),2)));
   var eta2 = (nu / rho) - 1;

   //Compute Latitude
   var VII = (Math.tan(PHId)) / (2 * rho * nu);
   var VIII = ((Math.tan(PHId)) / (24 * rho * Math.pow(nu,3))) * (5 + (3 * (Math.pow(Math.tan(PHId),2))) + eta2 - (9 * eta2 * (Math.pow(Math.tan(PHId),2))));
   //var IX = ((Math.tan(PHId)) / (720 * rho * Math.pow(nu,5))) * (61 + (90 * ((Math.tan(PHId)) ^ 2)) + (45 * (Math.pow(Math.tan(PHId),4))));
   var IX = ((Math.tan(PHId)) / (720 * rho * Math.pow(nu,5))) * (61 + (90 * (Math.pow(Math.tan(PHId), 2))) + (45 * (Math.pow(Math.tan(PHId),4))));
   //oslLatitude = osl180DivPi * (PHId - (Math.pow(Et,2) * VII) + (Math.pow(Et,4) * VIII) - ((Et ^ 6) * IX));
   oslLatitude = osl180DivPi * (PHId - (Math.pow(Et,2) * VII) + (Math.pow(Et,4) * VIII) - (Math.pow(Et, 6) * IX));

   //Compute Longitude
   var X = (Math.pow(Math.cos(PHId),-1)) / nu;
   var XI = ((Math.pow(Math.cos(PHId),-1)) / (6 * Math.pow(nu,3))) * ((nu / rho) + (2 * (Math.pow(Math.tan(PHId),2))));
   var XII = ((Math.pow(Math.cos(PHId),-1)) / (120 * Math.pow(nu,5))) * (5 + (28 * (Math.pow(Math.tan(PHId),2))) + (24 * (Math.pow(Math.tan(PHId),4))));
   var XIIA = ((Math.pow(Math.cos(PHId),-1)) / (5040 * Math.pow(nu,7))) * (61 + (662 * (Math.pow(Math.tan(PHId),2))) + (1320 * (Math.pow(Math.tan(PHId),4))) + (720 * (Math.pow(Math.tan(PHId),6))));
   oslLongitude = osl180DivPi * (RadLAM0 + (Et * X) - (Math.pow(Et,3) * XI) + (Math.pow(Et,5) * XII) - (Math.pow(Et,7) * XIIA));



   var RadPHI = oslLatitude * oslPiDiv180;
   var RadLAM = oslLongitude * oslPiDiv180;

   e2 = (Math.pow(6377563.396,2) - Math.pow(6356256.910,2)) / Math.pow(6377563.396,2);
   var V = a / (Math.sqrt(1 - (e2 * (  Math.pow(Math.sin(RadPHI),2)))));

   X = V * (Math.cos(RadPHI)) * (Math.cos(RadLAM));
   var Y = V * (Math.cos(RadPHI)) * (Math.sin(RadLAM));
   var Z = (V * (1 - e2)) * (Math.sin(RadPHI));

   // do Helmert transforms

   var sfactor = -20.4894 * 0.000001;
   var RadX_Rot = (0.1502 / 3600) * oslPiDiv180;
   var RadY_Rot = (0.2470 / 3600) * oslPiDiv180;
   var RadZ_Rot = (0.8421 / 3600) * oslPiDiv180;

   var X2 = (X + (X * sfactor) - (Y * RadZ_Rot) + (Z * RadY_Rot) + 446.448);
   var Y2 = (X * RadZ_Rot) + Y + (Y * sfactor) - (Z * RadX_Rot) -125.157;
   var Z2 = (-1 * X * RadY_Rot) + (Y * RadX_Rot) + Z + (Z * sfactor) + 542.060;

   var RootXYSqr = Math.sqrt(Math.pow(X2,2) + Math.pow(Y2,2));
   e2 = (Math.pow(6378137.000,2) - Math.pow(6356752.313,2)) / Math.pow(6378137.000,2);
   PHI1 = Math.atan2(Z2 , (RootXYSqr * (1 - e2)) );

   V = 6378137.000 / (Math.sqrt(1 - (e2 * Math.pow(Math.sin(PHI1),2))));
   var PHI2 = Math.atan2((Z + (e2 * V * (Math.sin(PHI1)))) , RootXYSqr);
   while (Math.abs(PHI1 - PHI2) > 0.000000001)
   {
      PHI1 = PHI2;
      V = 6378137.000 / (Math.sqrt(1 - (e2 * Math.pow(Math.sin(PHI1),2))));
      PHI2 = Math.atan2((Z2 + (e2 * V * (Math.sin(PHI1)))) , RootXYSqr);
   }
   oslLatitude = PHI2 * osl180DivPi;
   oslLongitude = Math.atan2(Y2 , X2) * osl180DivPi;
}
function oslWGStoOSGB()
{
   oslLatLontoHelmXYZ();
   var oslLatitude2  = oslXYZtoLat();
   var oslLongitude2 = Math.atan2(oslHelmY , oslHelmX) * osl180DivPi;
   oslLatLonoslToOSGrid(oslLatitude2,oslLongitude2);
}
function oslLatLontoHelmXYZ()
{
   const a = 6378137.0;
   const b = 6356752.313;
   const DX = -446.448;
   const DY = 125.157;
   const DZ = -542.060;
   const rotX = -0.1502;
   const rotY = -0.2470;
   const rotZ = -0.8421;
   const sfactor = 20.4894 * 0.000001;
   const e2 = (Math.pow(a,2) - Math.pow(b,2)) / Math.pow(a,2);

   // perform initial lat-lon to cartesian coordinate translation
   var RadPHI = oslLatitude * oslPiDiv180;
   var RadLAM = oslLongitude * oslPiDiv180;
   var V = a / (Math.sqrt(1 - (e2 * (  Math.pow(Math.sin(RadPHI),2)))));
   var cartX = V * (Math.cos(RadPHI)) * (Math.cos(RadLAM));
   var cartY = V * (Math.cos(RadPHI)) * (Math.sin(RadLAM));
   var cartZ = (V * (1 - e2)) * (Math.sin(RadPHI));

   // Compute Helmert transformed coordinates
   var RadX_Rot = (rotX / 3600) * oslPiDiv180;
   var RadY_Rot = (rotY / 3600) * oslPiDiv180;
   var RadZ_Rot = (rotZ / 3600) * oslPiDiv180;
   oslHelmX = (cartX + (cartX * sfactor) - (cartY * RadZ_Rot) + (cartZ * RadY_Rot) + DX);
   oslHelmY = (cartX * RadZ_Rot) + cartY + (cartY * sfactor) - (cartZ * RadX_Rot) + DY;
   oslHelmZ = (-1 * cartX * RadY_Rot) + (cartY * RadX_Rot) + cartZ + (cartZ * sfactor) + DZ;
}
function oslXYZtoLat()
{
   const a = 6377563.396;
   const b = 6356256.910;
   const e2 = (Math.pow(a,2) - Math.pow(b,2)) / Math.pow(a,2);

   var RootXYSqr = Math.sqrt(Math.pow(oslHelmX,2) + Math.pow(oslHelmY,2));
   var PHI1 = Math.atan2(oslHelmZ , (RootXYSqr * (1 - e2)) );
   var PHI = oslIterateOSLXYZtoLat(a, e2, PHI1, oslHelmZ, RootXYSqr);
   return PHI * osl180DivPi;
}
function oslIterateOSLXYZtoLat(a, e2, PHI1, Z, RootXYSqr)
{
   var V = a / (Math.sqrt(1 - (e2 * Math.pow(Math.sin(PHI1),2))));
   var PHI2 = Math.atan2((Z + (e2 * V * (Math.sin(PHI1)))) , RootXYSqr);
   while (Math.abs(PHI1 - PHI2) > 0.000000001)
   {
      PHI1 = PHI2;
      V = a / (Math.sqrt(1 - (e2 * Math.pow(Math.sin(PHI1),2))));
      PHI2 = Math.atan2((Z + (e2 * V * (Math.sin(PHI1)))) , RootXYSqr);
   }
   return PHI2;
}
function oslMarc(bf0, n, PHI0, PHI)
{
   return bf0 * (((1 + n + ((5 / 4) * Math.pow(n,2)) + ((5 / 4) * Math.pow(n,3))) * (PHI - PHI0)) - (((3 * n) + (3 * Math.pow(n,2)) +
          ((21 / 8) * Math.pow(n,3))) * (Math.sin(PHI - PHI0)) * (Math.cos(PHI + PHI0))) + ((((15 / 8) * Math.pow(n,2)) + ((15 / 8) *
          Math.pow(n,3))) * (Math.sin(2 * (PHI - PHI0))) * (Math.cos(2 * (PHI + PHI0)))) - (((35 / 24) * Math.pow(n,3)) *
          (Math.sin(3 * (PHI - PHI0))) * (Math.cos(3 * (PHI + PHI0)))));
}
function oslLatLonoslToOSGrid(PHI, LAM)
{
   const a = 6377563.396;
   const b = 6356256.910;
   const e0 = 400000;
   const n0 = -100000;
   const f0 = 0.999601272;
   const PHI0 = 49.00000;
   const LAM0 = -2.00000;
   const RadPHI0 = PHI0 * oslPiDiv180;
   const RadLAM0 = LAM0 * oslPiDiv180;
   const af0 = a * f0;
   const bf0 = b * f0;
   const n = (af0 - bf0) / (af0 + bf0);
   const e2 = (Math.pow(af0,2) - Math.pow(bf0,2)) / Math.pow(af0,2);

   var RadPHI = PHI * oslPiDiv180;
   var RadLAM = LAM * oslPiDiv180;
   var nu = af0 / (Math.sqrt(1 - (e2 * Math.pow(Math.sin(RadPHI),2) )));
   var rho = (nu * (1 - e2)) / (1 - (e2 * Math.pow(Math.sin(RadPHI),2) ));
   var eta2 = (nu / rho) - 1;
   var p = RadLAM - RadLAM0;
   var M = oslMarc(bf0, n, RadPHI0, RadPHI);
   var I = M + n0;
   var II = (nu / 2) * (Math.sin(RadPHI)) * (Math.cos(RadPHI));
   var III = ((nu / 24) * (Math.sin(RadPHI)) * (Math.pow(Math.cos(RadPHI),3))) * (5 - (Math.pow(Math.tan(RadPHI),2)) + (9 * eta2));
   var IIIA = ((nu / 720) * (Math.sin(RadPHI)) * (Math.pow(Math.cos(RadPHI),5))) * (61 - (58 * (Math.pow(Math.tan(RadPHI),2))) + (Math.pow(Math.tan(RadPHI),4)));
   var IV = nu * (Math.cos(RadPHI));
   var V = (nu / 6) * ( Math.pow(Math.cos(RadPHI),3)) * ((nu / rho) - (Math.pow(Math.tan(RadPHI),2)));
   var VI = (nu / 120) * (Math.pow(Math.cos(RadPHI),5)) * (5 - (18 * (Math.pow(Math.tan(RadPHI),2))) + (Math.pow(Math.tan(RadPHI),4)) + (14 * eta2) - (58 * (Math.pow(Math.tan(RadPHI),2)) * eta2));
   oslEastings = Math.round(e0 + (p * IV) + (Math.pow(p,3) * V) + (Math.pow(p,5) * VI));
   oslNorthings = Math.round(I + (Math.pow(p,2) * II) + (Math.pow(p,4) * III) + (Math.pow(p,6) * IIIA));
   oslNorthings -= 16; // the above calculations give a northings error of between 10-22m, so add a final adjustment so that the error is evened out and reduced to +/- 6m
}
//-----------------------------------------------------------------------------------------------------------------------------------------
function oslCaseCorrect(wrongcase)
{
   var loop;
   var correctedCase = '';
   for(loop=0;loop<wrongcase.length;loop++)
   {
      // capitalise first letter following one of these substrings
      if
      (
         (loop === 0)||
         (wrongcase[loop-1] == ' ')||
         (wrongcase[loop-1] == '(')||
         (wrongcase.substr(loop-3,3) == '-Y-')||
         (wrongcase.substr(loop-4,4) == '-YR-')
      ) correctedCase += wrongcase[loop].toUpperCase();
      else correctedCase += wrongcase[loop].toLowerCase();
   }
   // recapitalise any roman numerals
   correctedCase = correctedCase.replace(' Ii ',' II ');
   correctedCase = correctedCase.replace(' Iii ',' III ');
   correctedCase = correctedCase.replace(' Iv ',' IV ');
   correctedCase = correctedCase.replace(' Vi ',' VI ');
   correctedCase = correctedCase.replace(' Vii ',' VII ');
   return correctedCase;
}
function oslSaintsPreserveUs(oslName)
{
   var nameBits = [];
   if(oslName.indexOf('St ') != -1)
   {
      nameBits = oslName.split('St ');
      oslName = nameBits[0] + 'St. ' + nameBits[1];
   }
   else if(oslName.indexOf('Saint ') != -1)
   {
      nameBits = oslName.split('Saint ');
      oslName = nameBits[0] + 'St. ' + nameBits[1];
   }
   return oslName;
}
function oslWazeifyStreetName(oslName, debugOutput)
{
   var wazeName = '';
   var loop;

   // strip out any HTML encoding added by the server when returning the street name data...
   var textArea = document.createElement('textarea');
   textArea.innerHTML = oslName;
   oslName = textArea.value;

   wazeName = oslCaseCorrect(oslName);
   wazeName = oslSaintsPreserveUs(wazeName);

   var nameoslPieces = wazeName.split(' ');
   if(nameoslPieces.length > 1)
   {
      var dirSuffix = '';
      var namePrefix = '';
      if((nameoslPieces[nameoslPieces.length-1] == 'North')||(nameoslPieces[nameoslPieces.length-1] == 'South')||(nameoslPieces[nameoslPieces.length-1] == 'East')||(nameoslPieces[nameoslPieces.length-1] == 'West'))
      {
         dirSuffix = ' ' + nameoslPieces[nameoslPieces.length-1][0];
         for(loop=0;loop<nameoslPieces.length-1;loop++) namePrefix += (nameoslPieces[loop] + ' ');
      }
      else
      {
         for(loop=0;loop<nameoslPieces.length;loop++) namePrefix += (nameoslPieces[loop] + ' ');

      }
      namePrefix = namePrefix.trimRight(1);

      if(debugOutput === true) console.log(oslName);
      // replace road type with abbreviated form
      for(var pass=0;pass<2;pass++)
      {
         for(loop=0;loop<oslNameAbbreviations.length;loop+=2)
         {
            var abbrPos = namePrefix.lastIndexOf(oslNameAbbreviations[loop]);
            var abbrLen = oslNameAbbreviations[loop].length;
            var npLength = namePrefix.length;
            var npRemaining = npLength - abbrPos;
            if(debugOutput === true) console.log(pass,' ',oslNameAbbreviations[loop],' ',abbrPos,' ',abbrLen,' ',npLength,' ',npRemaining);
            if(abbrPos != -1)
            {
               // make sure the road type we've found comes firstly at the end of the name string, or is suffixed with a space
               // if there's a non-road type at the end of the string (e.g. High Road Eastcote)
               // isn't, then we've actually found a type match within a longer string segment (e.g. The Parkside) and so we
               // should leave it alone...
               if
               (
                  ((pass === 0) && (npRemaining == abbrLen)) ||
                  ((pass == 1) && (namePrefix[abbrPos+abbrLen] == ' '))
               )
               {
                  var preName = namePrefix.substr(0,abbrPos);
                  if((preName.length >= 4) && (preName.lastIndexOf("The") != (preName.length - 4)))
                  {
                     var theName = namePrefix.substr(abbrPos);
                     theName = theName.replace(oslNameAbbreviations[loop],oslNameAbbreviations[loop+1]);
                     wazeName = preName + theName + dirSuffix;
                     return wazeName;
                  }
               }
            }
         }
      }
      wazeName = namePrefix + dirSuffix;
   }
   return wazeName;
}
function oslCPDistance(cpE, cpN, posE, posN)
{
   return Math.round(Math.sqrt(((posE - cpE) * (posE - cpE)) + ((posN - cpN) * (posN - cpN))));
}
function oslGetBBCornerPixels(boxW, boxE, boxS, boxN)
{
   var lonlat_sw = new OpenLayers.LonLat(boxW,boxS);
   var lonlat_se = new OpenLayers.LonLat(boxE,boxS);
   var lonlat_nw = new OpenLayers.LonLat(boxW,boxN);
   var lonlat_ne = new OpenLayers.LonLat(boxE,boxN);

   lonlat_sw.transform(new OpenLayers.Projection("EPSG:4326"),new OpenLayers.Projection("EPSG:900913"));
   lonlat_se.transform(new OpenLayers.Projection("EPSG:4326"),new OpenLayers.Projection("EPSG:900913"));
   lonlat_nw.transform(new OpenLayers.Projection("EPSG:4326"),new OpenLayers.Projection("EPSG:900913"));
   lonlat_ne.transform(new OpenLayers.Projection("EPSG:4326"),new OpenLayers.Projection("EPSG:900913"));

   var pix_sw = W.map.getPixelFromLonLat(lonlat_sw);
   var pix_se = W.map.getPixelFromLonLat(lonlat_se);
   var pix_ne = W.map.getPixelFromLonLat(lonlat_ne);
   var pix_nw = W.map.getPixelFromLonLat(lonlat_nw);

   boxE = (pix_ne.x + pix_se.x) / 2;
   boxW = (pix_nw.x + pix_sw.x) / 2;
   boxN = (pix_ne.y + pix_nw.y) / 2;
   boxS = (pix_se.y + pix_sw.y) / 2;

   var boxToleranceWidth = ((boxE - boxW) * 0.05);
   var boxToleranceHeight = ((boxS - boxN) * 0.05);

   boxW -= boxToleranceWidth;
   boxE += boxToleranceWidth;
   boxS += boxToleranceHeight;
   boxN -= boxToleranceHeight;

   boxE = Math.round(boxE);
   boxW = Math.round(boxW);
   boxS = Math.round(boxS);
   boxN = Math.round(boxN);

   // extend width/height of box if the calculated dimension is too small for the box to be readily visible
   if(boxE-boxW < 20)
   {
      boxE += 10;
      boxW -= 10;
   }
   if(boxS-boxN < 20)
   {
      boxS += 10;
      boxN -= 10;
   }

   return [boxW, boxE, boxS, boxN];
}
function oslVisualiseBoundingBox(boxW, boxE, boxS, boxN, mode)
{
   if(oslOSLDiv.style.height == '0px')
   {
      oslBBDiv.innerHTML = '';
      return;
   }

   var boxPos = [boxW, boxE, boxS, boxN];

   if((mode == 1) || (mode == 2))
   {
      boxPos = oslGetBBCornerPixels(boxW, boxE, boxS, boxN);
   }

   if(mode === 0)
   {
      oslBBDivInnerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="'+document.getElementById('WazeMap').offsetWidth+'px" height="'+document.getElementById('WazeMap').offsetHeight+'px" version="1.1">';
   }
   else if(mode == 1)
   {
      oslBBDivInnerHTML += '<rect x="'+boxPos[0]+'" y="'+boxPos[3]+'" width="'+(boxPos[1]-boxPos[0])+'" height="'+(boxPos[2]-boxPos[3])+'" style="fill:yellow;stroke:pink;stroke-width:4;fill-opacity:0.25;stroke-opacity:0.25"/>';
   }
   else if(mode == 2)
   {
      oslBBDivInnerHTML += '<rect x="'+boxPos[0]+'" y="'+boxPos[3]+'" width="'+(boxPos[1]-boxPos[0])+'" height="'+(boxPos[2]-boxPos[3])+'" style="fill:lightgrey;stroke:grey;stroke-width:4;fill-opacity:0.25;stroke-opacity:0.25"/>';
   }
   else if(mode == 3)
   {
      oslBBDivInnerHTML += '</svg>';
      oslBBDiv.innerHTML = oslBBDivInnerHTML;
   }
}
function oslMergeGazetteerData()
{
   if((typeof(gazetteerDataA) == "undefined") || (typeof(gazetteerDataB) == "undefined")) return false;
   if(oslMergeGazData)
   {
      oslGazetteerData = gazetteerDataA;
      oslGazetteerData = oslGazetteerData.concat(gazetteerDataB);
      oslMergeGazData = false;
      for(var idx=0;idx<oslGazetteerData.length;idx++)
      {
         oslGazetteerData[idx] = oslSaintsPreserveUs(oslGazetteerData[idx]);
      }
      oslAddLog('gazetteer data loaded, '+oslGazetteerData.length+' entries');
   }
   return true;
}
function oslHighlightOSCityName()
{
   var selectedIdx = document.getElementById('oslOSCityNames').selectedIndex;
}
function oslGetNearbyCityNames()
{
   if(oslMergeGazetteerData() === false) return;

   var names = [];
   for(var idx=0;idx<oslGazetteerData.length;idx++)
   {
      var gazElements = oslGazetteerData[idx].split(':');
      var cnEastings = gazElements[GAZ_ELM.CEast];
      var cnNorthings = gazElements[GAZ_ELM.CNorth];
      if((Math.abs(cnNorthings-oslNorthings) <= 5000)&&(Math.abs(cnEastings-oslEastings) <= 5000))
      {
         var dist = oslCPDistance(cnEastings,cnNorthings,oslEastings,oslNorthings);
         if(dist <= 5000)
         {
            names.push((dist * 1000000) + idx);
         }
      }
   }
   if(names.length > 1) names.sort(function(a,b){return a-b;});

   var cityInTopTen = false;
   var matchedOSName = false;
   var matchedIdx = -1;
   var listLength = names.length;
   if(listLength > 10) listLength = 10;

   var listOpt;
   var gElements;
   var gDist;

   var cityName;

   var oOCN = document.getElementById('oslOSCityNames');
   for(idx=0;idx<listLength;idx++)
   {
      gElements = oslGazetteerData[names[idx] % 1000000].split(':');
      gDist = (Math.round(names[idx] / 100000000)/10);

      // Build namestring for entry in the drop-down list - start with the placename :-)
      listOpt = document.createElement('option');
      cityName = gElements[GAZ_ELM.Name];
      listOpt.text = cityName;

      // if the name is neither a city nor unique, append a (county) suffix
      if(gElements[GAZ_ELM.Type] == 'C') cityInTopTen = true;
      else
      {
         if(oslCheckCityNameDuplicates(gElements[GAZ_ELM.Name],1) > 1)
         {
            listOpt.text += ', '+gElements[GAZ_ELM.Area];
         }
      }

      if(sessionStorage.cityNameRB == 'optUseOS')
      {
         if(cityName == sessionStorage.myCity)
         {
            matchedOSName = true;
            matchedIdx = idx;
         }
      }

      // Add place type and distance in [] brackets to allow easy removal later...
      if(gElements[GAZ_ELM.Type] == 'C') listOpt.text += ' [City, ';
      else if(gElements[GAZ_ELM.Type] == 'T') listOpt.text += ' [Town, ';
      else if(gElements[GAZ_ELM.Type] == 'V') listOpt.text += ' [Village, ';
      else if(gElements[GAZ_ELM.Type] == 'H') listOpt.text += ' [Hamlet, ';
      else listOpt.text += ' [Other, ';
      listOpt.text += gDist + 'km]';
      oOCN.add(listOpt,null);
   }

   if((!cityInTopTen) && (names.length > 10))
   {
      idx = 10;
      while((idx < names.length) && (!cityInTopTen))
      {
         gElements = oslGazetteerData[names[idx] % 1000000].split(':');
         if(gElements[GAZ_ELM.Type] == 'C')
         {
            cityInTopTen = true;
            gDist = ' [City, '+(Math.round(names[idx] / 100000000)/10)+'km]';
            listOpt = document.createElement('option');
            listOpt.text = gElements[GAZ_ELM.Name]+gDist;
            oOCN.add(listOpt,null);
            if(sessionStorage.cityNameRB == 'optUseOS')
            {
               if(gElements[GAZ_ELM.Name] == sessionStorage.myCity)
               {
                  matchedOSName = true;
                  matchedIdx = 10;
                  break;
               }
            }
         }
         idx++;
      }
   }

   //oOCN.addEventListener("input", oslHighlightOSCityName, false);

   if(matchedOSName === true) oOCN.options.selectedIndex = matchedIdx;

   if((sessionStorage.cityNameRB == 'optUseOS') && (matchedOSName === false))
   {
      oslAddLog('Selected city name no longer in nearby OS list...');
      alert('City name no longer present in nearby OS data, please reselect');
      sessionStorage.cityNameRB = 'optUseExisting';
      document.getElementById('optUseExisting').checked = true;
   }
}
function oslCheckCityNameDuplicates(cityName, mode)
{
   if(oslMergeGazetteerData() === false) return;

   var cnCount = 0;
   var searchDist = Math.round(oslGazetteerData.length/2);
   var searchIdx = searchDist;
   var hasCounty = false;

   var debugOutput = false;


   // remove county suffix from actual city name string if present
   if(cityName.indexOf('(') != -1)
   {
      cityName = cityName.substr(0,cityName.indexOf('('));
      cityName = cityName.replace(/^\s+|\s+$/g, "");
      hasCounty = true;
   }
   // remove script-appended county suffix from city name held in drop down if present
   if(cityName.indexOf(',') != -1)
   {
      cityName = cityName.substr(0,cityName.indexOf(','));
      cityName = cityName.replace(/^\s+|\s+$/g, "");
   }

   cityName = cityName.toLowerCase();
   cityName = cityName.replace(/-/g, ' ');
   var gazName = '';

   if(debugOutput === true) console.log('scan for duplicates of '+cityName);

   var gazElements = [];
   while((searchDist > 1) && (cityName.localeCompare(gazName) !== 0))
   {
      searchDist = Math.round(searchDist/2);
      gazElements = oslGazetteerData[searchIdx].split(':');
      gazName = gazElements[GAZ_ELM.Name].toLowerCase();
      gazName = gazName.replace(/-/g, ' ');
      if(debugOutput === true) console.log('a: '+searchDist+' '+searchIdx+' '+gazName);
      if(cityName.localeCompare(gazName) > 0) searchIdx += searchDist;
      else if(cityName.localeCompare(gazName) < 0) searchIdx -= searchDist;
      if(searchIdx >= oslGazetteerData.length) searchIdx = oslGazetteerData.length-1;
      if(searchIdx < 0) searchIdx = 0;
   }
   gazElements = oslGazetteerData[searchIdx].split(':');
   gazName = gazElements[GAZ_ELM.Name].toLowerCase();
   gazName = gazName.replace(/-/g, ' ');
   while((searchIdx > 0) && (cityName.localeCompare(gazName) <= 0))
   {
      gazElements = oslGazetteerData[--searchIdx].split(':');
      gazName = gazElements[GAZ_ELM.Name].toLowerCase();
      gazName = gazName.replace(/-/g, ' ');
      if(debugOutput === true) console.log('b: '+(searchIdx)+' '+gazName);
   }
   gazElements = oslGazetteerData[searchIdx].split(':');
   gazName = gazElements[GAZ_ELM.Name].toLowerCase();
   gazName = gazName.replace(/-/g, ' ');
   while((searchIdx < oslGazetteerData.length) && (cityName.localeCompare(gazName) > 0))
   {
      gazElements = oslGazetteerData[++searchIdx].split(':');
      gazName = gazElements[GAZ_ELM.Name].toLowerCase();
      gazName = gazName.replace(/-/g, ' ');
      if(debugOutput === true) console.log('c: '+(searchIdx)+' '+gazName);
   }
   while((cityName.localeCompare(gazName) === 0) && (searchIdx < oslGazetteerData.length))
   {
      cnCount++;
      gazElements = oslGazetteerData[++searchIdx].split(':');
      gazName = gazElements[GAZ_ELM.Name].toLowerCase();
      gazName = gazName.replace(/-/g, ' ');
      if(debugOutput === true) console.log('d: '+(searchIdx)+' '+gazName+' '+cnCount);
   }

   if(mode === 0)
   {
      var newHTML = '';
      if(cnCount === 0) newHTML = '&nbsp;&nbsp;Place name is not in OS data';
      else if(cnCount == 1)
      {
         newHTML = '&nbsp;&nbsp;Place name is unique';
         if(hasCounty) newHTML += '<br>&nbsp;&nbsp;<i>(County) suffix not required</i>';
      }
      else
      {
         newHTML = '&nbsp;&nbsp;Place name is not unique';
      }
      document.getElementById('oslCNInfo').innerHTML = newHTML;
   }
   else return cnCount;
}
function oslHighlightAdjacentSameNameSegments(ldEastings, ldNorthings, ldIgnoreIdx, srcElements)
{
   ldNorthings -= oslLocatorBlockSize;
   ldEastings -= oslLocatorBlockSize;
   for(var x = 0; x < 3; ++x)
   {
      for(var y = 0; y < 3; ++y)
      {
         var arrayName = 'locatorData_'+(ldEastings + (x * oslLocatorBlockSize))+'_'+(ldNorthings + (y * oslLocatorBlockSize));
         oslEvalString = 'typeof '+arrayName;
         if(eval(oslEvalString) != "undefined")
         {
            oslEvalString = 'oslBlockData = '+arrayName;
            eval(oslEvalString);
            for(var loop = 0; loop < oslBlockData.length; ++loop)
            {
               if(loop != ldIgnoreIdx)
               {
                  var locatorElements = oslBlockData[loop].split(':');
                  if
                  (
                     (locatorElements[OSL_ELM.RoadName] == srcElements[OSL_ELM.RoadName]) &&
                     (locatorElements[OSL_ELM.RoadNumber] == srcElements[OSL_ELM.RoadNumber]) &&
                     (locatorElements[OSL_ELM.AreaName] == srcElements[OSL_ELM.AreaName])
                  )
                  {
                     oslVisualiseBoundingBox(locatorElements[OSL_ELM.BoundW],locatorElements[OSL_ELM.BoundE],locatorElements[OSL_ELM.BoundS],locatorElements[OSL_ELM.BoundN],2);
                  }
               }
            }
         }
      }
   }
}
function oslRadioClick()
{
   var oslElements = document.getElementById('oslRoadNameMatches');
   var selectedName = '';
   var loop;
   var roadData;
   var locatorElements;
   var tagname;
   var attr;
   var oslID;
   var evalstr;

   for(loop=0;loop<oslElements.childNodes.length;loop++)
   {
      if(oslElements.childNodes[loop].nodeType == 1)
      {
         tagname = oslElements.childNodes[loop].tagName;
         if(tagname !== null)
         {
            if(tagname == "LABEL")
            {
               if(oslElements.childNodes[loop].childNodes[0].checked)
               {
                  attr = oslElements.childNodes[loop].childNodes[0].attributes.getNamedItem("id").value;
                  if((attr.indexOf('oslID_') === 0) || (attr.indexOf('alt-oslID_') === 0))
                  {
                     roadData = '';
                     oslID = attr.split('_');
                     if(oslID[1] != 'null')
                     {
                        evalstr = 'var roadData = locatorData_'+oslID[1]+'_'+oslID[2]+'['+oslID[3]+']';
                        eval(evalstr);
                     }
                     else
                     {
                        roadData = "null:null";
                     }
                     locatorElements = roadData.split(":");
                     if(locatorElements[OSL_ELM.RoadName] != 'null')
                     {
                        selectedName = locatorElements[OSL_ELM.RoadName]+locatorElements[OSL_ELM.RoadNumber];
                        oslVisualiseBoundingBox(0,0,0,0,0);
                        oslVisualiseBoundingBox(locatorElements[OSL_ELM.BoundW],locatorElements[OSL_ELM.BoundE],locatorElements[OSL_ELM.BoundS],locatorElements[OSL_ELM.BoundN],1);

                        oslHighlightAdjacentSameNameSegments(oslID[1], oslID[2], oslID[3], locatorElements);
                     }
                     else
                     {
                        oslBBDiv.innerHTML = '';
                     }
                  }
               }
            }
         }
      }
   }

   if(selectedName === '')
   {
      oslBBDiv.innerHTML = '';
      return;
   }
   oslVisualiseBoundingBox(0,0,0,0,3);
}
function oslClick()
{
   oslCityName = '';
   oslCountyName = '';
   oslUsingNewName = false;
   if(document.getElementById('optUseNewManual').checked)
   {
      oslCityName = oslCaseCorrect(document.getElementById('myCityName').value);
      oslUsingNewName = true;
   }
   else if(document.getElementById('optUseExistingWME').checked)
   {
      var oWCN = document.getElementById('oslWMECityNames');
      oslCityName = oWCN.options[oWCN.options.selectedIndex].text;
      if(oslCityName.indexOf(', ') !== -1)
      {
         oslCountyName = oslCityName.split(', ')[1];
         oslCityName = oslCityName.split(', ')[0];
      }
      oslUsingNewName = true;
   }
   else if(document.getElementById('optUseOS').checked)
   {
      var oOCN = document.getElementById('oslOSCityNames');
      oslCityName = oOCN.options[oOCN.options.selectedIndex].text;
      oslCityName = oslCityName.substring(0,oslCityName.indexOf('[')-1);
      if(oslCityName.indexOf(', ') !== -1)
      {
         oslCountyName = oslCityName.split(', ')[1];
         oslCityName = oslCityName.split(', ')[0];
      }
      oslUsingNewName = true;
   }
   if(sessionStorage.myCity === '')
   {
      oslAddLog('Update city name position at '+oslEastings+'x'+oslNorthings);
      sessionStorage.cityChangeEastings = oslEastings;
      sessionStorage.cityChangeNorthings = oslNorthings;
   }

   if(oslCountyName !== '')
   {
      sessionStorage.myCity = oslCityName+', '+oslCountyName;
   }
   else
   {
      sessionStorage.myCity = oslCityName;
   }

   oslUseName = false;
   if((oslCityName.length > 0) && oslUsingNewName)
   {
      oslCheckCityNameDuplicates(oslCityName,0);

      if(oslCityName != sessionStorage.prevCityName)
      {
         oslAddLog('Change of city name at '+oslEastings+'x'+oslNorthings);
         sessionStorage.cityChangeEastings = oslEastings;
         sessionStorage.cityChangeNorthings = oslNorthings;
         sessionStorage.prevCityName = oslCityName;
         oslUseName = true;
      }
      else
      {
         var nameChangeDist = oslCPDistance(oslEastings,oslNorthings,sessionStorage.cityChangeEastings,sessionStorage.cityChangeNorthings);
         oslAddLog('Current name was set '+nameChangeDist+'m away from segment location');
         if(nameChangeDist > 1000)
         {
            oslAddLog('Distance exceeds 1km threshold, name verification required...');
            if(confirm('Confirm continued use of this city name'))
            {
               oslAddLog('Confirm city name at '+oslEastings+'x'+oslNorthings);
               sessionStorage.cityChangeEastings = oslEastings;
               sessionStorage.cityChangeNorthings = oslNorthings;
               oslUseName = true;
            }
         }
         else
         {
            oslUseName = true;
         }
      }
   }

   var oslElements = document.getElementById('oslRoadNameMatches');
   for(var loop=0;loop<oslElements.childNodes.length;loop++)
   {
      if(oslElements.childNodes[loop].nodeType == 1)
      {
         var tagname = oslElements.childNodes[loop].tagName;
         if(tagname !== null)
         {
            if(tagname == "LABEL")
            {
               var rbElement = oslElements.childNodes[loop].childNodes[0];
               if((rbElement.name == "oslChoice") && (rbElement.checked))
               {
                  var attr = rbElement.attributes.getNamedItem("id").value;
                  var useMain = (attr.indexOf('oslID_') === 0);
                  oslUseAlt = (attr.indexOf('alt-oslID_') === 0);
                  if((useMain === true) || (oslUseAlt === true))
                  {
                     var roadData = '';
                     var oslID = attr.split('_');
                     if(oslID[1] != 'null')
                     {
                        var evalstr = 'var roadData = locatorData_'+oslID[1]+'_'+oslID[2]+'['+oslID[3]+']';
                        eval(evalstr);
                     }
                     else
                     {
                        for(var i = 0; i < OSL_ELM.MAX; ++i)
                        {
                           roadData += ':';
                        }
                     }
                     oslLocatorElements = roadData.split(":");

                     // auto-click the Edit Address link...
                     document.getElementsByClassName('full-address')[0].click();
                     oslAltNamesClearing = false;
                     oslWaitAddressElementOpen();
                  }
               }
            }
         }
      }
   }
}
function oslWaitAddressElementOpen()
{
   // clear any altnames that may be present...
   var altNames = document.getElementsByClassName('alt-street-delete').length;
   if(altNames > 0)
   {
      if(oslAltNamesClearing == false)
      {
         if(document.getElementsByClassName('alt-street-delete')[0].offsetHeight == 0)
         {
            window.setTimeout(oslWaitAddressElementOpen, 500);
            return;
         }
      }

      oslAltNamesClearing = true;
      for(var j = 0; j < altNames; ++j)
      {
         document.getElementsByClassName('alt-street-delete')[j].click();
         if(document.getElementsByClassName('alt-street-delete')[j].offsetHeight > 0)
         {
            window.setTimeout(oslWaitAddressElementOpen, 500);
            return;
         }
      }
   }

   // make sure the Country field is set to "United Kingdom"
   var snelm = document.getElementsByClassName('country-id')[0];
   if(snelm.value != 234) snelm.value = 234;
   snelm.dispatchEvent(new Event('change', { 'bubbles': true }));

   // fill in the City field
   snelm = document.getElementsByClassName('city-name')[0];
   if(oslUsingNewName)
   {
      if(document.getElementById('optUseNewManual').checked === true)
      {
         sessionStorage.cityNameRB = 'optUseNewManual';
      }
      else if(document.getElementById('optUseExistingWME').checked === true)
      {
         sessionStorage.cityNameRB = 'optUseExistingWME';
      }
      else if(document.getElementById('optUseOS').checked === true)
      {
         sessionStorage.cityNameRB = 'optUseOS';
      }
      if(oslUseName)
      {
         snelm.value = oslCityName;
         snelm.disabled = false;
         snelm.dispatchEvent(new Event('change', { 'bubbles': true }));
         snelm = document.getElementsByClassName('empty-city')[0];
         snelm.checked = false;
         snelm.dispatchEvent(new Event('change', { 'bubbles': true }));

         if(oslCountyName !== '')
         {
            // set the county field if one was provided with the selected city name
            var countyIdx;
            for(countyIdx = 0; countyIdx < document.getElementsByClassName('state-id')[0].options.length; countyIdx++)
            {
               if(document.getElementsByClassName('state-id')[0].options[countyIdx].innerHTML == oslCountyName)
               {
                  document.getElementsByClassName('state-id')[0].options.selectedIndex = countyIdx;
                  document.getElementsByClassName('state-id')[0].dispatchEvent(new Event('change', { 'bubbles': true }));
                  break;
               }
            }
         }
      }
   }
   else if(document.getElementById('optClearExisting').checked === true)
   {
      sessionStorage.cityNameRB = 'optClearExisting';
      snelm.disabled = true;
      snelm.dispatchEvent(new Event('change', { 'bubbles': true }));
      snelm = document.getElementsByClassName('empty-city')[0];
      snelm.checked = true;
      snelm.dispatchEvent(new Event('change', { 'bubbles': true }));
   }
   else
   {
      sessionStorage.cityNameRB = 'optUseExisting';
   }

   if(oslUseAlt === true)
   {
      var tName = oslLocatorElements[OSL_ELM.RoadName];
      oslLocatorElements[OSL_ELM.RoadName] = oslLocatorElements[OSL_ELM.AltName];
      oslLocatorElements[OSL_ELM.AltName] = tName;
   }

   // finally the Street field
   var oslName = oslLocatorElements[OSL_ELM.RoadNumber];
   if((oslLocatorElements[OSL_ELM.RoadName].length > 0)&&(oslLocatorElements[OSL_ELM.RoadNumber].length > 0)) oslName += ' - ';
   var altName = oslName;   
   if((oslName.length > 0)||(oslLocatorElements[OSL_ELM.RoadName].length > 0))
   {
      oslName += oslWazeifyStreetName(oslLocatorElements[OSL_ELM.RoadName], false);
      oslPrevStreetName = oslName;
      snelm = document.getElementsByClassName('street-name')[0];
      snelm.value = oslName;
      snelm.disabled = false;
      snelm.dispatchEvent(new Event('change', { 'bubbles': true }));
      snelm = document.getElementsByClassName('empty-street')[0];
      snelm.checked = false;
      snelm.dispatchEvent(new Event('change', { 'bubbles': true }));
   }
   else
   {
      snelm = document.getElementsByClassName('street-name')[0];
      snelm.value = '';
      snelm.disabled = true;
      snelm.dispatchEvent(new Event('change', { 'bubbles': true }));
      snelm = document.getElementsByClassName('empty-street')[0];
      snelm.checked = true;
      snelm.dispatchEvent(new Event('change', { 'bubbles': true }));
   }

   if(oslLocatorElements[OSL_ELM.AltName] != '')
   {
      altName += oslWazeifyStreetName(oslLocatorElements[OSL_ELM.AltName], false);
      document.getElementsByClassName('add-alt-street-btn')[0].click();
      snelm = document.getElementsByClassName('alt-street-name')[0];
      snelm.value = altName;
      snelm.disabled = false;
      snelm.dispatchEvent(new Event('change', { 'bubbles': true}));

      if(document.getElementById('optUseExisting').checked === true)
      {
         if(document.getElementsByClassName('empty-city')[0].checked === true)
         {
            snelm = document.getElementsByClassName('alt-address empty-city')[0];
            snelm.checked = true;
            snelm.dispatchEvent(new Event('change', { 'bubbles': true }));
         }
         else
         {
            oslUsingNewName = true;
            oslUseName = true;   
            oslCityName = document.getElementsByClassName("city-name form-control")[0].value;
         }
      }

      snelm = document.getElementsByClassName('alt-city-name')[0];
      if(oslUsingNewName)
      {
         if(oslUseName)
         {
            snelm.value = oslCityName;
            snelm.disabled = false;
            snelm.dispatchEvent(new Event('change', { 'bubbles': true }));
            snelm = document.getElementsByClassName('alt-address empty-city')[0];
            snelm.checked = false;
            snelm.dispatchEvent(new Event('change', { 'bubbles': true }));
         }
      }
      else if(document.getElementById('optClearExisting').checked === true)
      {
         snelm.disabled = true;
         snelm.dispatchEvent(new Event('change', { 'bubbles': true }));
         snelm = document.getElementsByClassName('alt-address empty-city')[0];
         snelm.checked = true;
         snelm.dispatchEvent(new Event('change', { 'bubbles': true }));
      }
   }

   if(oslAdvancedMode)
   {
      // auto-click the Apply button...
      document.getElementsByClassName('address-form')[0].getElementsByClassName('save-button')[0].click();
   }
}
function oslMatch(oslLink, oslArea, oslRadioID, oslAltRadioID)
{
   this.oslLink = oslLink;
   this.oslArea = oslArea;
   this.oslRadioID = oslRadioID;
   this.oslAltRadioID = oslAltRadioID;
}
function oslSortCandidates(a,b)
{
   var x = a.oslArea;
   var y = b.oslArea;
   return((x<y) ? -1 : ((x>y) ? 1 : 0));
}
function oslCityNameKeyup()
{
   oslCheckCityNameDuplicates(oslCaseCorrect(document.getElementById('myCityName').value),0);
   document.getElementById('optUseNewManual').checked = true;
}
function oslSelectWMEName()
{
   var oWCN = document.getElementById('oslWMECityNames');
   var cityName = oWCN.options[oWCN.options.selectedIndex].text;
   oslCheckCityNameDuplicates(cityName,0);
   document.getElementById('optUseExistingWME').checked = true;
}
function oslSelectOSName()
{
   var oOCN = document.getElementById('oslOSCityNames');
   var cityName = oOCN.options[oOCN.options.selectedIndex].text;
   cityName = cityName.substring(0,cityName.indexOf('[')-1);
   document.getElementById('optUseOS').checked = true;
   document.getElementById('oslCNInfo').innerHTML = '';
}
function oslBlockCacheObj(blockName)
{
   this.blockName = blockName;
   this.lastAccessed = Math.floor(new Date().getTime() / 1000);
}
function oslLoadBlocks()
{
   for(var i = 0; i < oslBlocksToLoad.length;)
   {
      // inject block data
      var script = document.createElement("script");
      script.setAttribute('type','text/javascript');
      script.setAttribute('charset','UTF-8');
      script.src = oslBlocksToLoad[i];
      document.head.appendChild(script);
      oslLoadingMsg = true;
      oslBlockCacheList.push(new oslBlockCacheObj(oslBlocksToLoad[i+1]));
      i += 2;
   }
}
function oslToOSGrid(lat, lon, mode)
{
   if(oslInUK === false) return;

   var loop;
   var i;
   var x;
   var y;
   var eBlock_point;
   var nBlock_point;
   var eBlock;
   var nBlock;

   if((lat !== 0) && (mode != OSL_MODE.OpenRoads))
   {
      if(mode == OSL_MODE.NameCheck)
      {
         oslEastings = lat;
         oslNorthings = lon;
      }
      else
      {
         oslLatitude = lat;
         oslLongitude = lon;
         oslWGStoOSGB();
      }
   }

   if((mode == OSL_MODE.OpenNames) || (mode == OSL_MODE.NameCheck))
   {
      // determine which grid block contains the current mouse position
      eBlock_point = (Math.floor(oslEastings/oslLocatorBlockSize)) * oslLocatorBlockSize;
      nBlock_point = (Math.floor(oslNorthings/oslLocatorBlockSize)) * oslLocatorBlockSize;

      var candidates = [];
      oslBlocksToLoad = [];
      var arrayName;
      var oslEvalString;

      for(x = -1; x < 2; x++)
      {
         for(y = -1; y < 2; y++)
         {
            eBlock = (eBlock_point + (oslLocatorBlockSize * x));
            nBlock = (nBlock_point + (oslLocatorBlockSize * y));
            // check we're within the outer bounds of the current OS dataset...
            if
               (
                  (eBlock >= 64000) &&
                  (eBlock <= 655999) &&
                  (nBlock >= 8000) &&
                  (nBlock <= 1214999)
               )
            {
               arrayName = 'locatorData_'+eBlock+'_'+nBlock;

               // check to see if there's a corresponding array already loaded...
               oslEvalString = 'typeof '+arrayName;

               if(eval(oslEvalString) == "undefined")
               {
                  // create a blank placeholder which will get replaced by the actual data if the array is present on the server...
                  eval(arrayName+' = []');
                  oslBlocksToLoad.push(oslBlockPath+Math.floor(eBlock / 100000)+'/'+Math.floor(nBlock / 100000)+'/'+arrayName+'.user.js');
                  oslBlocksToLoad.push(arrayName);
               }
            }
         }
      }

      if(oslBlocksToLoad.length > 0)
      {
         oslLoadBlocks();
      }

      candidates = [];
      if(mode == OSL_MODE.OpenNames) candidates[candidates.length++] = new oslMatch('<label style="display:inline;"><input type="radio" name="oslChoice" id="oslID_null_null_null" />Un-named segment</label><br>',1000000000000,'oslID_null_null_null',null);

      for(x = -1; x < 2; x++)
      {
         for(y = -1; y < 2; y++)
         {
            eBlock = (eBlock_point + (oslLocatorBlockSize * x));
            nBlock = (nBlock_point + (oslLocatorBlockSize * y));

            // check we're within the outer bounds of the current OS dataset...
            if ((eBlock >= 64000) && (eBlock <= 655999) && (nBlock >= 8000) && (nBlock <= 1214999))
            {
               arrayName = 'locatorData_'+eBlock+'_'+nBlock;
               oslEvalString = 'typeof '+arrayName;
               if(eval(oslEvalString) != "undefined")
               {
                  oslLoadingMsg = false;
                  // yes...  make a local copy to avoid having an eval() in each iteration of the loop
                  if((eBlock != oslEvalEBlock) || (nBlock != oslEvalNBlock))
                  {
                     oslEvalEBlock = eBlock;
                     oslEvalNBlock = nBlock;
                     var evalstr = 'oslBlockData = locatorData_'+eBlock+'_'+nBlock;
                     eval(evalstr);
                  }

                  for (var bcObj in oslBlockCacheList)
                  {
                     if(oslBlockCacheList[bcObj].blockName == arrayName)
                     {
                        oslBlockCacheList[bcObj].lastAccessed = Math.floor(new Date().getTime() / 1000);
                     }
                  }

                  var bdstr = '';
                  var preselect = false;

                  for(loop = 0;loop < oslBlockData.length; loop++)
                  {
                     var locatorElements = [];
                     // for each entry in the array, test the centrepoint position to see if it lies within the bounding box for that entry
                     // note that we allow a 30m tolerance on all sides of the box to allow for inaccuracies in the latlon->gridref conversion,
                     // and to increase the chance of a successful match when the road runs E-W or N-S and thus has a long but narrow bounding box

                     // the following block of code more or less replicates the string.split() method, but is around 2x faster in the Chrome
                     // JS engine...
                     if(navigator.userAgent.indexOf("Chrome") != -1)
                     {
                        bdstr = oslBlockData[loop];
                        while(bdstr.indexOf(':') != -1)
                        {
                           locatorElements.push(bdstr.substr(0,bdstr.indexOf(':')));
                           bdstr = bdstr.substr(bdstr.indexOf(':')+1);
                        }
                        locatorElements.push(bdstr);
                     }
                     else
                     {
                        locatorElements = oslBlockData[loop].split(':');
                     }

                     if((locatorElements[OSL_ELM.RoadName].length > 0) || (locatorElements[OSL_ELM.RoadNumber].length > 0))
                     {
                        var tolE;
                        var tolN;
                        if(mode == OSL_MODE.NameCheck)
                        {
                           // wider tolerance when doing a namecheck lookup, to reduce falsely flagging roads as being mis-named
                           tolE = 20;
                           tolN = 30;
                        }
                        else
                        {
                           // tighter tolerance when doing other lookups
                           tolE = 10;
                           tolN = 10;
                        }

                        var streetName = '';
                        if(locatorElements[OSL_ELM.RoadNumber].length > 0)
                        {
                           streetName += locatorElements[OSL_ELM.RoadNumber];
                           if(locatorElements[OSL_ELM.RoadName].length > 0)
                           {
                              streetName += ' - ';
                           }
                        }
                        streetName += oslWazeifyStreetName(locatorElements[OSL_ELM.RoadName], false);

                        var altName = '';
                        if(locatorElements[OSL_ELM.AltName] != '')
                        {
                           if(locatorElements[OSL_ELM.RoadNumber].length > 0)
                           {
                              altName += locatorElements[OSL_ELM.RoadNumber];
                              if(locatorElements[OSL_ELM.AltName].length > 0)
                              {
                                 altName += ' - ';
                              }
                           }
                           altName += oslWazeifyStreetName(locatorElements[OSL_ELM.AltName], false);
                        }

                        if(mode == OSL_MODE.OpenNames)
                        {
                           // Name search from mouse position...
                           var bbPix = oslGetBBCornerPixels(parseFloat(locatorElements[OSL_ELM.BoundW]), parseFloat(locatorElements[OSL_ELM.BoundE]), parseFloat(locatorElements[OSL_ELM.BoundS]), parseFloat(locatorElements[OSL_ELM.BoundN]));
                           bbPix[0] -= tolE;
                           bbPix[1] += tolE;
                           bbPix[2] += tolN;
                           bbPix[3] -= tolN;

                           if((oslMousePixelpos.x >= bbPix[0])&&(oslMousePixelpos.x <= bbPix[1])&&(oslMousePixelpos.y <= bbPix[2])&&(oslMousePixelpos.y >= bbPix[3]))
                           {
                              var area = ((bbPix[1] - bbPix[0]) * (bbPix[2] - bbPix[3]));

                              var radioID = 'oslID_'+eBlock+'_'+nBlock+'_'+loop;
                              var altRadioID = null;
                              var oslLink = '<label style="display:inline;"><input type="radio" name="oslChoice" id="'+radioID+'"';
                              if((streetName == oslPrevStreetName)&&(preselect === false))
                              {
                                 oslLink += 'checked="true"';
                                 preselect = true;
                              }
                              oslLink += '/>';
                              oslLink += streetName+'&nbsp;&nbsp;[<i>'+locatorElements[OSL_ELM.AreaName]+'</i>]</label>';

                              if(altName != '')
                              {
                                 altRadioID = 'alt-'+radioID;
                                 oslLink += '<br>&nbsp;<label style="display:inline;"><input type="radio" name="oslChoice" id="'+altRadioID+'"';
                                 if((altName == oslPrevStreetName)&&(preselect === false))
                                 {
                                    oslLink += 'checked="true"';
                                    preselect = true;
                                 }
                                 oslLink += '/>';
                                 oslLink += '<i>Alt name: '+altName+'</i></label>';
                              }

                              if(locatorElements[OSL_ELM.Form] == 'Collapsed Dual Carriageway')
                              {
                                 locatorElements[OSL_ELM.Form] = 'Dual Carriageway';
                              }

                              oslLink += '<br>&nbsp;<i>'+oslRoadClassifications[locatorElements[OSL_ELM.Classification]] + ' - ';
                              oslLink += oslRoadFunctions[locatorElements[OSL_ELM.Function]] + ' - ' + oslFormsOfWay[locatorElements[OSL_ELM.Form]];
                              if(locatorElements[OSL_ELM.Structure] == OSL_ROADSTRUCTURES.Road_In_Tunnel)
                              {
                                 oslLink += ' [Tunnel]';
                              }
                              else if(locatorElements[OSL_ELM.Structure] == OSL_ROADSTRUCTURES.Road_On_Bridge)
                              {
                                 oslLink += ' [Bridge]';
                              }
                              if(locatorElements[OSL_ELM.IsPrimary] == 'Y')
                              {
                                 oslLink += ' (PRN)';
                              }
                              if(locatorElements[OSL_ELM.IsTrunk] == 'Y')
                              {
                                 oslLink += ' (TRN)';
                              }
                              oslLink += '</i><br>';
                              candidates[candidates.length++] = new oslMatch(oslLink,area,radioID,altRadioID);
                           }
                        }
                        else if(mode == OSL_MODE.NameCheck)
                        {
                           // NameCheck comparisons...
                           var bbW = parseFloat(locatorElements[OSL_ELM.BoundW]) - tolE;
                           var bbE = parseFloat(locatorElements[OSL_ELM.BoundE]) + tolE;
                           var bbS = parseFloat(locatorElements[OSL_ELM.BoundS]) - tolN;
                           var bbN = parseFloat(locatorElements[OSL_ELM.BoundN]) + tolN;

                           for(i=0;i<oslOSLNCSegments.length;i++)
                           {
                              if(oslOSLNCSegments[i].match === false)
                              {
                                 if
                                    (
                                       ((oslOSLNCSegments[i].lonA >= bbW) && (oslOSLNCSegments[i].lonA <= bbE)) &&
                                       ((oslOSLNCSegments[i].lonB >= bbW) && (oslOSLNCSegments[i].lonB <= bbE)) &&
                                       ((oslOSLNCSegments[i].latA >= bbS) && (oslOSLNCSegments[i].latA <= bbN)) &&
                                       ((oslOSLNCSegments[i].latB >= bbS) && (oslOSLNCSegments[i].latB <= bbN))
                                    )
                                 {
                                    if((oslOSLNCSegments[i].streetname == streetName) || (oslOSLNCSegments[i].streetname == altName)) oslOSLNCSegments[i].match = true;
                                 }
                              }
                           }
                        }
                     }
                  }
               }
            }
         }
      }

      if(mode == OSL_MODE.OpenNames)
      {
         var newHTML = '<b>Matches at '+oslEastings+', '+oslNorthings+'</b>';

         if(candidates.length > 0)
         {
            var btnStyle = "cursor:pointer;";
            btnStyle += "font-size:14px;";
            btnStyle += "border: thin outset black;";
            btnStyle += "padding:2px 10px 2px 10px;";
            btnStyle += "background: #ccccff;";
            newHTML += '<div style="margin:10px;"><span id="oslSelect" style="'+btnStyle+'">';
            if(oslAdvancedMode) newHTML += 'Apply to Properties';
            else newHTML += 'Copy to Properties';
            newHTML += '</span></div>';
            if(candidates.length > 1) candidates.sort(oslSortCandidates);
            for(loop=0;loop<candidates.length;loop++)
            {
               newHTML += candidates[loop].oslLink;
            }
            newHTML += '<br>City name:<br>';
            newHTML += '<label style="display:inline;"><input type="radio" name="oslCityNameOpt" id="optUseExisting"/>Use existing segment name(s)</label><br>';
            newHTML += '<label style="display:inline;"><input type="radio" name="oslCityNameOpt" id="optClearExisting" />Clear existing segment name(s)</label><br>';
            newHTML += '<label style="display:inline;"><input type="radio" name="oslCityNameOpt" id="optUseNewManual" />Use new name:</label><br>';
            newHTML += '&nbsp;&nbsp;<input id="myCityName" style="font-size:14px; line-height:16px; height:22px; margin-bottom:4px; transition:none; focus:none; box-shadow:none" type="text"';
            if(sessionStorage.cityNameRB == 'optUseNewManual') newHTML += 'value="'+sessionStorage.myCity+'"/><br>';
            else newHTML += 'value=""/><br>';
            newHTML += '<label style="display:inline;"><input type="radio" name="oslCityNameOpt" id="optUseExistingWME" />Use name from map:</label><br>';
            newHTML += '&nbsp;&nbsp;<select id="oslWMECityNames"></select><br>';
            newHTML += '<label style="display:inline;"><input type="radio" name="oslCityNameOpt" id="optUseOS" />Use name from OS Gazetteer:</label><br>';
            newHTML += '&nbsp;&nbsp;<select id="oslOSCityNames"></select><br>';
            newHTML += '<div id="oslCNInfo"></div><br>';
            oslRoadNameMatches.innerHTML = newHTML;
            oslGetNearbyCityNames();
            var oWCN = document.getElementById('oslWMECityNames');
            var nameList = [];
            for(var cityObj in W.model.cities.objects)
            {
               if(W.model.cities.objects.hasOwnProperty(cityObj))
               {
                  var cityname = W.model.cities.objects[cityObj].attributes.name;
                  if(cityname !== '')
                  {
                     var countyID = [];
                     countyID[0] = W.model.cities.objects[cityObj].attributes.stateID;
                     var countyName = W.model.states.getByIds(countyID)[0].name;
                     if(countyName !== '')
                     {
                        cityname += ', '+countyName;
                     }
                     nameList.push(cityname);
                  }
               }
            }
            nameList.sort();
            var matchedWMEName = false;
            for(i=0; i<nameList.length; i++)
            {
               var listOpt = document.createElement('option');
               listOpt.text = nameList[i];
               oWCN.add(listOpt,null);
               if(sessionStorage.cityNameRB == 'optUseExistingWME')
               {
                  if(nameList[i] == sessionStorage.myCity)
                  {
                     oWCN.options.selectedIndex = i;
                     matchedWMEName = true;
                  }
               }
            }
            if((!matchedWMEName) && (sessionStorage.cityNameRB == 'optUseExistingWME'))
            {
               oWCN.options.selectedIndex = 0;
            }
            document.getElementById('oslSelect').addEventListener("click", oslClick, true);
            document.getElementById('oslWMECityNames').addEventListener("click", oslSelectWMEName, true);
            document.getElementById('optUseExistingWME').addEventListener("click", oslSelectWMEName, true);
            document.getElementById('oslOSCityNames').addEventListener("click", oslSelectOSName, true);
            document.getElementById('optUseOS').addEventListener("click", oslSelectOSName, true);
            document.getElementById('myCityName').addEventListener("keyup", oslCityNameKeyup, true);
            document.getElementById('optUseNewManual').addEventListener("click", oslCityNameKeyup, true);
            for(loop=0;loop<candidates.length;loop++)
            {
               document.getElementById(candidates[loop].oslRadioID).addEventListener("click", oslRadioClick, true);
               if(candidates[loop].oslAltRadioID != null)
               {
                  document.getElementById(candidates[loop].oslAltRadioID).addEventListener("click", oslRadioClick, true);
               }
            }

            document.getElementById(sessionStorage.cityNameRB).checked = true;
            oslKeepUIVisible();
         }
         else oslRoadNameMatches.innerHTML = newHTML;
      }
   }
   if(mode == OSL_MODE.OpenRoads)
   {
      // OpenRoads check
      var eastingsLeft = (Math.floor(oslVPLeft/oslLocatorBlockSize) - 1) * oslLocatorBlockSize;
      var eastingsRight = (Math.ceil(oslVPRight/oslLocatorBlockSize) + 1) * oslLocatorBlockSize;
      var northingsTop = (Math.ceil(oslVPTop/oslLocatorBlockSize) + 1) * oslLocatorBlockSize;
      var northingsBottom = (Math.floor(oslVPBottom/oslLocatorBlockSize) - 1) * oslLocatorBlockSize;

      var highlightMode = 0;

      for(var eLoop = eastingsLeft; eLoop <= eastingsRight; eLoop += oslLocatorBlockSize)
      {
         for(var nLoop = northingsBottom; nLoop <= northingsTop; nLoop += oslLocatorBlockSize)
         {
            highlightMode = oslHighlightOpenRoads(eLoop,nLoop,highlightMode);
         }
      }
      oslHighlightOpenRoads(0,0,2);
   }

   else return '?e='+oslEastings+'&n='+oslNorthings;
}
function oslRemoveDirSuffix(currentName, dirSuffix)
{
   var dPos = currentName.indexOf(dirSuffix);
   if(dPos != -1)
   {
      var dLength = dirSuffix.length;
      currentName = currentName.substr(0,dPos) + currentName.substr(dPos+dLength);
   }
   return currentName;
}
function oslNameCheckTrigger()
{
   oslOSLNameCheckTimer = 2;
}
function oslNameComparison()
{
   for(;oslONC_E<=oslEBlock_max;)
   {
      for(;oslONC_N<=oslNBlock_max;)
      {
         oslToOSGrid(oslONC_E*oslLocatorBlockSize, oslONC_N*oslLocatorBlockSize, OSL_MODE.NameCheck);
         if(oslLoadingMsg === true)
         {
            window.setTimeout(oslNameComparison,500);
            return;
         }
         oslONC_N++;
      }
      oslONC_N = oslNBlock_min;
      oslONC_E++;
   }
   for(var i=0;i<oslOSLNCSegments.length;i++)
   {
      if(oslOSLNCSegments[i].match === false)
      {
         var pline = oslOSLNCSegments[i].pline;
         pline.setAttribute("stroke","#000000");
         pline.setAttribute("stroke-opacity","0.5");
         pline.setAttribute("stroke-width","9");
         pline.setAttribute("stroke-dasharray","none");
      }
   }
}
function oslNCCandidateNew(pline, lonA, latA, lonB, latB, streetname)
{
   this.pline = pline;
   this.lonA = lonA;
   this.latA = latA;
   this.lonB = lonB;
   this.latB = latB;
   this.streetname = streetname;
   this.match = false;
}
function oslNCStateChange()
{
   if(document.getElementById('_cbNCEnabled').checked === false)
   {
      for(var segObj in W.model.segments.objects)
      {
         if(W.model.segments.objects.hasOwnProperty(segObj))
         {
            var seg = W.model.segments.objects[segObj];
            var pline = document.getElementById(seg.geometry.id);
            if(pline !== null)
            {
               pline.setAttribute("stroke-width","5");
               pline.setAttribute("stroke","#dd7700");
               pline.setAttribute("stroke-opacity","0.001");
               pline.setAttribute("stroke-dasharray","none");
            }
         }
      }
   }
   else
   {
      if(W.map.getZoom() >= 4) oslNameCheck();
   }
}
function oslOpenRoadsStateChange(e)
{
   oslMapIsStaticProcessing(true);
   oslUpdateHighlightCBs(e.srcElement.id);
}
function oslNameCheck()
{
   if((document.getElementById('_cbNCEnabled').checked === false) || (W.map.getZoom() < 4)) return;

   var geoCenter=new OpenLayers.LonLat(W.map.getCenter().lon,W.map.getCenter().lat);
   geoCenter.transform(new OpenLayers.Projection("EPSG:900913"),new OpenLayers.Projection("EPSG:4326"));
   oslToOSGrid(geoCenter.lat, geoCenter.lon, OSL_MODE.Conversion);
   if(oslLoadingMsg === true)
   {
      window.setTimeout(oslNameCheck,500);
      return;
   }

   oslEBlock_min = 99999;
   oslEBlock_max = -1;
   oslNBlock_min = 99999;
   oslNBlock_max = -1;

   var mapExtents = W.map.getExtent();
   var ignoreSegment;
   oslOSLNCSegments = [];

   for(var segObj in W.model.segments.objects)
   {
      if(W.model.segments.objects.hasOwnProperty(segObj))
      {
         ignoreSegment = false;
         var seg = W.model.segments.objects[segObj];
         var segRT = seg.attributes.roadType;

         if
         (
            (seg.geometry.bounds.left > mapExtents.right) ||
            (seg.geometry.bounds.right < mapExtents.left) ||
            (seg.geometry.bounds.top < mapExtents.bottom) ||
            (seg.geometry.bounds.bottom > mapExtents.top)
         )
         {
            // ignore segment as it's not visible...
            ignoreSegment = true;
         }
         if
         (
            (segRT < 1) ||
            ((segRT > 3) && (segRT < 6)) ||
            ((segRT > 8) && (segRT < 17)) ||
            ((segRT > 17) && (segRT < 20)) ||
            (segRT > 21)
         )
         {
            // ignore segment as it's non-driveable...
            ignoreSegment = true;
         }

         if(ignoreSegment === false)
         {
            var streetObj = W.model.streets.objects[seg.attributes.primaryStreetID];
            if(streetObj !== undefined)
            {
               var currentName = streetObj.name;
               var gid = seg.geometry.id;
               var pline = document.getElementById(gid);

               if((currentName !== null) && (pline !== null))
               {
                  currentName = oslRemoveDirSuffix(currentName,' (N)');
                  currentName = oslRemoveDirSuffix(currentName,' (S)');
                  currentName = oslRemoveDirSuffix(currentName,' (E)');
                  currentName = oslRemoveDirSuffix(currentName,' (W)');
                  currentName = oslRemoveDirSuffix(currentName,' (CW)');
                  currentName = oslRemoveDirSuffix(currentName,' (ACW)');

                  var endPointA = new OpenLayers.LonLat(seg.geometry.components[0].x,seg.geometry.components[0].y);
                  endPointA.transform(new OpenLayers.Projection("EPSG:900913"),new OpenLayers.Projection("EPSG:4326"));
                  oslToOSGrid(endPointA.lat, endPointA.lon, OSL_MODE.Conversion);
                  var eBlock = Math.floor(oslEastings/oslLocatorBlockSize);
                  var nBlock = Math.floor(oslNorthings/oslLocatorBlockSize);
                  if(eBlock < oslEBlock_min) oslEBlock_min = eBlock;
                  if(eBlock > oslEBlock_max) oslEBlock_max = eBlock;
                  if(nBlock < oslNBlock_min) oslNBlock_min = nBlock;
                  if(nBlock > oslNBlock_max) oslNBlock_max = nBlock;
                  var endPointB = new OpenLayers.LonLat(seg.geometry.components[seg.geometry.components.length-1].x,seg.geometry.components[seg.geometry.components.length-1].y);
                  endPointB.transform(new OpenLayers.Projection("EPSG:900913"),new OpenLayers.Projection("EPSG:4326"));
                  oslToOSGrid(endPointB.lat, endPointB.lon, OSL_MODE.Conversion);
                  oslOSLNCSegments.push(new oslNCCandidateNew(pline, endPointA.lon, endPointA.lat, endPointB.lon, endPointB.lat, currentName));
                  eBlock = Math.floor(oslEastings/oslLocatorBlockSize);
                  nBlock = Math.floor(oslNorthings/oslLocatorBlockSize);
                  if(eBlock < oslEBlock_min) oslEBlock_min = eBlock;
                  if(eBlock > oslEBlock_max) oslEBlock_max = eBlock;
                  if(nBlock < oslNBlock_min) oslNBlock_min = nBlock;
                  if(nBlock > oslNBlock_max) oslNBlock_max = nBlock;
               }
            }
         }
      }
   }

   if(oslOSLNCSegments.length > 0)
   {
      oslONC_E = oslEBlock_min;
      oslONC_N = oslNBlock_min;
      oslNameComparison();
   }
}
function oslTenthSecondTick()
{
   if(W.app.modeController.mode.mteModeState !== undefined)
   {
      if(!oslMTEMode)
      {
         oslAddLog('MTE mode, sleeping until normal service is resumed...');
         oslWindowMinimise();
      }
      oslMTEMode = true;
      return;
   }
   oslMTEMode = false;

   if(oslMOAdded === false)
   {
		if(document.getElementById('edit-panel') !== null)
		{
			oslAddLog('edit-panel mutation observer added...');
			var editPanelMO = new MutationObserver(oslEditPanelCheck);
			editPanelMO.observe(document.getElementById('edit-panel'),{childList:true,subtree:true});
			oslMOAdded = true;
		}
   }

   var hideUI = Boolean
   (
      (document.getElementsByClassName('menu')[0].className.indexOf('not-visible') === -1) &&
      (document.getElementsByClassName('menu')[0].className.indexOf('hide') === -1)
   );
   hideUI = Boolean(hideUI || (document.getElementsByClassName('toolbar-group open').length > 0));
   if(hideUI === false)
   {
      oslWindow.style.zIndex = 2000;
   }
   else
   {
      oslWindow.style.zIndex = -2000;
   }

   if(!oslAdvancedMode) oslEnableAdvancedOptions();

   var oslSelectedItems = [];
   if(W.selectionManager.selectedItems === undefined)
   {
      oslSelectedItems = W.selectionManager._selectedFeatures;
   }
   else
   {
      oslSelectedItems = W.selectionManager.selectedItems;
   }
   if(oslSelectedItems.length == 1)
   {
      if(oslPrevSelected === null) oslDoOSLUpdate = true;
      else if(oslSelectedItems[0].model.attributes.id != oslPrevSelected) oslDoOSLUpdate = true;
      oslPrevSelected = oslSelectedItems[0].model.attributes.id;
   }
   else
   {
      oslPrevSelected = null;
   }

   if(document.getElementById('oslSelect') !== null)
   {
      var editDisabled = ((document.getElementsByClassName('full-address disabled').length > 0) || (document.getElementsByClassName('full-address-container').length === 0));
      if(editDisabled === true)
      {
         document.getElementById('oslSelect').style.background = "rgb(160, 160, 160)";
      }
      else
      {
         document.getElementById('oslSelect').style.background = "rgb(204, 204, 255)";
      }
      document.getElementById('oslSelect').disabled = editDisabled;
   }

   if(oslOSLNameCheckTimer > 0)
   {
      if(--oslOSLNameCheckTimer === 0) oslNameCheck();
   }

   var bcIdx;
   var timeNow;
   if(--oslBlockCacheTestTimer === 0)
   {
      oslBlockCacheTestTimer = (oslCacheDecayPeriod * 10);
      timeNow = Math.floor(new Date().getTime() / 1000);
      for(bcIdx = oslBlockCacheList.length-1; bcIdx >= 0; bcIdx--)
      {
         if((timeNow - oslBlockCacheList[bcIdx].lastAccessed) > oslCacheDecayPeriod)
         {
            eval('delete '+oslBlockCacheList[bcIdx].blockName);
            oslBlockCacheList.splice(bcIdx,1);
         }
      }
   }

   if((oslDoOSLUpdate === true) && (oslMousepos !== null))
   {
      // update the OS Locator matches
      oslToOSGrid(oslMousepos.lat,oslMousepos.lon,OSL_MODE.OpenNames);
      oslDoOSLUpdate = oslLoadingMsg;
      if(!oslDoOSLUpdate) oslRadioClick();
   }

   if(oslRefreshAutoTrack)
   {
      // refresh any of the site tabs/windows we've checked for auto-tracking
      if(oslInUK === true)
      {
         if(document.getElementById('_cbAutoTrackOSOD').checked == 1) window.open(oslOSOD_url,'_osopendata');
         if(document.getElementById('_cbAutoTrackRWO').checked == 1) window.open(oslRWO_url,'_roadworksorg');
         if(oslInWiltshire === true)
         {
            if(document.getElementById('_cbAutoTrackSWG').checked == 1) window.open(oslSWG_url,'_wiltshire');
         }
         if(oslInLondon === true)
         {
            if(document.getElementById('_cbAutoTrackLRR').checked == 1) window.open(oslLRR_url,'_londonregister');
         }
      }
      oslRefreshAutoTrack = false;
   }

   if(oslBlocksToTest.length > 0)
   {
      for(var i=0; i<oslBlocksToTest.length; i++)
      {
         var oslEvalString = 'typeof '+oslBlocksToTest[i];
         if(eval(oslEvalString) != 'undefined')
         {
            oslBlocksToTest.splice(i, 1);
         }
      }

      if(oslBlocksToTest.length === 0)
      {
         oslMapIsStaticProcessing(true);
      }
   }   
}
function oslEnableAdvancedOptions()
{
   if (oslAdvancedMode) return;
   if(W.loginManager === null) return;
   if(W.loginManager.isLoggedIn() === true)
   {
      var thisUser = W.loginManager.user;
      if (thisUser !== null && thisUser.getRank() >= 2)
      {
         oslAdvancedMode = true;
         oslAddLog('advanced mode enabled');
      }
   }
}
function oslUpdateLiveMapLink()
{
   var lmLink = document.getElementById('livemap-link');
   if(lmLink === null)
   {
      window.setTimeout(oslUpdateLiveMapLink,100);
      return;
   }

   // translate the zoom level between WME and live map.
   var livemap_zoom = parseInt(sessionStorage.zoom)+12;
   if (livemap_zoom > 17) livemap_zoom = 17;
   var livemap_url = 'https://www.waze.com/livemap/?';
   livemap_url += 'lon='+sessionStorage.lon;
   livemap_url += '&lat='+sessionStorage.lat;
   livemap_url += '&zoom='+livemap_zoom;

   // Modify existing livemap link to reference current position in WME
   lmLink.href = livemap_url;
   lmLink.target = '_blank';
}
function oslOSGridRefToPixel(osEast, osNorth)
{
   var lonlat = new OpenLayers.LonLat();
   lonlat.lon = parseFloat(osEast);
   lonlat.lat = parseFloat(osNorth);
   lonlat.transform(new OpenLayers.Projection("EPSG:4326"),new OpenLayers.Projection("EPSG:900913"));
   return W.map.getPixelFromLonLat(lonlat);
}

function oslBoundsCheck(pix1, pix2, width, height)
{
   var xmin = Math.min(pix1.x,pix2.x);
   var xmax = Math.max(pix1.x,pix2.x);
   var ymin = Math.min(pix1.y,pix2.y);
   var ymax = Math.max(pix1.y,pix2.y);
   var retval = 
   (
      (xmin <= width) &&
      (xmax >= 0) &&
      (ymin <= height) &&
      (ymax >= 0)
   );
   return retval;
}

function oslHighlightOpenRoads(oslEastings, oslNorthings, mode)
{
   oslBlocksToLoad = [];
   if((mode === 0) || (mode == 1))
   {
      var arrayName = 'locatorData_'+oslEastings+'_'+oslNorthings;
      // check to see if there's a corresponding array loaded already
      var oslEvalString = 'typeof '+arrayName;

      if(eval(oslEvalString) == "undefined")
      {
         // create a blank placeholder which will get replaced by the actual data if the array is present on the server...
         eval(arrayName+' = []');
         oslBlocksToLoad.push(oslBlockPath+Math.floor(oslEastings / 100000)+'/'+Math.floor(oslNorthings / 100000)+'/'+arrayName+'.user.js');
         oslBlocksToLoad.push(arrayName);
         oslBlocksToTest.push(arrayName);
         oslLoadBlocks();
      }

      var divWidth = document.getElementById('WazeMap').offsetWidth;
      var divHeight = document.getElementById('WazeMap').offsetHeight;
      var displayPoints = false;
      if(mode === 0)
      {         
         // initialise SVG container
         oslSegGeoDivInnerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="'+divWidth+'px" height="'+divHeight+'px" version="1.1">';
      }

      // create local copy of array to avoid an eval() in each iteration of the loop
      if(eval('typeof '+arrayName) == "undefined") return;
      var oslOpenRoadsData;
      eval('oslOpenRoadsData = '+arrayName);

      // calculate an appropriate stroke width for the current zoom level
      var strokeWidth = W.map.getZoom();
      if(strokeWidth < 2) strokeWidth = 2;
      if(strokeWidth > 9) strokeWidth = 9;

      var renderByFunction = [];
      renderByFunction.push(true);  // Undefined roads always get rendered when the OpenRoads layer is enabled
      renderByFunction.push(document.getElementById('_cbSegGeoMotorway').checked);
      renderByFunction.push(document.getElementById('_cbSegGeoARoad').checked);
      renderByFunction.push(document.getElementById('_cbSegGeoBRoad').checked);
      renderByFunction.push(document.getElementById('_cbSegGeoMinor').checked);
      renderByFunction.push(document.getElementById('_cbSegGeoLocal').checked);
      if(oslAdvancedMode == true)
      {
         renderByFunction.push(document.getElementById('_cbSegGeoLocalAccess').checked);
         renderByFunction.push(document.getElementById('_cbSegGeoRestricted').checked);
         renderByFunction.push(document.getElementById('_cbSegGeoSecondary').checked);
      }
      else
      {
         renderByFunction.push(false);
         renderByFunction.push(false);
         renderByFunction.push(false);
      }
      var useFullGeo = document.getElementById('_cbOpenRoadsUseGeo').checked;
      var enhancePolylines = document.getElementById('_cbOpenRoadsEnhanceGeoVis').checked;

      var pix1;
      var pix2;
      var nameColour;
      for(var roadIdx = 0; roadIdx < oslOpenRoadsData.length; roadIdx++)
      {
         var roadEntry = oslOpenRoadsData[roadIdx];
         var elements = roadEntry.split(':');
         var roadFunction = parseInt(elements[OSL_ELM.Function]);

         if ((elements[OSL_ELM.RoadName] == '') && (elements[OSL_ELM.RoadNumber] == ''))
         {
            nameColour = 'cyan';
         }
         else
         {
            nameColour = 'magenta';
         }

         if(renderByFunction[roadFunction] == true)
         {
            pix1 = oslOSGridRefToPixel(elements[OSL_ELM.BoundW], elements[OSL_ELM.BoundS]);
            pix2 = oslOSGridRefToPixel(elements[OSL_ELM.BoundE], elements[OSL_ELM.BoundN]);
            if(oslBoundsCheck(pix1, pix2, divWidth, divHeight) == true)
            {
               if(useFullGeo == true)
               {
                  var geoPairs = elements[OSL_ELM.Geometry].split('  ');
                  var geoPoints = geoPairs[0].split(' ');
                  var isTunnel = (elements[OSL_ELM.Structure] == OSL_ROADSTRUCTURES.Road_In_Tunnel);
                  pix1 = oslOSGridRefToPixel(geoPoints[0], geoPoints[1]);
                  var pline = '';
                  for(var pts=1; pts < geoPairs.length; pts++)
                  {
                     geoPoints = geoPairs[pts].split(' ');
                     pix2 = oslOSGridRefToPixel(geoPoints[0],geoPoints[1]);
                     if(oslBoundsCheck(pix1, pix2, divWidth, divHeight) == true)
                     {
                        if(displayPoints === false)
                        {
                           pline += '<polyline points="';
                           displayPoints = true;
                        }
                        pline += pix1.x+','+pix1.y+' '+pix2.x+','+pix2.y+' ';
                     }
                     pix1 = pix2;
                  }

                  if(displayPoints === true)
                  {
                     var strokeColour = oslStrokeColoursByFunction[roadFunction];
                     var strokeArray = '';
                     if(isTunnel == true)
                     {
                        strokeArray = 'stroke-dasharray:10,10;';
                     }
                     
                     if(enhancePolylines == true)
                     {
                        oslSegGeoDivInnerHTML += pline + '" style="stroke:';
                        oslSegGeoDivInnerHTML += nameColour;
                        oslSegGeoDivInnerHTML += ';'+strokeArray+'stroke-width:'+(strokeWidth+11)+';stroke-linecap:round;fill:none"/>';
                     }
                     oslSegGeoDivInnerHTML += pline + '" style="stroke:black;'+strokeArray+'stroke-width:'+(strokeWidth+2)+';stroke-linecap:round;fill:none"/>';
                     oslSegGeoDivInnerHTML += pline + '" style="stroke:'+strokeColour+';'+strokeArray+'stroke-width:'+strokeWidth+';stroke-linecap:round;fill:none"/>';
                     displayPoints = false;
                  }
               }
               else
               {
                  const minDim = 20;
                  var bWidth = Math.abs(pix2.x-pix1.x);
                  if(bWidth < minDim) bWidth = minDim;
                  var bHeight = Math.abs(pix2.y-pix1.y);
                  if(bHeight < minDim) bHeight = minDim;

                  oslSegGeoDivInnerHTML += '<rect x="'+pix1.x+'" y="'+pix2.y+'" width="'+bWidth+'" height="'+bHeight+'" style="fill:';
                  oslSegGeoDivInnerHTML += nameColour;
                  oslSegGeoDivInnerHTML += ';stroke:black;stroke-width:4;fill-opacity:0.25;stroke-opacity:0.25"/>';
               }
            }
         }
      }
   }
   else if(mode == 2)
   {
      // finalise SVG
      oslSegGeoDivInnerHTML += '</svg>';
      oslSegGeoDiv.innerHTML = oslSegGeoDivInnerHTML;
   }
   else if(mode == 3)
   {
      // erase SVG
      oslSegGeoDiv.innerHTML = '';
   }

   return 1;
}
function oslRepositionOverlays(doBoth)
{
   var divTop = (0 - parseInt(oslWazeMapElement.style.top.replace('px','')));
   var divLeft = (0 - parseInt(oslWazeMapElement.style.left.replace('px','')));
   oslBBDiv.style.top = divTop + 'px';
   oslBBDiv.style.left = divLeft + 'px';
   if(doBoth == true)
   {
      oslSegGeoDiv.style.top = divTop + 'px';
      oslSegGeoDiv.style.left = divLeft + 'px';
   }
}
function oslGetCorrectedLonLatFromPixelPos(px, py, toolbarCompensation)
{
   if((toolbarCompensation) && (oslOffsetToolbar)) py -= document.getElementById('toolbar').clientHeight;
   py -= document.getElementById('topbar-container').clientHeight;
   var pixelPos = new OpenLayers.Pixel(px, py);
   return W.map.getLonLatFromPixel(pixelPos);
}
function oslGetOffsetMapCentre()
{
   // get lon/lat of viewport centrepoint for modifying the livemap link and for passing to the external mapping sites.

   // shift the longitude pixel offset by half the width of the WME sidebar to account for the lateral offset that would
   // otherwise occur when switching between the WME tab and the other map tabs - all of those use a full-width map view,
   // so their map centre is further to the left within the browser window than the WME centrepoint...
   var mapVPX = (W.map.olMap.getViewport().clientWidth / 2) - (document.getElementById('sidebar').clientWidth / 2);
   var mapVPY = W.map.olMap.getViewport().clientHeight / 2;
   return oslGetCorrectedLonLatFromPixelPos(mapVPX, mapVPY, false);
}
function oslMouseMoveAndUp(e)
{
   if(oslUserPrefs.ui.state == 'minimised') return;
   var mouseX = e.pageX - document.getElementById('map').getBoundingClientRect().left;
   var mouseY = e.pageY - document.getElementById('map').getBoundingClientRect().top;
  
   oslMousePixelpos = new OpenLayers.Pixel(mouseX, mouseY);
   oslMousepos = oslGetCorrectedLonLatFromPixelPos(mouseX, mouseY, true).transform(new OpenLayers.Projection("EPSG:900913"),new OpenLayers.Projection("EPSG:4326"));
   if((oslMousepos != sessionStorage.oslMousepos) || (oslLoadingMsg && (eval(oslEvalString) != "undefined")))
   {
      oslLoadingMsg = false;
      sessionStorage.oslMousepos = oslMousepos;

      oslDoOSLUpdate = false;
      // update the OSL results if there are no selected segments, but there is a highlighted segment
      // which we haven't already done an update for

      oslSegmentHighlighted = false;
      var noneSelected = false;
      if(W.selectionManager.selectedItems !== undefined)
      {
         noneSelected = (W.selectionManager.selectedItems.length === 0);
      }
      else
      {
         noneSelected = (W.selectionManager._selectedFeatures.length === 0);
      }
      if(noneSelected === true)
      {
         for(var slIdx=0; slIdx < W.map.segmentLayer.features.length; slIdx++)
         {
            if(W.map.segmentLayer.features[slIdx].renderIntent == 'highlight')
            {
               if(slIdx != oslPrevHighlighted)
               {
                  oslPrevHighlighted = slIdx;
               }
               oslDoOSLUpdate = true;
               oslSegmentHighlighted = true;
            }
         }
      }
   }

   var geoCenter = oslGetOffsetMapCentre();
   var geoCenterTransform = oslGetOffsetMapCentre().transform(new OpenLayers.Projection("EPSG:900913"),new OpenLayers.Projection("EPSG:4326"));
   var lat = geoCenterTransform.lat;
   var lon = geoCenterTransform.lon;
   var zoom = W.map.getZoom();
   // compare the new parameters against the persistent copies, and update the external links
   // only if there's a change required - the newly-inserted <a> element can't be clicked
   // on until the insertion process is complete, and if we were to re-insert it every timeout
   // then it'd spend a lot of its time giving the appearance of being clickable but without
   // actually doing anything...
   if((zoom != parseInt(sessionStorage.zoom))||(lat != parseFloat(sessionStorage.lat))||(lon != parseFloat(sessionStorage.lon)))
   {
      var country = null;
      if((W.model.countries.top === undefined)||(W.model.countries.top === null))
      {
         if(W.model.countries.additionalInfo !== null)
         {
            country = W.model.countries.additionalInfo[0].name;
         }
      }
      else
      {
         country = W.model.countries.top.name;
      }

      if(country == "United Kingdom")
      {
         if (oslInUK === false)
         {
            oslAddLog('location is the UK, enabling full UI...');
            oslInUK = true;
            document.getElementById('oslOSLDiv').style.display = "block";
            document.getElementById('oslNCDiv').style.display = "block";
            document.getElementById('oslSegGeoUIDiv').style.display = "block";
            document.getElementById('_extlinksUK').style.display = "block";
         }
      }
      else
      {
         // ...somewhere not yet supported, or WME isn't telling us just yet...
         oslAddLog('location not recognised, disabling UK-specific parts of UI...');
         oslInUK = false;
         oslInLondon = false;
         document.getElementById('oslOSLDiv').style.display = "none";
         document.getElementById('oslNCDiv').style.display = "none";
         document.getElementById('oslSegGeoUIDiv').style.display = "none";
         document.getElementById('_extlinksUK').style.display = "none";
      }

      if(oslInUK === true)
      {
         // we're in the UK, so test to see if we're within the approximate Greater London bounding box
         if((lon >= -0.55) && (lon <= 0.30) && (lat >= 51.285) && (lat <= 51.695))
         {
            oslInLondon = true;
            document.getElementById('lrrCtrls').style.display='inline';
         }
         else
         {
            oslInLondon = false;
            document.getElementById('lrrCtrls').style.display='none';
         }

         // also see if we're more or less within Wiltshire
         if((lon >= -2.33) && (lon <= -1.50) && (lat >= 50.94) && (lat <= 51.71))
         {
            oslInWiltshire = true;
            document.getElementById('swgCtrls').style.display='inline';
         }
         else
         {
            oslInWiltshire = false;
            document.getElementById('swgCtrls').style.display='none';
         }
      }

      if(zoom != sessionStorage.zoom)
      {
         if(zoom < 4) document.getElementById('_cbNCEnabled').disabled = true;
         else document.getElementById('_cbNCEnabled').disabled = false;
      }
      // update the persistent vars with the new position
      sessionStorage.zoom = zoom;
      sessionStorage.lat = lat;
      sessionStorage.lon = lon;

      if(oslInUK === true)
      {
         // calculate the OS eastings/northings for the current WME centrepoint - this is required by the OpenData and London Roadworks sites...
         var osCoords = oslToOSGrid(lat,lon,OSL_MODE.Conversion);

         // translate the zoom level between WME and OpenData - the match here isn't quite so good...
         var odzoom = zoom + 8;
         if(odzoom < 6) odzoom = 6;
         if(odzoom > 12) odzoom = 12;
         // generate the OpenData URL
         oslOSOD_url = 'https://openspacewmb.ordnancesurvey.co.uk/osmapapi/mapbuilder?'+osCoords+'&z='+odzoom;

         // generate the one.network URL - no zoom translation required here
         oslRWO_url = 'https://one.network?lat='+lat+'&lon='+lon+'&z='+zoom+'&showlinks=true';

         // generate the Wiltshire streetworks URL - use mapCenter() as is, but translate the zoom level
         var swgzoom = zoom + 2;
         if(swgzoom > 11) swgzoom = 11;
         oslSWG_url = 'http://streetworks.wiltshire.gov.uk/map.aspx?lat='+geoCenter.lat+'&lon='+geoCenter.lon+'&z='+swgzoom;

         // translate the zoom level between WME and London Roadworks Register - this gives reasonable results up to WME zoom of 5, beyond
         // which the LRR site can't zoom in any further...
         var lrrzoom = zoom;
         if(lrrzoom > 5) lrrzoom = 5;
         // generate the roadworks register URL
         oslLRR_url = 'http://public.londonworks.gov.uk/roadworks/home'+osCoords+'&z='+lrrzoom;
      }

      // wait to update the livemap link, as WME now does its own update after this point so any changes we make here
      // end up being wiped out...
      window.setTimeout(oslUpdateLiveMapLink,100);

      // update the link URLs
      if(oslInUK === true)
      {
         document.getElementById("_linkOSOD").href = oslOSOD_url;
         document.getElementById("_linkRWO").href = oslRWO_url;
         document.getElementById("_linkSWG").href = oslSWG_url;
         document.getElementById("_linkLRR").href = oslLRR_url;
      }
      document.getElementById('_linkPermalink').href = document.getElementsByClassName('WazeControlPermalink')[0].getElementsByTagName('a')[0].href;

      // refreshing the tabs within the event handler causes Chrome to switch focus to the tabs, so we
      // simply set the flag here and let the refresh occur within the 100ms tick handler as before
      oslRefreshAutoTrack = true;

      oslRadioClick();
   }
   
   oslRepositionOverlays(false);
   if((e.type == "mouseup") || (e.type == "zoomend"))
   {
      oslMapIsStaticProcessing(false);
   }
}
var oslWaitingForMapToStopMoving = false;
function oslMapIsStaticProcessing(forceUpdate)
{
   if(oslUserPrefs.ui.state == 'maximised')
   {
      var tCenter = W.map.getCenter();
      var tZoom = W.map.getZoom();
      if
      (
         (oslRORCenter == null) || (tCenter.lat != oslRORCenter.lat) || (tCenter.lon != oslRORCenter.lon) ||
         (oslRORZoom == null) || (tZoom != oslRORZoom)
      )
      {
         oslWaitingForMapToStopMoving = true;
         oslRORCenter = tCenter;
         oslRORZoom = tZoom;
         window.setTimeout(oslMapIsStaticProcessing, 250);
         return;
      }

      if((oslWaitingForMapToStopMoving == false) && (forceUpdate == false))
      {
         return;
      }
      oslWaitingForMapToStopMoving = false;

      var geoCenter=new OpenLayers.LonLat(W.map.getCenter().lon,W.map.getCenter().lat);
      geoCenter.transform(new OpenLayers.Projection("EPSG:900913"),new OpenLayers.Projection("EPSG:4326"));
      oslToOSGrid(geoCenter.lat, geoCenter.lon, OSL_MODE.Conversion);

      oslNameCheck();
      if(oslInUK === true)
      {
         // recalculate the map viewport extents in terms of oslEastings/oslNorthings
         var vpHalfWidth = (W.map.getExtent().right-W.map.getExtent().left) / (2 * 1.61);
         var vpHalfHeight = (W.map.getExtent().top-W.map.getExtent().bottom) / (2 * 1.61);
         oslVPLeft = oslEastings - vpHalfWidth;
         oslVPRight = oslEastings + vpHalfWidth;
         oslVPBottom = oslNorthings - vpHalfHeight;
         oslVPTop = oslNorthings + vpHalfHeight;

         if(document.getElementById('_cbOpenRoadsEnabled').checked === true)
         {
            oslToOSGrid(oslEastings,oslNorthings,OSL_MODE.OpenRoads);
         }
         else
         {
            oslHighlightOpenRoads(0,0,3);
         }
      }
      else
      {
         oslHighlightOpenRoads(0,0,3);
      }
      oslRepositionOverlays(true);
   }
}
function oslTestPointerOutsideMap(mX, mY)
{
   var mapElm = document.getElementById("map");
   if(mapElm === undefined) return false;

   var bLeft = mapElm.parentElement.offsetLeft;
   var bRight = (bLeft + mapElm.offsetWidth);
   var bTop = (mapElm.parentElement.offsetTop + document.getElementById("topbar-container").clientHeight);
   var bBottom = (mapElm.parentElement.offsetTop + mapElm.offsetHeight + document.getElementById("topbar-container").clientHeight - document.getElementsByClassName("WazeMapFooter")[0].clientHeight);

   if((mX < bLeft) || (mX > bRight) || (mY < bTop) || (mY > bBottom)) return true;
   else return false;
}
function oslMouseOut(e)
{
   if(oslTestPointerOutsideMap(e.clientX, e.clientY))
   {
      // when the mouse pointer leaves the map area, WME treats it similarly to a mouseup
      // event without generating an actual mouseup event, so we need to do the same...
      oslMapIsStaticProcessing(false);
   }
}
function oslCancelEvent(e)
{
  e = e ? e : window.event;
  if(e.stopPropagation)
    e.stopPropagation();
  if(e.preventDefault)
    e.preventDefault();
  e.cancelBubble = true;
  e.cancel = true;
  e.returnValue = false;
  return false;
}

function oslOSLDivMouseDown(e)
{
   oslPrevMouseX = e.pageX;
   oslPrevMouseY = e.pageY;
   oslDivDragging = true;
   oslDragBar.style.cursor = 'move';
   document.body.addEventListener('mousemove', oslOSLDivMouseMove, false);
   document.body.addEventListener('mouseup', oslOSLDivMouseUp, false);
   // lock the UI width during a drag so we can correctly detect for falling off the window edge
   oslWindow.style.width = oslWindow.getBoundingClientRect().width+'px';
   return true;
}
function oslOSLDivMouseUp()
{
   if(oslDivDragging)
   {
      oslDivDragging = false;
      oslUserPrefs.ui.x = oslOSLDivLeft;
      oslUserPrefs.ui.y = oslOSLDivTop;
      oslWriteUserPrefs();
      // unlock the UI width again so we can expand/contract as required based on other script behaviour
      oslWindow.style.width = "auto";
   }
   oslDragBar.style.cursor = 'auto';
   document.body.removeEventListener('mousemove', oslOSLDivMouseMove, false);
   document.body.removeEventListener('mouseup', oslOSLDivMouseUp, false);
   return true;
}
function oslOSLDivMouseMove(e)
{
   var vpHeight = window.innerHeight;
   var vpWidth = window.innerWidth;

   oslOSLDivTop = parseInt(oslOSLDivTop) + parseInt((e.pageY - oslPrevMouseY));
   oslOSLDivLeft = parseInt(oslOSLDivLeft) + parseInt((e.pageX - oslPrevMouseX));
   oslPrevMouseX = e.pageX;
   oslPrevMouseY = e.pageY;

   if(oslOSLDivTop < 0) oslOSLDivTop = 0;
   if(oslOSLDivTop + 16 >= vpHeight) oslOSLDivTop = vpHeight-16;
   if(oslOSLDivLeft < 0) oslOSLDivLeft = 0;
   if(oslOSLDivLeft + 32 >= vpWidth) oslOSLDivLeft = vpWidth-32;

   oslWindow.style.top = oslOSLDivTop+'px';
   oslWindow.style.left = oslOSLDivLeft+'px';

   return oslCancelEvent(e);
}
function oslKeepUIVisible()
{
   var vpHeight = window.innerHeight;
   var vpWidth = window.innerWidth;
   var topbarHeight = document.getElementById('toolbar').offsetHeight;
   var maxUIHeight = (vpHeight - topbarHeight);
   // Restore auto-sizing so we can see the effect of whatever UI change caused us to get here...
   oslWindow.style.height = "auto";
   oslWindow.style.overflow = "hidden";

   // If the bottom edge of the ui would fall off the bottom edge of the map viewport, nudge
   // the UI up as far as required to keep the whole UI visible.  If this would however require
   // the top edge of the UI to be pushed off the top of the map viewport, constrain the UI
   // height to the viewport height and enable a scrollbar...
   if(oslWindow.getBoundingClientRect().bottom >= maxUIHeight)
   {
      var newTop = (vpHeight-oslWindow.getBoundingClientRect().height);
      if(newTop < topbarHeight)
      {
         newTop = topbarHeight;
         oslWindow.style.height = maxUIHeight+'px';
         oslWindow.style.overflow = "scroll";
      }
      oslOSLDivTop = newTop;
      oslUserPrefs.ui.y = oslOSLDivTop;
      oslWindow.style.top = oslOSLDivTop+'px';
   }
}
function oslMinimiseDiv(divID)
{
   var tDiv = document.getElementById(divID);
   if(tDiv != null)
   {
      tDiv.style.height = '0px';
      tDiv.style.padding = '0px';
      tDiv.style.overflow = 'hidden';
   }
}
function oslMaximiseDiv(divID)
{
   var tDiv = document.getElementById(divID);
   if(tDiv != null)
   {
      tDiv.style.height = 'auto';
      tDiv.style.padding = '2px';
      tDiv.style.overflow = 'auto';
   }
}
function oslWindowMaximise()
{
   var tHTML = '';
   tHTML += '<span style="float:left"><a href="'+oslUpdateURL+'" target="_blank"><b>WMEOpenData</a> v'+oslVersion+'</b></span>';
   tHTML += '<span id="_minimax" style="float:right"><i class="fa fa-chevron-circle-up"></i></span>';
   tHTML += '<br>';
   oslDragBar.innerHTML = tHTML;
   document.getElementById('_minimax').addEventListener('click', oslWindowMinimise, false);

   oslMaximiseDiv('oslOSLDiv');
   oslMaximiseDiv('oslNCDiv');
   oslMaximiseDiv('oslSegGeoUIDiv');
   oslMaximiseDiv('oslMLCDiv');
   oslKeepUIVisible();
   oslUserPrefs.ui.state = 'maximised';
   oslWriteUserPrefs();
   oslRepositionOverlays(true);
   oslMapIsStaticProcessing(true);
}
function oslWindowMinimise()
{
   var tHTML = '';
   tHTML += '<span style="float:left"><b>WMEOpenData v'+oslVersion+'</b></span>';
   tHTML += '<span id="_minimax" style="float:right"><i class="fa fa-chevron-circle-down"></i></span>';
   tHTML += '<br>';
   oslDragBar.innerHTML = tHTML;
   document.getElementById('_minimax').addEventListener('click', oslWindowMaximise, false);

   oslMinimiseDiv('oslOSLDiv');
   oslMinimiseDiv('oslNCDiv');
   oslMinimiseDiv('oslSegGeoUIDiv');
   oslMinimiseDiv('oslMLCDiv');
   oslKeepUIVisible();
   oslBBDiv.innerHTML = '';
   oslSegGeoDiv.innerHTML = '';
   oslUserPrefs.ui.state = 'minimised';
   oslWriteUserPrefs();
}
function oslSubDivSetState(divID, state)
{
   var tSubDiv = document.getElementById(divID).children[1];
   var tToggle = document.getElementById(divID).children[0].children[1].children[0];
   if(state == 'minimised')
   {
      tSubDiv.style.height = "0px";
      tSubDiv.style.padding = "0px";
      tSubDiv.style.overflow = "hidden";
      tToggle.className = "fa fa-chevron-circle-down";
   }
   else
   {
      tSubDiv.style.height = "auto";
      tSubDiv.style.width = "100%";
      tSubDiv.style.padding = "2px";
      tSubDiv.style.overflow = "auto";
      tToggle.className = "fa fa-chevron-circle-up";
   }
}
function oslUIMinMax(e)
{
   var headerDivID = e.srcElement.parentElement.parentElement.parentElement.id;
   var tSubDiv = document.getElementById(headerDivID).children[1];
   if(tSubDiv != null)
   {
      var newState;
      if(tSubDiv.style.height == "auto")
      {
         newState = 'minimised';
      }
      else
      {
         newState = 'maximised';
      }
      oslSubDivSetState(headerDivID, newState);
      oslUpdateMinMax(headerDivID, newState);
   }
   oslKeepUIVisible();
}
function oslNestedTypeof(nestedObj)
{
   var objFields = nestedObj.split(".");
   var retval = "unknown";
   var fieldIdx = 0;
   var level = unsafeWindow;
   while((retval != "undefined") && (fieldIdx < objFields.length))
   {
      level = level[objFields[fieldIdx++]];
      retval = typeof(level);
   }
   return retval;
}
function oslEditPanelCheck()
{
   if(document.getElementById('edit-panel').getElementsByClassName('map-comment-feature-editor').length)
   {
      document.body.style.overflow = "auto";
   }
   else
   {
      document.body.style.overflow = "hidden";
   }
}
function oslSetupBBDiv()
{
   // add a new div to the map viewport, to hold the bounding box SVG
   oslAddLog('create bounding box DIV');
   oslBBDiv = document.createElement('div');
   oslBBDiv.id = "oslBBDiv";
   oslBBDiv.style.position = 'absolute';
   oslBBDiv.style.top = (0-parseInt(oslWazeMapElement.style.top.replace('px',''))) + 'px';
   oslBBDiv.style.left = (0-parseInt(oslWazeMapElement.style.left.replace('px',''))) + 'px';
   oslBBDiv.style.overflow = 'hidden';
   oslBBDiv.style.width = window.innerWidth;
   oslBBDiv.style.height = window.innerHeight;
   oslWazeMapElement.appendChild(oslBBDiv);
}
function oslSetupORDiv()
{
   // add a new div to the map viewport, to hold the OpenRoads SVG
   oslAddLog('create OpenRoads DIV');
   oslSegGeoDiv = document.createElement('div');
   oslSegGeoDiv.id = "oslSegGeoDiv";
   oslSegGeoDiv.style.position = 'absolute';
   oslSegGeoDiv.style.top = (0-parseInt(oslWazeMapElement.style.top.replace('px',''))) + 'px';
   oslSegGeoDiv.style.left = (0-parseInt(oslWazeMapElement.style.left.replace('px',''))) + 'px';
   oslSegGeoDiv.style.overflow = 'hidden';
   oslSegGeoDiv.style.width = window.innerWidth;
   oslSegGeoDiv.style.height = window.innerHeight;
   oslWazeMapElement.appendChild(oslSegGeoDiv);
}
function oslGenerateOpenRoadsCBHTML(isStd, ID, label, indentLevel)
{
   var tHTML = '';
   var indent = 'padding-left: '+(indentLevel * 0.5)+'em;';
   
   tHTML += '<label style="display: inline; '+indent+'">';
   tHTML += '<input type="checkbox" name="oslOpenRoads_';
   if(isStd == true) tHTML += 'std';
   else tHTML += 'adv';
   tHTML += '" id="' + ID + '" />';
   tHTML += label + '</label>';
   return tHTML;
}
function oslGenerateCollapsibleSubDiv(headerText, subDivHTML)
{
   var tHTML;
   tHTML = '<div><span style="float:left"><b>'+headerText+'</b></span>';
   tHTML += '<span name="oslMinMaxToggle" style="float:right"><i class="fa fa-chevron-circle-down"></i></span></div>';
   tHTML += '<div style="height: 0px; padding: 0px; overflow: hidden;">';
   tHTML += subDivHTML;
   tHTML += '</div>';
   return tHTML;
}
function oslSetupUI()
{
   var subDivHTML;

   // add a new div to hold the OS Locator results, in the form of a draggable window
   oslAddLog('create lookup results DIV');
   oslWindow = document.createElement('div');
   oslWindow.id = "oslWindow";
   oslWindow.style.position = 'absolute';
   oslWindow.style.border = '1px solid #BBDDBB';
   oslWindow.style.borderRadius = '4px';
   oslWindow.style.overflow = 'hidden';
   oslWindow.style.zIndex = 2000;
   oslWindow.style.opacity = 0;
   oslWindow.style.transitionProperty = "opacity";
   oslWindow.style.transitionDuration = "1000ms";
   oslWindow.style.webkitTransitionProperty = "opacity";
   oslWindow.style.webkitTransitionDuration = "1000ms";
   oslWindow.style.boxShadow = '5px 5px 10px Silver';
   document.body.appendChild(oslWindow);

   // dragbar div
   oslAddLog('create dragbar DIV');
   oslDragBar = document.createElement('div');
   oslDragBar.id = "oslDragBar";
   oslDragBar.style.backgroundColor = '#D0D0D0';
   oslDragBar.style.padding = '4px';
   oslDragBar.style.fontSize = '16px';
   oslDragBar.style.lineHeight = '18px';
   oslWindow.appendChild(oslDragBar);

   // OS results div
   oslAddLog('create results DIV');
   oslOSLDiv = document.createElement('div');
   oslOSLDiv.id = "oslOSLDiv";
   oslOSLDiv.style.backgroundColor = '#DDFFDD';
   oslOSLDiv.style.padding = '2px';
   oslOSLDiv.style.fontSize = '14px';
   oslOSLDiv.style.lineHeight = '16px';
   oslOSLDiv.style.display = 'none';

   subDivHTML = "<div id='oslRoadNameMatches'></div>";
   oslOSLDiv.innerHTML = oslGenerateCollapsibleSubDiv("OS Open Names", subDivHTML);

   oslWindow.appendChild(oslOSLDiv);

   // Segment geometry control div
   oslAddLog('create SegGeo control DIV');
   oslSegGeoUIDiv = document.createElement('div');
   oslSegGeoUIDiv.id = "oslSegGeoUIDiv";
   oslSegGeoUIDiv.style.backgroundColor = '#40A040';
   oslSegGeoUIDiv.style.padding = '2px';
   oslSegGeoUIDiv.style.fontSize = '14px';
   oslSegGeoUIDiv.style.lineHeight = '16px';
   oslSegGeoUIDiv.style.display = 'none';

   subDivHTML = oslGenerateOpenRoadsCBHTML(true, "_cbOpenRoadsEnabled", "Highlight by Classification:", 0) + '<br>';
   subDivHTML += oslGenerateOpenRoadsCBHTML(true, "_cbSegGeoMotorway", "Motorway", 1) + '<br>';
   subDivHTML += oslGenerateOpenRoadsCBHTML(true, "_cbSegGeoARoad", "A Road", 1) + '<br>';
   subDivHTML += oslGenerateOpenRoadsCBHTML(true, "_cbSegGeoBRoad", "B Road", 1) + '<br>';
   subDivHTML += oslGenerateOpenRoadsCBHTML(true, "_cbSegGeoMinor", "Minor Road", 1) + '<br>';
   subDivHTML += oslGenerateOpenRoadsCBHTML(true, "_cbSegGeoLocal", "Local Road", 1) + '<br>';
   if(oslAdvancedMode == true)
   {
      subDivHTML += oslGenerateOpenRoadsCBHTML(false, "_cbSegGeoLocalAccess", "Local Access Road", 1) + '<br>';
      subDivHTML += oslGenerateOpenRoadsCBHTML(false, "_cbSegGeoRestricted", "Restricted Access Road", 1) + '<br>';
      subDivHTML += oslGenerateOpenRoadsCBHTML(false, "_cbSegGeoSecondary", "Secondary Access Road", 1) + '<br>';
   }
   subDivHTML += '<br>' + oslGenerateOpenRoadsCBHTML(true, "_cbOpenRoadsUseGeo", "Show as polylines", 0) + '<br>';
   subDivHTML += oslGenerateOpenRoadsCBHTML(true, "_cbOpenRoadsEnhanceGeoVis", "Enhance polyline visibility", 1);
   oslSegGeoUIDiv.innerHTML = oslGenerateCollapsibleSubDiv("OS Open Roads", subDivHTML);
   oslWindow.appendChild(oslSegGeoUIDiv);

   // NameCheck div
   oslAddLog('create NameCheck DIV');
   oslNCDiv = document.createElement('div');
   oslNCDiv.id = "oslNCDiv";
   oslNCDiv.style.backgroundColor = '#DDDDFF';
   oslNCDiv.style.padding = '2px';
   oslNCDiv.style.fontSize = '14px';
   oslNCDiv.style.lineHeight = '16px';
   oslNCDiv.style.display = 'none';

   subDivHTML = '<label style="display:inline;"><input type="checkbox" id="_cbNCEnabled" />Highlight potential naming errors</label>';
   subDivHTML += '<br><i>Note: only active at at zoom level 4 and above</i>';
   oslNCDiv.innerHTML = oslGenerateCollapsibleSubDiv("NameCheck", subDivHTML);
   oslWindow.appendChild(oslNCDiv);

   // external links div
   oslAddLog('create extern links DIV');
   oslMLCDiv = document.createElement('div');
   oslMLCDiv.id = "oslMLCDiv";
   oslMLCDiv.style.backgroundColor = '#EEFFEE';
   oslMLCDiv.style.padding = '2px';
   oslMLCDiv.style.fontSize = '14px';
   oslMLCDiv.style.lineHeight = '16px';
   
   // add the anchors and auto-track checkboxes for external sites.  Note that the urls are blank at this stage,
   // they'll be filled in by oslMouseMoveAndUp()...
   subDivHTML = '<div id="_extlinksUK" style="display: none;">';
   subDivHTML += '<a href="" id="_linkOSOD" target="_osopendata">OS OpenData</a> <input type="checkbox" id="_cbAutoTrackOSOD"></input> | ';
   subDivHTML += '<a href="" id="_linkRWO" target="_roadworksorg">one.network</a> <input type="checkbox" id="_cbAutoTrackRWO"></input><br>';
   subDivHTML += '<div id="swgCtrls"><a href="" id="_linkSWG" target="_wiltshire">Wiltshire</a> <input type="checkbox" id="_cbAutoTrackSWG"></input></div>';
   subDivHTML += '<div id="lrrCtrls"><a href="" id="_linkLRR" target="_londonregister">London Roadworks</a> <input type="checkbox" id="_cbAutoTrackLRR"></input></div>';
   subDivHTML += '</div>';
   subDivHTML += '<br>(Checkboxes enable auto-tracking)';
   subDivHTML += '<br><br><a href="" id="_linkPermalink">Permalink</a>';
   oslMLCDiv.innerHTML = oslGenerateCollapsibleSubDiv("External resources", subDivHTML);
   oslWindow.appendChild(oslMLCDiv);
}
function oslSetupEventListeners()
{
   var idx;
   oslAddLog('adding event listeners');
   oslDragBar.addEventListener('mousedown', oslOSLDivMouseDown, false);
   oslDragBar.addEventListener('mouseup', oslOSLDivMouseUp, false);
   W.map.events.register("zoomend", null, oslMouseMoveAndUp);
   W.map.events.register("mouseout", null, oslMouseOut);
   document.getElementById('_cbNCEnabled').addEventListener('click', oslNCStateChange, false);

   for(idx = 0; idx < document.getElementsByName('oslOpenRoads_std').length; ++idx)
   {
      document.getElementsByName('oslOpenRoads_std')[idx].addEventListener('click', oslOpenRoadsStateChange, false);
   }
   if(oslAdvancedMode == true)
   {
      for(idx = 0; idx < document.getElementsByName('oslOpenRoads_adv').length; ++idx)
      {
         document.getElementsByName('oslOpenRoads_adv')[idx].addEventListener('click', oslOpenRoadsStateChange, false);
      }
   }

   for(idx = 0; idx < document.getElementsByName('oslMinMaxToggle').length; ++idx)
   {
      document.getElementsByName('oslMinMaxToggle')[idx].addEventListener('click', oslUIMinMax, false);
   }

   W.map.events.register("mousemove", null, oslMouseMoveAndUp);
   W.map.events.register("mouseup", null, oslMouseMoveAndUp);

   W.map.segmentLayer.events.register("featuresadded", null, oslNameCheckTrigger);
   W.map.segmentLayer.events.register("featuresremoved", null, oslNameCheckTrigger);
}
function oslSetupUIPosition()
{
   oslAddLog('adjusting UI position...');
   document.body.style.overflow = 'hidden';

   var vpHeight = window.innerHeight;
   var vpWidth = window.innerWidth;

   if
   (
      (oslUserPrefs.ui.y === null)||
      (oslUserPrefs.ui.x === null)||
      (oslUserPrefs.ui.y > vpHeight)||
      (oslUserPrefs.ui.x > vpWidth)||
      (oslUserPrefs.ui.y < 0)||
      (oslUserPrefs.ui.x < 0)
   )
   {
      oslOSLDivTop = document.getElementById('sidebar').getBoundingClientRect().top + (document.getElementById('sidebar').getBoundingClientRect().height / 2);
      oslOSLDivLeft = 8;
   }
   else
   {
      oslOSLDivTop = oslUserPrefs.ui.y;
      oslOSLDivLeft = oslUserPrefs.ui.x;
   }

   if(oslUserPrefs.ui.state === undefined) oslUserPrefs.ui.state = 'maximised';
   oslOSLDivTopMinimised = oslOSLDivTop;
   oslWindow.style.left = oslOSLDivLeft+'px';
   oslWindow.style.top = oslOSLDivTop+'px';
   if(oslUserPrefs.ui.state == 'maximised') oslWindowMaximise();
   else oslWindowMinimise();

   oslWindow.style.opacity = 1;   
}
function oslSetLayerZIndices()
{
   oslAddLog('setting layer zIndices...');
   for(var i=0; i < W.map.layers.length; i++)
   {
      if(W.map.layers[i].uniqueName == 'satellite_imagery')
      {
         oslBBDiv.style.zIndex = parseInt(W.map.layers[i].div.style.zIndex) + 1;
         oslSegGeoDiv.style.zIndex = parseInt(W.map.layers[i].div.style.zIndex) + 2;
      }
      if(W.map.layers[i].name == 'Spotlight') oslOSLMaskLayer = W.map.layers[i].div;
   }
}
function oslCheckAccessKey()
{
   var lastAccessKey = GM_getValue('_rwoAccessKey', null);
   var keyFound = false;
   if(lastAccessKey !== null)
   {
      lastAccessKey = parseInt(lastAccessKey);
      if((Date.now() - lastAccessKey) <= 10000)
      {
         keyFound = true;
      }
   }

   if(keyFound === false)
   {
      oslAddLog('No recent access key found, generating one for this tab and setting up message listener');
      oslAccessKeyUpdate();
      window.setInterval(oslAccessKeyUpdate,5000);

      GM_addValueChangeListener("_rwoPositionToWME", function()
      {
         if(arguments[3] === true)
         {
            oslAddLog('WME reposition request from one.network...');
            var tPos = new OpenLayers.LonLat();
            var posBits = arguments[2].split(',');
            tPos.lat = parseFloat(posBits[2]);
            tPos.lon = parseFloat(posBits[3]);
            tPos.transform(new OpenLayers.Projection("EPSG:4326"),new OpenLayers.Projection("EPSG:900913"));
            W.map.setCenter(tPos);
            var tZoom = parseInt(posBits[4]);
            if(tZoom < 0) tZoom = 0;
            if(tZoom > 10) tZoom = 10;
            W.map.zoomTo(tZoom);
         }
      });
   }
}
function oslLoadOrInitPrefs()
{
   // See if this system already has something in localStorage
   var userPrefs = localStorage.oslUserPrefs;
   if(userPrefs != undefined)
   {
      oslUserPrefs = JSON.parse(userPrefs);
   }

   // Fill out any undefined parts of the prefs object with default values - this works both to initialise
   // the object if nothing at all is stored in localStorage, and can also be used to initialise any newer
   // parameters that might get added later on which aren't yet defined in the localStorage copy
   if(oslUserPrefs.minmax == undefined) oslUserPrefs.minmax = [];
   if(oslUserPrefs.openRoads == undefined) oslUserPrefs.openRoads = [];
   if(oslUserPrefs.ui == undefined) oslUserPrefs.ui = {};
   if(oslUserPrefs.ui.x == undefined) oslUserPrefs.ui.x = null;
   if(oslUserPrefs.ui.y == undefined) oslUserPrefs.ui.y = null;
   if(oslUserPrefs.ui.state == undefined) oslUserPrefs.ui.state = null;

   // Import any older preferences set on this system, then remove from localStorage - this should only ever
   // occur the first time this version of the script is run after updating an existing setup, so there
   // wouldn't be anything already stored in these three properties that would get wiped out by the import.
   if(localStorage.oslOSLDivState != undefined)
   {
      oslUserPrefs.ui.state = localStorage.oslOSLDivState;
      localStorage.removeItem("oslOSLDivState");
   }
   if(localStorage.oslOSLDivLeft != undefined)
   {
      oslUserPrefs.ui.x = localStorage.oslOSLDivLeft;
      localStorage.removeItem("oslOSLDivLeft");
   }
   if(localStorage.oslOSLDivTop != undefined)
   {
      oslUserPrefs.ui.y = localStorage.oslOSLDivTop;
      localStorage.removeItem("oslOSLDivTop");
   }
}
function oslApplyPrefs()
{
   var i;
   // subDiv states
   for(i = 0; i < oslUserPrefs.minmax.length; ++i)
   {
      oslSubDivSetState(oslUserPrefs.minmax[i][0], oslUserPrefs.minmax[i][1]);
   }

   // Open Roads options
   for(i = 0; i < oslUserPrefs.openRoads.length; ++i)
   {
      var cbID = oslUserPrefs.openRoads[i][0];
      if(document.getElementById(cbID) != undefined)
      {
         document.getElementById(cbID).checked = oslUserPrefs.openRoads[i][1];
      }
   } 
   // Force-disable Open Roads display on startup to avoid problems if the current WME viewport is 
   // showing a more densely mapped area than before...
   document.getElementById('_cbOpenRoadsEnabled').checked = false;
}
function oslWriteUserPrefs()
{
   localStorage.oslUserPrefs = JSON.stringify(oslUserPrefs);
}
function oslUpdateMinMax(key, value)
{
   var found = false;
   for(var i = 0; i < oslUserPrefs.minmax.length; ++i)
   {
      if(oslUserPrefs.minmax[i][0] == key)
      {
         oslUserPrefs.minmax[i][1] = value;
         found = true;
         break;
      }
   }
   if(found == false)
   {
      oslUserPrefs.minmax.push([key, value]);
   }
   oslWriteUserPrefs();
}
function oslUpdateHighlightCBs(key)
{
   var found = false;
   var value = document.getElementById(key).checked;
   for(var i = 0; i < oslUserPrefs.openRoads.length; ++i)
   {
      if(oslUserPrefs.openRoads[i][0] == key)
      {
         oslUserPrefs.openRoads[i][1] = value;
         found = true;
         break;
      }
   }
   if(found == false)
   {
      oslUserPrefs.openRoads.push([key, value]);
   }
   oslWriteUserPrefs();
}
function oslFinaliseSetup()
{
   oslEnableAdvancedOptions();
   oslLoadOrInitPrefs();

   oslSetupBBDiv();
   oslSetupORDiv();
   oslSetupUI();
   oslSetupEventListeners();      
   oslSetupUIPosition();
   oslSetLayerZIndices();
   oslCheckAccessKey();

   oslApplyPrefs();

   oslOffsetToolbar = document.getElementById('map').contains(document.getElementById('toolbar'));
   window.setInterval(oslTenthSecondTick,100);
   oslDoneOnload = true;
}
function oslWazeBits()
{
   if(document.location.href.indexOf('user') !== -1)
   {
      oslAddLog('User profile page detected, script is disabled...');
      return;
   }

   oslAddLog('adding WazeBits...'+oslWazeBitsPresent);
   if((oslWazeBitsPresent & 0x01) === 0)
   {
      if(oslNestedTypeof("W.selectionManager") != "undefined")
      {
         W = unsafeWindow.W;
         oslAddLog('   W.selectionManager OK');
         oslWazeBitsPresent |= 0x01;
      }
   }

   if((oslWazeBitsPresent & 0x02) === 0)
   {
      if(typeof(OpenLayers) != "undefined")
      {
         oslAddLog('   OpenLayers OK');
         oslWazeBitsPresent |= 0x02;
      }
   }

   if((oslWazeBitsPresent & 0x04) === 0)
   {
      if(oslNestedTypeof("W.map.segmentLayer.div.parentElement.id") != "undefined")
      {
         if(document.getElementById(W.map.segmentLayer.div.parentElement.id) !== null)
         {
            oslAddLog('   WazeMap OK');
            oslWazeBitsPresent |= 0x04;
            oslWazeMapElement = document.getElementById(W.map.segmentLayer.div.parentElement.id);
         }
      }
   }

   if((oslWazeBitsPresent & 0x08) === 0)
   {
      if(oslNestedTypeof("W.model.countries") != "undefined")
      {
         oslAddLog('   W.model.countries OK');
         oslWazeBitsPresent |= 0x08;
      }
   }

   if(oslWazeBitsPresent != 0x0F)
   {
      window.setTimeout(oslWazeBits,250);
   }
   else
   {
      oslFinaliseSetup();
   }
}
function oslAccessKeyUpdate()
{
   GM_setValue('_rwoAccessKey',Date.now());
}
function oslInitialise()
{
   oslAddLog('initialise()');

   var nameTestMode = false;
   // oslWazeifyStreetName() functionality test code
   if(nameTestMode === true)
   {
      console.log(oslWazeifyStreetName("North Street", true));
      console.log(oslWazeifyStreetName("South Street", true));
      console.log(oslWazeifyStreetName("East Street", true));
      console.log(oslWazeifyStreetName("West Street", true));
      console.log(oslWazeifyStreetName("North Town Street", true));
      console.log(oslWazeifyStreetName("South Town Street", true));
      console.log(oslWazeifyStreetName("East Town Street", true));
      console.log(oslWazeifyStreetName("West Town Street", true));
      console.log(oslWazeifyStreetName("Green Street", true));
      console.log(oslWazeifyStreetName("The Boulevard", true));
      console.log(oslWazeifyStreetName("Aston Boulevard West", true));
      console.log(oslWazeifyStreetName("Saint Albans Way", true));
      console.log(oslWazeifyStreetName("Orchard On The Green", true));
      console.log(oslWazeifyStreetName("The Orchard On The Green", true));
      console.log(oslWazeifyStreetName("The Avenue", true));
      console.log(oslWazeifyStreetName("High Road Ickenham", true));
      console.log(oslWazeifyStreetName("Westway Avenue", true));
      console.log(oslWazeifyStreetName("Parkway Park", true));
      console.log(oslWazeifyStreetName("Parkway Crescent", true));
      console.log(oslWazeifyStreetName("Breakspear Road North", true));
      console.log(oslWazeifyStreetName("Breakspear Road South", true));
      console.log(oslWazeifyStreetName("Breakspear Road East", true));
      console.log(oslWazeifyStreetName("Breakspear Road West", true));
      console.log(oslWazeifyStreetName("Kensal Green Way", true));
      console.log(oslWazeifyStreetName("Great North Road", true));
      return;
   }

   // inject gazetteer data
   var gazscript = document.createElement("script");
   gazscript.setAttribute('type','text/javascript');
   gazscript.setAttribute('charset','windows-1252');
   gazscript.src = oslGazetteerURL;
   document.head.appendChild(gazscript);
   oslMergeGazData = true;

   // initialise persistent vars
   sessionStorage.zoom = 0;
   sessionStorage.lat = '';
   sessionStorage.lon = '';
   sessionStorage.myCity = '';
   sessionStorage.prevCity = '';
   sessionStorage.cityChangeEastings = 0;
   sessionStorage.cityChangeNorthings = 0;
   sessionStorage.cityNameRB = 'optUseExisting';
   sessionStorage.oslTabCreated = 0;

   oslWazeBits();
}

// External site helper functions
const hlp_ONE =
{
   // one.network helper functions
   addLog: function(logtext)
   {
      console.log('ONE: '+logtext);
   },
   clickToWME: function()
   {
      var elginLat = Elgin.map.getCenter().lat();
      var elginLon = Elgin.map.getCenter().lng();
      var elginZoom = Elgin.map.getZoom() - 12;
      if(elginZoom < 0) elginZoom = 0;
      if(elginZoom > 10) elginZoom = 10;

      // check for an active WME tab...
      var tabFound = false;
      var lastAccessKey = GM_getValue('_rwoAccessKey', null);
      if(lastAccessKey !== null)
      {
         lastAccessKey = parseInt(lastAccessKey);
         if((Date.now() - lastAccessKey) <= 10000)
         {
            // the access key was written within the last 10s which implies an active tab, so send it the repositioning data
            var toWrite = Date.now()+','+lastAccessKey+','+elginLat+','+elginLon+','+elginZoom;
            GM_setValue('_rwoPositionToWME',toWrite);
            tabFound = true;
         }
      }

      if(tabFound === false)
      {
         // couldn't find a WME tab to reposition, so open a new one...
         var wmeURL = 'https://www.waze.com/editor?';
         wmeURL += 'lon='+elginLon;
         wmeURL += '&lat='+elginLat;
         wmeURL += '&zoom='+elginZoom;
         window.open(wmeURL);
      }
   },
   init: function()
   {
      hlp_ONE.addLog('initialise()');
      hlp_ONE.addLog('waiting for map objects...');
      var waitSomeMore = false;

      try
      {
         if(Elgin === undefined)
         {
            waitSomeMore = true;
         }
         else if (Elgin.map === undefined)
         {
            waitSomeMore = true;
         }

         if(google === undefined)
         {
            waitSomeMore = true;
         }
         else if(google.maps === undefined)
         {
            waitSomeMore = true;
         }
      }
      catch
      {
         waitSomeMore = true;
      }

      if(document.getElementById('map-canvas') === null)
      {
         waitSomeMore = true;
      }

      if(document.getElementsByClassName('gm-bundled-control-on-bottom').length == 0)
      {
         waitSomeMore = true;
      }

      if(waitSomeMore)
      {
         window.setTimeout(hlp_ONE.init,500);
         return;
      }

      hlp_ONE.addLog('all required objects found...');

      // Add "open in WME" button...
      var tBtnDiv = document.createElement('div');
      tBtnDiv.id = 'rwoWMELink';
      var tHTML = '<div class="gmnoprint" draggable="false" controlwidth="40" controlheight="40" style="margin: 10px; user-select: none; position: absolute; bottom: 220px; right: 40px;">';
      tHTML += '<div class="gmnoprint" controlwidth="40" controlheight="40" style="position: absolute; left: 0px; top: 0px;">';
      tHTML += '<div draggable="false" style="user-select: none; box-shadow: rgba(0, 0, 0, 0.3) 0px 1px 4px -1px; border-radius: 2px; cursor: pointer; background-color: rgb(255, 255, 128); width: 40px; height: 40px;">';
      tHTML += '<button draggable="false" title="Open in WME" aria-label="Open in WME" type="button" class="gm-control-active" style="background: none; display: block; border: 0px; margin: 0px; padding: 0px; position: relative; cursor: pointer; user-select: none; overflow: hidden; width: 40px; height: 40px; top: 0px; left: 0px;"><strong>WME</strong></button>';
      tHTML += '</div></div></div>';
      tBtnDiv.innerHTML = tHTML;
      document.getElementById('map-canvas').firstChild.appendChild(tBtnDiv);

      document.getElementById('rwoWMELink').addEventListener('click', hlp_ONE.clickToWME, false);

      // extract the coords/zoom from the url...
      var userloc = document.location.href;
      var latpos = userloc.indexOf("?lat=");
      var lonpos = userloc.indexOf("&lon=");
      var zpos = userloc.indexOf("&z=");
      if((latpos != -1)&&(lonpos != -1)&&(zpos != -1))
      {
         var rwoLat = parseFloat(userloc.substr(latpos+5,lonpos-(latpos+5)));
         var rwoLon = parseFloat(userloc.substr(lonpos+5,zpos-(lonpos+5)));
         var rwoZoom = parseInt(userloc.substr(zpos+3,2))+12;
         Elgin.map.setCenter(new google.maps.LatLng(rwoLat,rwoLon));
         Elgin.map.setZoom(rwoZoom);
         hlp_ONE.addLog('map repositioned');
      }
   }
};

const hlp_WSW =
{
   // Wiltshire Streetworks helper functions
   addLog: function(logtext)
   {
      console.log('WSW: '+logtext);
   },
   init: function()
   {
      hlp_WSW.addLog('initialise()');
      hlp_WSW.addLog('waiting for map objects...');
      var waitSomeMore = false;

      if((map === null) || (map === undefined))
      {
         waitSomeMore = true;
         hlp_WSW.addLog('map not found...');
      }
      else
      {
         if(map.setCenter === undefined)
         {
            waitSomeMore = true;
            hlp_WSW.addLog('map.setCenter not found...');
         }
         if(map.zoomTo === undefined)
         {
            waitSomeMore = true;
            hlp_WSW.addLog('map.zoomTo not found...');
         }
      }

      if((OpenLayers === null) || (OpenLayers === undefined))
      {
         waitSomeMore = true;
         hlp_WSW.addLog('OpenLayers not found...');
      }
      else
      {
         if(OpenLayers.LonLat === undefined)
         {
            waitSomeMore = true;
            hlp_WSW.addLog('OpenLayers.LonLat not found...');
         }
      }

      if(waitSomeMore)
      {
         window.setTimeout(hlp_WSW.init,500);
         return;
      }

      hlp_WSW.addLog('all required objects found...');

      // extract the coords/zoom from the url...
      var userloc = document.location.href;
      var latpos = userloc.indexOf("?lat=");
      var lonpos = userloc.indexOf("&lon=");
      var zpos = userloc.indexOf("&z=");
      if((latpos != -1)&&(lonpos != -1)&&(zpos != -1))
      {
         var swgLat = parseFloat(userloc.substr(latpos+5,lonpos-(latpos+5)));
         var swgLon = parseFloat(userloc.substr(lonpos+5,zpos-(lonpos+5)));
         var swgZoom = parseInt(userloc.substr(zpos+3,2));
         map.setCenter(new OpenLayers.LonLat(swgLon,swgLat));
         map.zoomTo(swgZoom);
         hlp_WSW.addLog('map repositioned');
      }
   }
};

const hlp_LRR =
{
   // London Roadworks Register helper functions
   addLog: function(logtext)
   {
      console.log('LRR: '+logtext);
   },
   init: function()
   {
      // trap the page reload that occurs when the map view is generated...
      if(document.location.href.indexOf('home') == -1)
      {
         hlp_LRR.addLog('page reloaded during map generation - redirecting to second stage initialisation...');
         hlp_LRR.initPartDeux();
         return;
      }

      hlp_LRR.addLog('initialise()');
      hlp_LRR.addLog('waiting for search page to load...');
      var waitSomeMore = false;
      if(document.getElementsByName('mapResults')[0].length < 1)
      {
         waitSomeMore = true;
      }
      if(document.getElementsByName('postCode')[0].length < 1)
      {
         waitSomeMore = true;
      }

      if(waitSomeMore === true)
      {
         window.setTimeout(hlp_LRR.init,500);
         return;
      }

      // extract the coords/zoom from the url...
      var userloc = document.location.href;
      var epos = userloc.indexOf("?e=");
      var npos = userloc.indexOf("&n=");
      var zpos = userloc.indexOf("&z=");
      if((epos != -1)&&(npos != -1)&&(zpos != -1))
      {
         var lrrLon = userloc.substr(epos+3,npos-(epos+3));
         var lrrLat = userloc.substr(npos+3,zpos-(npos+3));
         var lrrZoom = userloc.substr(zpos+3,2);
         sessionStorage.setItem('lrrLon',lrrLon);
         sessionStorage.setItem('lrrLat',lrrLat);
         sessionStorage.setItem('lrrZoom',lrrZoom);

         hlp_LRR.addLog('accessing map...');
         // first set a "safe" postcode as the search criteria - whilst asking for the map view to be generated without any
         // search terms usually works OK, from time to time the site throws a wobbler and refuses to do anything without
         // having the search narrowed down a bit...
         document.getElementsByName('postCode')[0].value="EC1A 1AA";
         document.getElementsByName('mapResults')[0].click();
      }
   },
   initPartDeux: function()
   {
      hlp_LRR.addLog('waiting for map objects...');
      var waitSomeMore = false;

      if(map === undefined)
      {
         waitSomeMore = true;
      }

      if(OpenLayers === undefined)
      {
         waitSomeMore = true;
      }

      if(waitSomeMore)
      {
         window.setTimeout(hlp_LRR.initPartDeux,500);
         return;
      }

      hlp_LRR.addLog('all required objects found...');
      var lrrLon = parseInt(sessionStorage.getItem('lrrLon'));
      var lrrLat = parseInt(sessionStorage.getItem('lrrLat'));
      var lrrZoom = parseInt(sessionStorage.getItem('lrrZoom'));
      map.setCenter(new OpenLayers.LonLat(lrrLon,lrrLat));
      map.zoomTo(lrrZoom);
      hlp_LRR.addLog('map repositioned');
   }
};

const hlp_ODM =
{
   // OS OpenData map helper functions
   eastings: 0,
   northings: 0,
   zoom: 0,

   addLog: function(logtext)
   {
      console.log('ODM: '+logtext);
   },
   resizeMap: function()
   {
      hlp_ODM.addLog('resize');
      // resizes map viewport whenever browser window changes size
      var newWidth = window.innerWidth - 10;
      var newHeight = window.innerHeight - 10;
      hlp_ODM.addLog('  '+newWidth+' x '+newHeight);

      osMap.size.w = newWidth;
      osMap.size.h = newHeight;

      var elm = document.getElementById("map");
      // first we need to move the map DIV out of its original placement heirarchy, so that
      // it can flow to the left of the browser window rather than to the left of its
      // parent block...
      document.children[0].appendChild(elm);
      // then we resize it to fill the available space
      elm.style.height = newHeight+'px';
      elm.style.width = newWidth+'px';
      elm.style.left = "4px";

      // force a redraw of the map object to use the resized viewport correctly
      osMap.render('map');
   },
   recentreMap: function()
   {
      // call the OS provided functions required to point the map at a
      // given grid ref and zoom level
      var mymapCenter = new OpenSpace.MapPoint(this.eastings, this.northings);
      osMap.setCenter(mymapCenter, this.zoom);
   },
   hideThing: function(thing)
   {
      if((thing === null)||(thing === undefined))
      {
         hlp_ODM.addLog('  not found');
      }
      else
      {
         thing.style.visibility = "hidden";
         thing.style.position = "absolute";
         thing.style.top = "0px";
         hlp_ODM.addLog('  hidden');
      }
   },
   hideElement: function(elmID)
   {
      hlp_ODM.addLog('hiding element '+elmID);
      var elm = document.getElementById(elmID);
      hlp_ODM.hideThing(elm);
   },
   hideTagName: function(elmID)
   {
      hlp_ODM.addLog('hiding tag '+elmID);
      var elm = document.getElementsByTagName(elmID)[0];
      hlp_ODM.hideThing(elm);
   },
   fakeOnload: function()
   {
      // remove the non-map stuff from the page...
      hlp_ODM.hideElement("openspace.mapbuilder.header");
      hlp_ODM.hideElement("col2");
      hlp_ODM.hideTagName("h1");
      hlp_ODM.hideElement("div1");

      // reduce the width of the whitespace around the map viewport
      document.getElementById("wrapper").style.padding = '4px';
      // resize the map viewport...
      hlp_ODM.resizeMap();

      window.addEventListener('resize', hlp_ODM.resizeMap, true);

      // extract the starting coords/zoom from the url...
      var userloc = document.location.href;
      var epos = userloc.indexOf("?e=");
      var npos = userloc.indexOf("&n=");
      var zpos = userloc.indexOf("&z=");
      if((epos != -1)&&(npos != -1)&&(zpos != -1))
      {
         this.eastings = userloc.substr(epos+3,npos-(epos+3));
         this.northings = userloc.substr(npos+3,zpos-(npos+3));
         this.zoom = userloc.substr(zpos+3,2);
         //...then recentre the map
         hlp_ODM.recentreMap();
      }
   },
   init: function()
   {
      hlp_ODM.addLog('initialise()');
      if(osMap === undefined) window.setTimeout(hlp_ODM.init,500);
      else hlp_ODM.fakeOnload();
   }
};

oslBootstrap();