WME OpenData

Provides access to certain OS OpenData products within the WME environment

  1. // ==UserScript==
  2. // @name WME OpenData
  3. // @namespace http://greasemonkey.chizzum.com
  4. // @description Provides access to certain OS OpenData products within the WME environment
  5. // @include https://*.waze.com/*editor*
  6. // @exclude https://editor-beta.waze.com/*
  7. // @exclude https://beta.waze.com/*
  8. // @include https://one.network/*
  9. // @include https://public.londonworks.gov.uk/roadworks/*
  10. // @include http://public.londonworks.gov.uk/roadworks/*
  11. // @include https://search-property-information.service.gov.uk/search/search-by-map/*
  12. // @grant GM_setValue
  13. // @grant GM_getValue
  14. // @grant GM_addValueChangeListener
  15. // @grant unsafeWindow
  16. // @version 4.0
  17. // ==/UserScript==
  18.  
  19. // Contains Ordnance Survey data Crown copyright and database right 2024
  20. //
  21. // Contents of the locatorData_*.js files are derived under the
  22. // Open Government Licence from the OS Open Names and Open Roads datasets
  23. //
  24. // Contents of the gazetteer.js file are derived under the
  25. // Open Government Licence from the OS Open Names dataset
  26.  
  27. /*
  28. =======================================================================================================================
  29. DONE FOR THIS RELEASE
  30. =======================================================================================================================
  31. Road names are now applied via the scripting SDK
  32.  
  33.  
  34. =======================================================================================================================
  35. Bug fixes - MUST BE CLEARED BEFORE RELEASE
  36. =======================================================================================================================
  37.  
  38. =======================================================================================================================
  39. Things to be checked
  40. =======================================================================================================================
  41.  
  42. */
  43.  
  44. /* JSHint Directives */
  45. /* globals W: true */
  46. /* globals getWmeSdk: true */
  47. /* globals OpenLayers: true */
  48. /* globals Elgin: true */
  49. /* globals gazetteerData: true */
  50. /* globals oslRoadNameMatches: true */
  51. /* globals map: true */
  52. /* globals GM_setValue: true */
  53. /* globals GM_getValue: true */
  54. /* globals GM_addValueChangeListener: true */
  55. /* globals unsafeWindow: true */
  56. /* globals trustedTypes: */
  57. /* jshint evil: true */
  58. /* jshint bitwise: false */
  59. /* jshint eqnull: true */
  60. /* jshint esversion: 11 */
  61. /* jshint undef: true */
  62. /* jshint unused: true */
  63.  
  64. const oslVersion = '4.0';
  65. const oslUpdateURL = 'https://greasyfork.org/scripts/1941-wme-to-os-link';
  66. const oslBlockPath = 'https://greasemonkey.chizzum.com/osl_v3.0/';
  67. const oslGazetteerURL = 'https://chizzum.com/greasemonkey/gaz_v5/_gazetteer.js';
  68. const oslPi = 3.14159265358979;
  69. const oslPiDiv180 = (oslPi / 180);
  70. const osl180DivPi = (180 / oslPi);
  71. const oslLocatorBlockSize = 1000;
  72. const oslCacheDecayPeriod = 60;
  73.  
  74. const oslNameTextPts = '12pt';
  75. const oslNameHalfPix = 12;
  76.  
  77.  
  78. const GAZ_ELM =
  79. {
  80. Name: 0,
  81. CEast: 1,
  82. CNorth: 2,
  83. Type: 3,
  84. Area: 4,
  85. LEast: 5,
  86. LNorth: 6,
  87. REast: 7,
  88. RNorth: 8
  89. };
  90. const OSL_ELM =
  91. {
  92. RoadName: 0,
  93. RoadNumber: 1,
  94. BoundW: 2,
  95. BoundE: 3,
  96. BoundS: 4,
  97. BoundN: 5,
  98. Geometry: 6,
  99. AreaName: 7,
  100. AltName: 8,
  101. Classification: 9,
  102. Function: 10,
  103. Form: 11,
  104. Structure: 12,
  105. IsPrimary: 13,
  106. IsTrunk: 14,
  107. MAX: 15
  108. };
  109. const OSL_MODE =
  110. {
  111. Conversion: 0,
  112. OpenNames: 1,
  113. NameCheck: 2,
  114. OpenRoads: 3
  115. };
  116. const OSL_BBMODE =
  117. {
  118. Init: 0,
  119. Match: 1,
  120. Other: 2,
  121. Finalise: 3
  122. };
  123. const OSL_ROADRENDERER =
  124. {
  125. Init: 0,
  126. Render: 1,
  127. Finalise: 2,
  128. Erase: 3
  129. };
  130.  
  131. const oslRoadClassifications = new Array
  132. (
  133. 'Undefined',
  134. 'Motorway',
  135. 'A Road',
  136. 'B Road',
  137. 'Classified Unnumbered',
  138. 'Unclassified',
  139. 'Not Classified',
  140. 'Unknown'
  141. );
  142.  
  143. const oslRoadFunctions = new Array
  144. (
  145. 'Undefined',
  146. 'Motorway',
  147. 'A Road',
  148. 'B Road',
  149. 'Minor Road',
  150. 'Local Road',
  151. 'Local Access Road',
  152. 'Restricted Local Access Road',
  153. 'Secondary Access Road'
  154. );
  155. const oslStrokeColoursByFunction = new Array
  156. (
  157. "red",
  158. "deepskyblue",
  159. "limegreen",
  160. "darkorange",
  161. "yellow",
  162. "white",
  163. "grey",
  164. "tan",
  165. "grey"
  166. );
  167. const oslRoadStructures = new Array
  168. (
  169. '',
  170. '[Tunnel]',
  171. '[Bridge]'
  172. );
  173. const oslFormsOfWay = new Array
  174. (
  175. 'Undefined',
  176. 'Single Carriageway',
  177. 'Dual Carriageway',
  178. 'Slip Road',
  179. 'Roundabout',
  180. 'Collapsed Dual Carriageway',
  181. 'Guided Busway',
  182. 'Shared Use Carriageway'
  183. );
  184.  
  185. // List of all road name suffixes, giving both their full length and abbreviated forms,
  186. // for use when automatically translating the standard OS names into the abbreviated
  187. // forms we prefer to use in WME.
  188. //
  189. // Note that some suffixes are included where the abbreviated form is identical to the
  190. // full length form - these are present to act as guidance to the translation code so
  191. // that it recognises which part of the OS name is the suffix - remember that the suffix
  192. // isn't always the last word in the name, as we also need to consider names that have
  193. // things after the suffix - e.g. Breakspear Road South, High Street Eastcote etc. If
  194. // these weren't present then we could end up incorrectly translating earlier parts of
  195. // the name which match one of the other suffixes - e.g. West Green Way...
  196. const oslNameAbbreviations = new Array
  197. (
  198. 'Avenue','Ave',
  199. 'Boulevard','Blvd',
  200. 'Broadway','Bdwy',
  201. 'Circus','Cir',
  202. 'Close','Cl',
  203. 'Court','Ct',
  204. 'Crescent','Cr',
  205. 'Drive','Dr',
  206. 'Garden','Gdn',
  207. 'Gardens','Gdns',
  208. 'Green','Gn',
  209. 'Grove','Gr',
  210. 'Lane','Ln',
  211. 'Mews','Mews',
  212. 'Mount','Mt',
  213. 'Place','Pl',
  214. 'Park','Pk',
  215. 'Ridge','Rdg',
  216. 'Road','Rd',
  217. 'Square','Sq',
  218. 'Street','St',
  219. 'Terrace','Ter',
  220. 'Valley','Val',
  221. 'By-pass','Bypass',
  222. 'Way','Way',
  223. 'Hill','Hill'
  224. );
  225.  
  226.  
  227. let oslUserPrefs = {};
  228.  
  229. let oslGazetteerData = [];
  230.  
  231. let oslAdvancedMode = false;
  232. let oslEvalString = '';
  233. let oslLoadingMsg = false;
  234. let oslMLCDiv = null;
  235. let oslOSLDiv = null;
  236. let oslBBDiv = null;
  237. let oslNamesDiv = null;
  238. let oslGazTagsDiv = null;
  239. let oslPrevHighlighted = null;
  240. let oslSegmentHighlighted = false;
  241. let oslPrevMouseX = null;
  242. let oslPrevMouseY = null;
  243. let oslDivDragging = false;
  244. let oslPrevSelected = null;
  245. let oslDoOSLUpdate = false;
  246. let oslMousepos = null;
  247. let oslMousePixelpos = null;
  248. let oslLastViewportWidth = null;
  249. let oslDoneOnload = false;
  250. let oslPrevStreetName = '';
  251. let oslMergeGazData = false;
  252. let oslOSLMaskLayer = null;
  253. let oslOSLNameCheckTimer = 0;
  254. let oslOSLNCSegments = [];
  255. let oslInUK = false;
  256. let oslInLondon = false;
  257.  
  258. let oslNorthings = null;
  259. let oslEastings = null;
  260. let oslLatitude = null;
  261. let oslLongitude = null;
  262.  
  263. let oslBlocksToLoad = [];
  264. let oslBlocksToTest = [];
  265.  
  266. let oslVPLeft = 0;
  267. let oslVPRight = 0;
  268. let oslVPBottom = 0;
  269. let oslVPTop = 0;
  270. let oslBBDivInnerHTML = '';
  271.  
  272. let oslEvalEBlock = 0;
  273. let oslEvalNBlock = 0;
  274. let oslBlockData = null;
  275. let oslBlockCacheList = [];
  276. let oslBlockCacheTestTimer = (oslCacheDecayPeriod * 10);
  277. let oslSegGeoDivInnerHTML = '';
  278. let oslNamesDivInnerHTML = '';
  279.  
  280. let oslONC_E = null;
  281. let oslONC_N = null;
  282. let oslEBlock_min = null;
  283. let oslEBlock_max = null;
  284. let oslNBlock_min = null;
  285. let oslNBlock_max = null;
  286.  
  287. let oslOSLDivLeft;
  288. let oslOSLDivTop;
  289. let oslSegGeoDiv;
  290. let oslWazeMapElement;
  291. let oslDragBar;
  292. let oslWindow;
  293. let oslOSLDivTopMinimised;
  294. let oslNCDiv;
  295. let oslSegGeoUIDiv;
  296.  
  297. let oslOffsetToolbar = false;
  298. let oslMOAdded = false;
  299.  
  300. let oslLocatorElements = null;
  301. let oslUsingNewName = false;
  302. let oslUseName = false;
  303. let oslCityName = '';
  304. let oslCountyName = '';
  305. let oslUseAlt = false;
  306.  
  307. let oslRORCenter = null;
  308. let oslRORZoom = null;
  309.  
  310. let oslSDK = null;
  311.  
  312. function oslBootstrap()
  313. {
  314. if(document.location.host == 'one.network')
  315. {
  316. hlp_ONE.init();
  317. }
  318. else if(document.location.host == 'search-property-information.service.gov.uk')
  319. {
  320. hlp_ODM.init();
  321. }
  322. else if(document.location.host == 'public.londonworks.gov.uk')
  323. {
  324. hlp_LRR.init();
  325. }
  326. else
  327. {
  328. oslInitialise();
  329. }
  330. }
  331. function oslAddLog(logtext)
  332. {
  333. console.log('WMEOpenData: '+logtext);
  334. }
  335. function oslModifySrc(srcIn)
  336. {
  337. if(typeof trustedTypes === "undefined")
  338. {
  339. return srcIn;
  340. }
  341. else
  342. {
  343. const escapeSrcPolicy = trustedTypes.createPolicy("name", {createScriptURL: (to_escape) => to_escape});
  344. return escapeSrcPolicy.createScriptURL(srcIn);
  345. }
  346. }
  347. function oslModifyHTML(htmlIn)
  348. {
  349. if(typeof trustedTypes === "undefined")
  350. {
  351. return htmlIn;
  352. }
  353. else
  354. {
  355. const escapeHTMLPolicy = trustedTypes.createPolicy("forceInner", {createHTML: (to_escape) => to_escape});
  356. return escapeHTMLPolicy.createHTML(htmlIn);
  357. }
  358. }
  359. function oslGetExtent()
  360. {
  361. // From DaveAcincy
  362. let extent = new OpenLayers.Bounds(oslSDK.Map.getMapExtent());
  363. extent = extent.transform('EPSG:4326', 'EPSG:3857');
  364. return extent;
  365. }
  366. function oslConvertLonLatXY(lon, lat)
  367. {
  368. let tPos = new OpenLayers.LonLat(lon, lat);
  369. tPos.transform(new OpenLayers.Projection("EPSG:4326"),new OpenLayers.Projection("EPSG:900913"));
  370. return{x:tPos.lon, y:tPos.lat};
  371. }
  372. function oslConvertXYLonLat(x, y)
  373. {
  374. let tPos = new OpenLayers.LonLat(x, y);
  375. tPos.transform(new OpenLayers.Projection("EPSG:900913"),new OpenLayers.Projection("EPSG:4326"));
  376. return{lon:tPos.lon, lat:tPos.lat};
  377. }
  378. function oslGetSegmentLayer()
  379. {
  380. return W.map.getLayerByUniqueName('segments');
  381. }
  382.  
  383. //-----------------------------------------------------------------------------------------------------------------------------------------
  384. // all code between here and the next ------------- marker line is a stripped down version of the original from Paul Dixon
  385. //
  386. // * GeoTools javascript coordinate transformations
  387. // * http://www.nearby.org.uk/tests/geotools2.js
  388. // *
  389. // * This file copyright (c)2005 Paul Dixon (paul@elphin.com)
  390. // *
  391. // * This program is free software; you can redistribute it and/or
  392. // * modify it under the terms of the GNU General Public License
  393. // * as published by the Free Software Foundation; either version 2
  394. // * of the License, or (at your option) any later version.
  395. // *
  396. // * This program is distributed in the hope that it will be useful,
  397. // * but WITHOUT ANY WARRANTY; without even the implied warranty of
  398. // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  399. // * GNU General Public License for more details.
  400. // *
  401. // * You should have received a copy of the GNU General Public License
  402. // * along with this program; if not, write to the Free Software
  403. // * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  404. // *
  405. // * ---------------------------------------------------------------------------
  406. // *
  407. // * Credits
  408. // *
  409. // * The algorithm used by the script for WGS84-OSGB36 conversions is derived
  410. // * from an OSGB spreadsheet (www.gps.gov.uk) with permission. This has been
  411. // * adapted into Perl by Ian Harris, and into PHP by Barry Hunter. Conversion
  412. // * accuracy is in the order of 7m for 90% of Great Britain, and should be
  413. // * be similar to the conversion made by a typical GPSr
  414. // *
  415. // * See accompanying documentation for more information
  416. // * http://www.nearby.org.uk/tests/GeoTools2.html
  417. function oslOSGBtoWGS(oseast, osnorth)
  418. {
  419. const a = 6377563.396;
  420. const b = 6356256.910;
  421. const e0 = 400000;
  422. const n0 = -100000;
  423. const f0 = 0.999601272;
  424. const PHI0 = 49.00000;
  425. const LAM0 = -2.00000;
  426. const RadPHI0 = PHI0 * oslPiDiv180;
  427. const RadLAM0 = LAM0 * oslPiDiv180;
  428.  
  429. //Compute af0, bf0, e squared (e2), n and Et
  430. const af0 = a * f0;
  431. const bf0 = b * f0;
  432. const e2 = (Math.pow(af0,2) - Math.pow(bf0,2)) / Math.pow(af0,2);
  433. const n = (af0 - bf0) / (af0 + bf0);
  434. let Et = oseast - e0;
  435.  
  436. //Compute initial value for oslLatitude (PHId) in radians
  437. let PHI1 = ((osnorth - n0) / af0) + RadPHI0;
  438. let M = oslMarc(bf0, n, RadPHI0, PHI1);
  439. let PHId = ((osnorth - n0 - M) / af0) + PHI1;
  440. while (Math.abs(osnorth - n0 - M) > 0.00001)
  441. {
  442. PHId = ((osnorth - n0 - M) / af0) + PHI1;
  443. M = oslMarc(bf0, n, RadPHI0, PHId);
  444. PHI1 = PHId;
  445. }
  446.  
  447. //Compute nu, rho and eta2 using value for PHId
  448. let nu = af0 / (Math.sqrt(1 - (e2 * ( Math.pow(Math.sin(PHId),2)))));
  449. let rho = (nu * (1 - e2)) / (1 - (e2 * Math.pow(Math.sin(PHId),2)));
  450. let eta2 = (nu / rho) - 1;
  451.  
  452. //Compute Latitude/Longitude
  453. let tanPHId = Math.tan(PHId);
  454. let tanPHIdSquared = Math.pow(tanPHId, 2);
  455. let tanPHIdPowFour = Math.pow(tanPHId, 4);
  456. let cosPHId = Math.cos(PHId);
  457. let cosPHIdPowNegOne = Math.pow(cosPHId, -1);
  458. let nuPowThree = Math.pow(nu, 3);
  459. let nuPowFive = Math.pow(nu, 5);
  460.  
  461. let VII = tanPHId / (2 * rho * nu);
  462. let VIII = (tanPHId / (24 * rho * nuPowThree)) * (5 + (3 * tanPHIdSquared) + eta2 - (9 * eta2 * tanPHIdSquared));
  463. let IX = (tanPHId / (720 * rho * nuPowFive)) * (61 + (90 * tanPHIdSquared) + (45 * tanPHIdPowFour));
  464. oslLatitude = osl180DivPi * (PHId - (Math.pow(Et,2) * VII) + (Math.pow(Et,4) * VIII) - (Math.pow(Et, 6) * IX));
  465.  
  466. let X = cosPHIdPowNegOne / nu;
  467. let XI = (cosPHIdPowNegOne / (6 * nuPowThree)) * ((nu / rho) + (2 * tanPHIdSquared));
  468. let XII = (cosPHIdPowNegOne / (120 * nuPowFive)) * (5 + (28 * tanPHIdSquared) + (24 * tanPHIdPowFour));
  469. let XIIA = (cosPHIdPowNegOne / (5040 * Math.pow(nu,7))) * (61 + (662 * tanPHIdSquared) + (1320 * tanPHIdPowFour) + (720 * (Math.pow(tanPHId,6))));
  470. oslLongitude = osl180DivPi * (RadLAM0 + (Et * X) - (Math.pow(Et,3) * XI) + (Math.pow(Et,5) * XII) - (Math.pow(Et,7) * XIIA));
  471.  
  472.  
  473.  
  474. let RadPHI = oslLatitude * oslPiDiv180;
  475. let RadLAM = oslLongitude * oslPiDiv180;
  476.  
  477. const ee2 = (Math.pow(6377563.396,2) - Math.pow(6356256.910,2)) / Math.pow(6377563.396,2);
  478. let V = a / (Math.sqrt(1 - (ee2 * ( Math.pow(Math.sin(RadPHI),2)))));
  479. let cosRadPHI = Math.cos(RadPHI);
  480.  
  481. X = V * cosRadPHI * Math.cos(RadLAM);
  482. let Y = V * cosRadPHI * Math.sin(RadLAM);
  483. let Z = (V * (1 - ee2)) * (Math.sin(RadPHI));
  484.  
  485. // do Helmert transforms
  486.  
  487. const sfactor = -20.4894 * 0.000001;
  488. const RadX_Rot = (0.1502 / 3600) * oslPiDiv180;
  489. const RadY_Rot = (0.2470 / 3600) * oslPiDiv180;
  490. const RadZ_Rot = (0.8421 / 3600) * oslPiDiv180;
  491.  
  492. let X2 = (X + (X * sfactor) - (Y * RadZ_Rot) + (Z * RadY_Rot) + 446.448);
  493. let Y2 = (X * RadZ_Rot) + Y + (Y * sfactor) - (Z * RadX_Rot) -125.157;
  494. let Z2 = (-1 * X * RadY_Rot) + (Y * RadX_Rot) + Z + (Z * sfactor) + 542.060;
  495.  
  496. let RootXYSqr = Math.sqrt(Math.pow(X2,2) + Math.pow(Y2,2));
  497. const eee2 = (Math.pow(6378137.000,2) - Math.pow(6356752.313,2)) / Math.pow(6378137.000,2);
  498. PHI1 = Math.atan2(Z2 , (RootXYSqr * (1 - eee2)) );
  499. let sinPHI1 = Math.sin(PHI1);
  500. let sinPHI1Squared = Math.pow(sinPHI1, 2);
  501.  
  502. V = 6378137.000 / (Math.sqrt(1 - (eee2 * sinPHI1Squared)));
  503. let PHI2 = Math.atan2((Z + (eee2 * V * sinPHI1)) , RootXYSqr);
  504. while (Math.abs(PHI1 - PHI2) > 0.000000001)
  505. {
  506. PHI1 = PHI2;
  507. let innerSinPHI1 = Math.sin(PHI1);
  508. V = 6378137.000 / (Math.sqrt(1 - (eee2 * Math.pow(innerSinPHI1,2))));
  509. PHI2 = Math.atan2((Z2 + (eee2 * V * innerSinPHI1)) , RootXYSqr);
  510. }
  511. oslLatitude = PHI2 * osl180DivPi;
  512. oslLongitude = Math.atan2(Y2 , X2) * osl180DivPi;
  513. }
  514. function oslWGStoOSGB(lon, lat)
  515. {
  516. let helm = oslLatLontoHelmXYZ(lon, lat);
  517. let lat2 = oslXYZtoLat(helm);
  518. let lon2 = Math.atan2(helm.y , helm.x) * osl180DivPi;
  519. oslLatLonoslToOSGrid(lat2,lon2);
  520. }
  521. function oslLatLontoHelmXYZ(lon, lat)
  522. {
  523. const a = 6378137.0;
  524. const b = 6356752.313;
  525. const DX = -446.448;
  526. const DY = 125.157;
  527. const DZ = -542.060;
  528. const rotX = -0.1502;
  529. const rotY = -0.2470;
  530. const rotZ = -0.8421;
  531. const sfactor = 20.4894 * 0.000001;
  532. const e2 = (Math.pow(a,2) - Math.pow(b,2)) / Math.pow(a,2);
  533.  
  534. // perform initial lat-lon to cartesian coordinate translation
  535. let RadPHI = lat * oslPiDiv180;
  536. let RadLAM = lon * oslPiDiv180;
  537. let V = a / (Math.sqrt(1 - (e2 * ( Math.pow(Math.sin(RadPHI),2)))));
  538. let cartX = V * (Math.cos(RadPHI)) * (Math.cos(RadLAM));
  539. let cartY = V * (Math.cos(RadPHI)) * (Math.sin(RadLAM));
  540. let cartZ = (V * (1 - e2)) * (Math.sin(RadPHI));
  541.  
  542. // Compute Helmert transformed coordinates
  543. let RadX_Rot = (rotX / 3600) * oslPiDiv180;
  544. let RadY_Rot = (rotY / 3600) * oslPiDiv180;
  545. let RadZ_Rot = (rotZ / 3600) * oslPiDiv180;
  546. let helmX = (cartX + (cartX * sfactor) - (cartY * RadZ_Rot) + (cartZ * RadY_Rot) + DX);
  547. let helmY = (cartX * RadZ_Rot) + cartY + (cartY * sfactor) - (cartZ * RadX_Rot) + DY;
  548. let helmZ = (-1 * cartX * RadY_Rot) + (cartY * RadX_Rot) + cartZ + (cartZ * sfactor) + DZ;
  549.  
  550. return {x:helmX,y:helmY,z:helmZ};
  551. }
  552. function oslXYZtoLat(helm)
  553. {
  554. const a = 6377563.396;
  555. const b = 6356256.910;
  556. const e2 = (Math.pow(a,2) - Math.pow(b,2)) / Math.pow(a,2);
  557.  
  558. let RootXYSqr = Math.sqrt(Math.pow(helm.x,2) + Math.pow(helm.y,2));
  559. let PHI1 = Math.atan2(helm.z , (RootXYSqr * (1 - e2)) );
  560. let PHI = oslIterateOSLXYZtoLat(a, e2, PHI1, helm.z, RootXYSqr);
  561. return PHI * osl180DivPi;
  562. }
  563. function oslIterateOSLXYZtoLat(a, e2, PHI1, Z, RootXYSqr)
  564. {
  565. let V = a / (Math.sqrt(1 - (e2 * Math.pow(Math.sin(PHI1),2))));
  566. let PHI2 = Math.atan2((Z + (e2 * V * (Math.sin(PHI1)))) , RootXYSqr);
  567. while (Math.abs(PHI1 - PHI2) > 0.000000001)
  568. {
  569. PHI1 = PHI2;
  570. let sinPHI1 = Math.sin(PHI1);
  571. V = a / (Math.sqrt(1 - (e2 * Math.pow(sinPHI1,2))));
  572. PHI2 = Math.atan2((Z + (e2 * V * (sinPHI1))) , RootXYSqr);
  573. }
  574. return PHI2;
  575. }
  576. function oslMarc(bf0, n, PHI0, PHI)
  577. {
  578. let c1 = PHI - PHI0;
  579. let c2 = PHI + PHI0;
  580. let c3 = Math.pow(n, 2);
  581. let c4 = Math.pow(n, 3);
  582. return bf0 *
  583. (
  584. (
  585. (1 + n + ((5 / 4) * c3) + ((5 / 4) * c4)) * c1
  586. ) -
  587. (
  588. ((3 * n) + (3 * c3) + ((21 / 8) * c4)) *
  589. Math.sin(c1) *
  590. Math.cos(c2)
  591. ) +
  592. (
  593. (((15 / 8) * c3) + ((15 / 8) * c4)) *
  594. Math.sin(2 * c1) *
  595. Math.cos(2 * c2)
  596. ) -
  597. (
  598. ((35 / 24) * c4) *
  599. (Math.sin(3 * c1)) * (Math.cos(3 * c2))
  600. )
  601. );
  602. }
  603. function oslLatLonoslToOSGrid(PHI, LAM)
  604. {
  605. const a = 6377563.396;
  606. const b = 6356256.910;
  607. const e0 = 400000;
  608. const n0 = -100000;
  609. const f0 = 0.999601272;
  610. const PHI0 = 49.00000;
  611. const LAM0 = -2.00000;
  612. const RadPHI0 = PHI0 * oslPiDiv180;
  613. const RadLAM0 = LAM0 * oslPiDiv180;
  614. const af0 = a * f0;
  615. const bf0 = b * f0;
  616. const n = (af0 - bf0) / (af0 + bf0);
  617. const e2 = (Math.pow(af0,2) - Math.pow(bf0,2)) / Math.pow(af0,2);
  618.  
  619. let RadPHI = PHI * oslPiDiv180;
  620. let RadLAM = LAM * oslPiDiv180;
  621. let sinRadPHI = Math.sin(RadPHI);
  622. let sinRadPHISquared = Math.pow(sinRadPHI, 2);
  623. let cosRadPHI = Math.cos(RadPHI);
  624. let cosRadPHIPowThree = Math.pow(cosRadPHI, 3);
  625. let cosRadPHIPowFive = Math.pow(cosRadPHI, 5);
  626. let tanRadPHI = Math.tan(RadPHI);
  627. let tanRadPHISquared = Math.pow(tanRadPHI, 2);
  628. let tanRadPHIPowFour = Math.pow(tanRadPHI, 4);
  629.  
  630. let nu = af0 / (Math.sqrt(1 - (e2 * sinRadPHISquared)));
  631. let rho = (nu * (1 - e2)) / (1 - (e2 * sinRadPHISquared));
  632. let eta2 = (nu / rho) - 1;
  633. let p = RadLAM - RadLAM0;
  634. let M = oslMarc(bf0, n, RadPHI0, RadPHI);
  635. let I = M + n0;
  636. let II = (nu / 2) * sinRadPHI * cosRadPHI;
  637. let III = ((nu / 24) * sinRadPHI * cosRadPHIPowThree) * (5 - tanRadPHISquared + (9 * eta2));
  638. let IIIA = ((nu / 720) * sinRadPHI * cosRadPHIPowFive) * (61 - (58 * tanRadPHISquared) + tanRadPHIPowFour);
  639. let IV = nu * cosRadPHI;
  640. let V = (nu / 6) * cosRadPHIPowThree * ((nu / rho) - tanRadPHISquared);
  641. let VI = (nu / 120) * cosRadPHIPowFive * ((5 - (18 * tanRadPHISquared)) + tanRadPHIPowFour + (14 * eta2) - (58 * tanRadPHISquared * eta2));
  642. oslEastings = Math.round(e0 + (p * IV) + (Math.pow(p,3) * V) + (Math.pow(p,5) * VI));
  643. oslNorthings = Math.round(I + (Math.pow(p,2) * II) + (Math.pow(p,4) * III) + (Math.pow(p,6) * IIIA));
  644.  
  645. // Conversion errors
  646.  
  647. // 50.06574112187924 -5.699894626953322
  648. // 135261,25033
  649. // 135256,25014
  650. // +6, +19
  651.  
  652. // 51.35363338966115 1.4443961072966522
  653. // 639795,167306
  654. // 639800,167284
  655. // -5, +22
  656.  
  657. // 60.15795987870581 -1.1466271562283679
  658. // 447367,1141743
  659. // 447362,1141733
  660. // +5, +10
  661.  
  662. let nCorrect = Math.round(70 - PHI);
  663. oslNorthings -= nCorrect;
  664. }
  665. //-----------------------------------------------------------------------------------------------------------------------------------------
  666. function oslCaseCorrect(wrongcase)
  667. {
  668. let correctedCase = '';
  669. for(let loop=0;loop<wrongcase.length;loop++)
  670. {
  671. // capitalise first letter following one of these substrings
  672. if
  673. (
  674. (loop === 0)||
  675. (wrongcase[loop-1] == ' ')||
  676. (wrongcase[loop-1] == '(')||
  677. (wrongcase.substr(loop-3,3) == '-Y-')||
  678. (wrongcase.substr(loop-4,4) == '-YR-')
  679. ) correctedCase += wrongcase[loop].toUpperCase();
  680. else correctedCase += wrongcase[loop].toLowerCase();
  681. }
  682. // recapitalise any roman numerals
  683. correctedCase = correctedCase.replace(' Ii ',' II ');
  684. correctedCase = correctedCase.replace(' Iii ',' III ');
  685. correctedCase = correctedCase.replace(' Iv ',' IV ');
  686. correctedCase = correctedCase.replace(' Vi ',' VI ');
  687. correctedCase = correctedCase.replace(' Vii ',' VII ');
  688. return correctedCase;
  689. }
  690. function oslSaintsPreserveUs(oslName)
  691. {
  692. let nameBits = [];
  693. if(oslName.indexOf('St ') != -1)
  694. {
  695. nameBits = oslName.split('St ');
  696. oslName = nameBits[0] + 'St. ' + nameBits[1];
  697. }
  698. else if(oslName.indexOf('Saint ') != -1)
  699. {
  700. nameBits = oslName.split('Saint ');
  701. oslName = nameBits[0] + 'St. ' + nameBits[1];
  702. }
  703. return oslName;
  704. }
  705. function oslWazeifyStreetName(oslName, debugOutput)
  706. {
  707. let wazeName = '';
  708.  
  709. // strip out any HTML encoding added by the server when returning the street name data...
  710. let textArea = document.createElement('textarea');
  711. textArea.innerHTML = oslModifyHTML(oslName);
  712. oslName = textArea.value;
  713.  
  714. wazeName = oslCaseCorrect(oslName);
  715. wazeName = oslSaintsPreserveUs(wazeName);
  716.  
  717. let nameoslPieces = wazeName.split(' ');
  718. if(nameoslPieces.length > 1)
  719. {
  720. let dirSuffix = '';
  721. let namePrefix = '';
  722. if((nameoslPieces[nameoslPieces.length-1] == 'North')||(nameoslPieces[nameoslPieces.length-1] == 'South')||(nameoslPieces[nameoslPieces.length-1] == 'East')||(nameoslPieces[nameoslPieces.length-1] == 'West'))
  723. {
  724. dirSuffix = ' ' + nameoslPieces[nameoslPieces.length-1][0];
  725. for(let loop=0;loop<nameoslPieces.length-1;loop++)
  726. {
  727. namePrefix += (nameoslPieces[loop] + ' ');
  728. }
  729. }
  730. else
  731. {
  732. for(let loop=0;loop<nameoslPieces.length;loop++)
  733. {
  734. namePrefix += (nameoslPieces[loop] + ' ');
  735. }
  736. }
  737. namePrefix = namePrefix.trimRight(1);
  738.  
  739. if(debugOutput === true) console.log(oslName);
  740. // replace road type with abbreviated form
  741. for(let pass=0;pass<2;pass++)
  742. {
  743. for(let loop=0;loop<oslNameAbbreviations.length;loop+=2)
  744. {
  745. let abbrPos = namePrefix.lastIndexOf(oslNameAbbreviations[loop]);
  746. let abbrLen = oslNameAbbreviations[loop].length;
  747. let npLength = namePrefix.length;
  748. let npRemaining = npLength - abbrPos;
  749. if(debugOutput === true) console.log(pass,' ',oslNameAbbreviations[loop],' ',abbrPos,' ',abbrLen,' ',npLength,' ',npRemaining);
  750. if(abbrPos != -1)
  751. {
  752. // make sure the road type we've found comes firstly at the end of the name string, or is suffixed with a space
  753. // if there's a non-road type at the end of the string (e.g. High Road Eastcote)
  754. // isn't, then we've actually found a type match within a longer string segment (e.g. The Parkside) and so we
  755. // should leave it alone...
  756. if
  757. (
  758. ((pass === 0) && (npRemaining == abbrLen)) ||
  759. ((pass == 1) && (namePrefix[abbrPos+abbrLen] == ' '))
  760. )
  761. {
  762. let preName = namePrefix.substr(0,abbrPos);
  763. if((preName.length >= 4) && (preName.lastIndexOf("The") != (preName.length - 4)))
  764. {
  765. let theName = namePrefix.substr(abbrPos);
  766. theName = theName.replace(oslNameAbbreviations[loop],oslNameAbbreviations[loop+1]);
  767. wazeName = preName + theName + dirSuffix;
  768. return wazeName;
  769. }
  770. }
  771. }
  772. }
  773. }
  774. wazeName = namePrefix + dirSuffix;
  775. }
  776. return wazeName;
  777. }
  778. function oslCPDistance(cpE, cpN, posE, posN)
  779. {
  780. return Math.round(Math.sqrt(((posE - cpE) * (posE - cpE)) + ((posN - cpN) * (posN - cpN))));
  781. }
  782. function oslGetBBCornerPixels(boxW, boxE, boxS, boxN)
  783. {
  784. let lonlat_sw = {lonLat: {lon: boxW, lat: boxS}};
  785. let lonlat_se = {lonLat: {lon: boxE, lat: boxS}};
  786. let lonlat_nw = {lonLat: {lon: boxW, lat: boxN}};
  787. let lonlat_ne = {lonLat: {lon: boxE, lat: boxN}};
  788.  
  789. let pix_sw = oslSDK.Map.getMapPixelFromLonLat(lonlat_sw);
  790. let pix_se = oslSDK.Map.getMapPixelFromLonLat(lonlat_se);
  791. let pix_nw = oslSDK.Map.getMapPixelFromLonLat(lonlat_nw);
  792. let pix_ne = oslSDK.Map.getMapPixelFromLonLat(lonlat_ne);
  793.  
  794. boxE = (pix_ne.x + pix_se.x) / 2;
  795. boxW = (pix_nw.x + pix_sw.x) / 2;
  796. boxN = (pix_ne.y + pix_nw.y) / 2;
  797. boxS = (pix_se.y + pix_sw.y) / 2;
  798.  
  799. let boxToleranceWidth = ((boxE - boxW) * 0.05);
  800. let boxToleranceHeight = ((boxS - boxN) * 0.05);
  801.  
  802. boxW -= boxToleranceWidth;
  803. boxE += boxToleranceWidth;
  804. boxS += boxToleranceHeight;
  805. boxN -= boxToleranceHeight;
  806.  
  807. boxE = Math.round(boxE);
  808. boxW = Math.round(boxW);
  809. boxS = Math.round(boxS);
  810. boxN = Math.round(boxN);
  811.  
  812. // extend width/height of box if the calculated dimension is too small for the box to be readily visible
  813. if(boxE-boxW < 20)
  814. {
  815. boxE += 10;
  816. boxW -= 10;
  817. }
  818. if(boxS-boxN < 20)
  819. {
  820. boxS += 10;
  821. boxN -= 10;
  822. }
  823.  
  824. return [boxW, boxE, boxS, boxN];
  825. }
  826. function oslVisualiseBoundingBox(boxW, boxE, boxS, boxN, mode)
  827. {
  828. if(oslOSLDiv.style.height == '0px')
  829. {
  830. oslBBDiv.innerHTML = '';
  831. return;
  832. }
  833.  
  834. let boxPos = [boxW, boxE, boxS, boxN];
  835.  
  836. if((mode == OSL_BBMODE.Match) || (mode == OSL_BBMODE.Other))
  837. {
  838. boxPos = oslGetBBCornerPixels(boxW, boxE, boxS, boxN);
  839. }
  840.  
  841. if(mode === OSL_BBMODE.Init)
  842. {
  843. oslBBDivInnerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="'+document.getElementById('WazeMap').offsetWidth+'px" height="'+document.getElementById('WazeMap').offsetHeight+'px" version="1.1">';
  844. }
  845. else if(mode == OSL_BBMODE.Match)
  846. {
  847. 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"/>';
  848. }
  849. else if(mode == OSL_BBMODE.Other)
  850. {
  851. 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"/>';
  852. }
  853. else if(mode == OSL_BBMODE.Finalise)
  854. {
  855. oslBBDivInnerHTML += '</svg>';
  856. oslBBDiv.innerHTML = oslBBDivInnerHTML;
  857. }
  858. }
  859. function oslMergeGazetteerData()
  860. {
  861. if(typeof(gazetteerData) == "undefined") return false;
  862. if(oslMergeGazData)
  863. {
  864. // We no longer need to inject the gazetteer data as two seperate arrays and then merge them here,
  865. // but we do still need to create a local reference to the injected array data, as trying to access
  866. // it directly as gazetteerData[] throws errors...
  867. oslGazetteerData = gazetteerData;
  868. oslMergeGazData = false;
  869. for(let idx=0;idx<oslGazetteerData.length;idx++)
  870. {
  871. oslGazetteerData[idx] = oslSaintsPreserveUs(oslGazetteerData[idx]);
  872. }
  873. oslAddLog('gazetteer data loaded, '+oslGazetteerData.length+' entries');
  874. }
  875. return true;
  876. }
  877. function oslGetTextWidth(text)
  878. {
  879. // from https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
  880. // re-use canvas object for better performance
  881. let canvas = oslGetTextWidth.canvas || (oslGetTextWidth.canvas = document.createElement("canvas"));
  882. let context = canvas.getContext("2d");
  883. context.font = 'bold '+oslNameTextPts+' arial';
  884. let metrics = context.measureText(text);
  885. return metrics.width;
  886. }
  887. function oslGetVisibleCityNames()
  888. {
  889. if(oslMergeGazetteerData() === false) return;
  890.  
  891. oslNamesDivInnerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="'+document.getElementById('WazeMap').offsetWidth+'px" height="'+document.getElementById('WazeMap').offsetHeight+'px" version="1.1">';
  892. if(document.getElementById('_cbGazTagsEnabled').checked === true)
  893. {
  894. let showCities = document.getElementById('_cbGazTagsCity').checked;
  895. let showTowns = document.getElementById('_cbGazTagsTown').checked;
  896. let showVillages = document.getElementById('_cbGazTagsVillage').checked;
  897. let showHamlets = document.getElementById('_cbGazTagsHamlet').checked;
  898. let showOthers = document.getElementById('_cbGazTagsOther').checked;
  899.  
  900. for(let idx=0;idx<oslGazetteerData.length;idx++)
  901. {
  902. let gazElements = oslGazetteerData[idx].split(':');
  903. let cnType = gazElements[GAZ_ELM.Type];
  904.  
  905. let showTag;
  906. let tagColour;
  907. if(cnType == 'C')
  908. {
  909. showTag = showCities;
  910. tagColour = "#FFFF00";
  911. }
  912. else if(cnType == 'T')
  913. {
  914. showTag = showTowns;
  915. tagColour = "#FF00FF";
  916. }
  917. else if(cnType == 'V')
  918. {
  919. showTag = showVillages;
  920. tagColour = "#FF8080";
  921. }
  922. else if(cnType == 'H')
  923. {
  924. showTag = showHamlets;
  925. tagColour = "#A0A0FF";
  926. }
  927. else
  928. {
  929. showTag = showOthers;
  930. tagColour = "#C0C0C0";
  931. }
  932.  
  933. if(showTag === true)
  934. {
  935. let cnEastings = gazElements[GAZ_ELM.CEast];
  936. let cnNorthings = gazElements[GAZ_ELM.CNorth];
  937. if
  938. (
  939. ((oslVPLeft < cnEastings) && (oslVPRight > cnEastings)) &&
  940. ((oslVPTop > cnNorthings) && (oslVPBottom < cnNorthings))
  941. )
  942. {
  943. let pix = oslOSGridRefToPixel(cnEastings,cnNorthings);
  944. let nameLength = oslGetTextWidth(gazElements[GAZ_ELM.Name]);
  945.  
  946. oslNamesDivInnerHTML += '<polygon points="';
  947. oslNamesDivInnerHTML += pix.x+','+pix.y+' ';
  948. oslNamesDivInnerHTML += (pix.x+oslNameHalfPix)+','+(pix.y-oslNameHalfPix)+' ';
  949. oslNamesDivInnerHTML += (pix.x+nameLength+oslNameHalfPix+8)+','+(pix.y-oslNameHalfPix)+' ';
  950. oslNamesDivInnerHTML += (pix.x+nameLength+oslNameHalfPix+8)+','+(pix.y+oslNameHalfPix)+' ';
  951. oslNamesDivInnerHTML += (pix.x+oslNameHalfPix)+','+(pix.y+oslNameHalfPix)+'" ';
  952. oslNamesDivInnerHTML += 'fill="'+tagColour+'" fill-opacity="0.75" stroke="black" stroke-opacity="0.75" />';
  953. oslNamesDivInnerHTML += '<text x="'+(pix.x+oslNameHalfPix)+'" y="'+(pix.y+(oslNameHalfPix/2))+'" ';
  954. oslNamesDivInnerHTML += 'style="font-family:sans-serif;font-size:'+oslNameTextPts+';font-weight:600;fill:black;';
  955. oslNamesDivInnerHTML += 'fill-opacity:1">'+gazElements[GAZ_ELM.Name]+'</text>';
  956. }
  957. }
  958. }
  959. }
  960. oslNamesDivInnerHTML += '</svg>';
  961. oslNamesDiv.innerHTML = oslNamesDivInnerHTML;
  962. }
  963. function oslGetNearbyCityNames()
  964. {
  965. if(oslMergeGazetteerData() === false) return;
  966.  
  967. let names = [];
  968. for(var idx=0;idx<oslGazetteerData.length;idx++)
  969. {
  970. let gazElements = oslGazetteerData[idx].split(':');
  971. let cnEastings = gazElements[GAZ_ELM.CEast];
  972. let cnNorthings = gazElements[GAZ_ELM.CNorth];
  973. if((Math.abs(cnNorthings-oslNorthings) <= 5000)&&(Math.abs(cnEastings-oslEastings) <= 5000))
  974. {
  975. let dist = oslCPDistance(cnEastings,cnNorthings,oslEastings,oslNorthings);
  976. if(dist <= 5000)
  977. {
  978. names.push((dist * 1000000) + idx);
  979. }
  980. }
  981. }
  982. if(names.length > 1) names.sort(function(a,b){return a-b;});
  983.  
  984. let cityInTopTen = false;
  985. let matchedOSName = false;
  986. let matchedIdx = -1;
  987. let listLength = names.length;
  988. if(listLength > 10) listLength = 10;
  989.  
  990. let listOpt;
  991. let gElements;
  992. let gDist;
  993.  
  994. let cityName;
  995.  
  996. let oOCN = document.getElementById('oslOSCityNames');
  997. for(idx=0;idx<listLength;idx++)
  998. {
  999. gElements = oslGazetteerData[names[idx] % 1000000].split(':');
  1000. gDist = (Math.round(names[idx] / 100000000)/10);
  1001.  
  1002. // Build namestring for entry in the drop-down list - start with the placename :-)
  1003. listOpt = document.createElement('option');
  1004. cityName = gElements[GAZ_ELM.Name];
  1005. listOpt.text = cityName;
  1006.  
  1007. // if the name is neither a city nor unique, append a (county) suffix
  1008. if(gElements[GAZ_ELM.Type] == 'C') cityInTopTen = true;
  1009. else
  1010. {
  1011. if(oslCheckCityNameDuplicates(gElements[GAZ_ELM.Name],1) > 1)
  1012. {
  1013. listOpt.text += ', '+gElements[GAZ_ELM.Area];
  1014. }
  1015. }
  1016.  
  1017. if(sessionStorage.cityNameRB == 'optUseOS')
  1018. {
  1019. if(cityName == sessionStorage.myCity)
  1020. {
  1021. matchedOSName = true;
  1022. matchedIdx = idx;
  1023. }
  1024. }
  1025.  
  1026. // Add place type and distance in [] brackets to allow easy removal later...
  1027. if(gElements[GAZ_ELM.Type] == 'C') listOpt.text += ' [City, ';
  1028. else if(gElements[GAZ_ELM.Type] == 'T') listOpt.text += ' [Town, ';
  1029. else if(gElements[GAZ_ELM.Type] == 'V') listOpt.text += ' [Village, ';
  1030. else if(gElements[GAZ_ELM.Type] == 'H') listOpt.text += ' [Hamlet, ';
  1031. else listOpt.text += ' [Other, ';
  1032. listOpt.text += gDist + 'km]';
  1033. oOCN.add(listOpt,null);
  1034. }
  1035.  
  1036. if((!cityInTopTen) && (names.length > 10))
  1037. {
  1038. idx = 10;
  1039. while((idx < names.length) && (!cityInTopTen))
  1040. {
  1041. gElements = oslGazetteerData[names[idx] % 1000000].split(':');
  1042. if(gElements[GAZ_ELM.Type] == 'C')
  1043. {
  1044. cityInTopTen = true;
  1045. gDist = ' [City, '+(Math.round(names[idx] / 100000000)/10)+'km]';
  1046. listOpt = document.createElement('option');
  1047. listOpt.text = gElements[GAZ_ELM.Name]+gDist;
  1048. oOCN.add(listOpt,null);
  1049. if(sessionStorage.cityNameRB == 'optUseOS')
  1050. {
  1051. if(gElements[GAZ_ELM.Name] == sessionStorage.myCity)
  1052. {
  1053. matchedOSName = true;
  1054. matchedIdx = 10;
  1055. break;
  1056. }
  1057. }
  1058. }
  1059. idx++;
  1060. }
  1061. }
  1062.  
  1063. if(matchedOSName === true) oOCN.options.selectedIndex = matchedIdx;
  1064.  
  1065. if((sessionStorage.cityNameRB == 'optUseOS') && (matchedOSName === false))
  1066. {
  1067. oslAddLog('Selected city name no longer in nearby OS list...');
  1068. alert('City name no longer present in nearby OS data, please reselect');
  1069. sessionStorage.cityNameRB = 'optUseExisting';
  1070. document.getElementById('optUseExisting').checked = true;
  1071. }
  1072. }
  1073. function oslCheckCityNameDuplicates(cityName, mode)
  1074. {
  1075. if(oslMergeGazetteerData() === false) return;
  1076.  
  1077. let cnCount = 0;
  1078. let searchDist = Math.round(oslGazetteerData.length/2);
  1079. let searchIdx = searchDist;
  1080. let hasCounty = false;
  1081.  
  1082. let debugOutput = false;
  1083.  
  1084.  
  1085. // remove county suffix from actual city name string if present
  1086. if(cityName.indexOf('(') != -1)
  1087. {
  1088. cityName = cityName.substr(0,cityName.indexOf('('));
  1089. cityName = cityName.replace(/^\s+|\s+$/g, "");
  1090. hasCounty = true;
  1091. }
  1092. // remove script-appended county suffix from city name held in drop down if present
  1093. if(cityName.indexOf(',') != -1)
  1094. {
  1095. cityName = cityName.substr(0,cityName.indexOf(','));
  1096. cityName = cityName.replace(/^\s+|\s+$/g, "");
  1097. }
  1098.  
  1099. cityName = cityName.toLowerCase();
  1100. cityName = cityName.replace(/-/g, ' ');
  1101. let gazName = '';
  1102.  
  1103. if(debugOutput === true) console.log('scan for duplicates of '+cityName);
  1104.  
  1105. var gazElements = [];
  1106. while((searchDist > 1) && (cityName.localeCompare(gazName) !== 0))
  1107. {
  1108. searchDist = Math.round(searchDist/2);
  1109. gazElements = oslGazetteerData[searchIdx].split(':');
  1110. gazName = gazElements[GAZ_ELM.Name].toLowerCase();
  1111. gazName = gazName.replace(/-/g, ' ');
  1112. if(debugOutput === true) console.log('a: '+searchDist+' '+searchIdx+' '+gazName);
  1113. if(cityName.localeCompare(gazName) > 0) searchIdx += searchDist;
  1114. else if(cityName.localeCompare(gazName) < 0) searchIdx -= searchDist;
  1115. if(searchIdx >= oslGazetteerData.length) searchIdx = oslGazetteerData.length-1;
  1116. if(searchIdx < 0) searchIdx = 0;
  1117. }
  1118. gazElements = oslGazetteerData[searchIdx].split(':');
  1119. gazName = gazElements[GAZ_ELM.Name].toLowerCase();
  1120. gazName = gazName.replace(/-/g, ' ');
  1121. while((searchIdx > 0) && (cityName.localeCompare(gazName) == 0))
  1122. {
  1123. gazElements = oslGazetteerData[--searchIdx].split(':');
  1124. gazName = gazElements[GAZ_ELM.Name].toLowerCase();
  1125. gazName = gazName.replace(/-/g, ' ');
  1126. if(debugOutput === true) console.log('b: '+(searchIdx)+' '+gazName);
  1127. }
  1128. ++searchIdx;
  1129. gazElements = oslGazetteerData[searchIdx].split(':');
  1130. gazName = gazElements[GAZ_ELM.Name].toLowerCase();
  1131. gazName = gazName.replace(/-/g, ' ');
  1132. while((searchIdx < (oslGazetteerData.length - 1)) && (cityName.localeCompare(gazName) > 0))
  1133. {
  1134. ++searchIdx;
  1135. try
  1136. {
  1137. gazElements = oslGazetteerData[searchIdx].split(':');
  1138. gazName = gazElements[GAZ_ELM.Name].toLowerCase();
  1139. gazName = gazName.replace(/-/g, ' ');
  1140. if(debugOutput === true) console.log('c: '+(searchIdx)+' '+gazName);
  1141. }
  1142. catch
  1143. {
  1144. oslAddLog("ERROR THROWN - "+oslGazetteerData[searchIdx]);
  1145. }
  1146. }
  1147. while((cityName.localeCompare(gazName) === 0) && (searchIdx < oslGazetteerData.length))
  1148. {
  1149. cnCount++;
  1150. gazElements = oslGazetteerData[++searchIdx].split(':');
  1151. gazName = gazElements[GAZ_ELM.Name].toLowerCase();
  1152. gazName = gazName.replace(/-/g, ' ');
  1153. if(debugOutput === true) console.log('d: '+(searchIdx)+' '+gazName+' '+cnCount);
  1154. }
  1155.  
  1156. if(mode === 0)
  1157. {
  1158. let newHTML = '';
  1159. if(cnCount === 0) newHTML = '&nbsp;&nbsp;Place name is not in OS data';
  1160. else if(cnCount == 1)
  1161. {
  1162. newHTML = '&nbsp;&nbsp;Place name is unique';
  1163. if(hasCounty) newHTML += '<br>&nbsp;&nbsp;<i>(County) suffix not required</i>';
  1164. }
  1165. else
  1166. {
  1167. newHTML = '&nbsp;&nbsp;Place name is not unique';
  1168. }
  1169. document.getElementById('oslCNInfo').innerHTML = newHTML;
  1170. }
  1171. else return cnCount;
  1172. }
  1173. function oslHighlightAdjacentSameNameSegments(ldEastings, ldNorthings, ldIgnoreIdx, srcElements)
  1174. {
  1175. ldNorthings -= oslLocatorBlockSize;
  1176. ldEastings -= oslLocatorBlockSize;
  1177. for(let x = 0; x < 3; ++x)
  1178. {
  1179. for(let y = 0; y < 3; ++y)
  1180. {
  1181. let arrayName = 'locatorData_'+(ldEastings + (x * oslLocatorBlockSize))+'_'+(ldNorthings + (y * oslLocatorBlockSize));
  1182. oslEvalString = arrayName;
  1183. if(typeof unsafeWindow[arrayName] != "undefined")
  1184. {
  1185. oslBlockData = unsafeWindow[arrayName];
  1186. for(let loop = 0; loop < oslBlockData.length; ++loop)
  1187. {
  1188. if(loop != ldIgnoreIdx)
  1189. {
  1190. var locatorElements = oslSplitEntry(oslBlockData[loop]);
  1191. if
  1192. (
  1193. (locatorElements[OSL_ELM.RoadName] == srcElements[OSL_ELM.RoadName]) &&
  1194. (locatorElements[OSL_ELM.RoadNumber] == srcElements[OSL_ELM.RoadNumber]) &&
  1195. (locatorElements[OSL_ELM.AreaName] == srcElements[OSL_ELM.AreaName])
  1196. )
  1197. {
  1198. oslVisualiseBoundingBox(locatorElements[OSL_ELM.BoundW],locatorElements[OSL_ELM.BoundE],locatorElements[OSL_ELM.BoundS],locatorElements[OSL_ELM.BoundN],OSL_BBMODE.Other);
  1199. }
  1200. }
  1201. }
  1202. }
  1203. }
  1204. }
  1205. }
  1206. function oslRadioClick()
  1207. {
  1208. let oslElements = document.getElementById('oslRoadNameMatches');
  1209. let selectedName = '';
  1210. let arrayName;
  1211.  
  1212. for(let loop=0;loop<oslElements.childNodes.length;loop++)
  1213. {
  1214. if(oslElements.childNodes[loop].nodeType == 1)
  1215. {
  1216. let tagname = oslElements.childNodes[loop].tagName;
  1217. if(tagname !== null)
  1218. {
  1219. if(tagname == "LABEL")
  1220. {
  1221. if(oslElements.childNodes[loop].childNodes[0].checked)
  1222. {
  1223. let attr = oslElements.childNodes[loop].childNodes[0].attributes.getNamedItem("id").value;
  1224. if((attr.indexOf('oslID_') === 0) || (attr.indexOf('alt-oslID_') === 0))
  1225. {
  1226. let roadData = '';
  1227. let oslID = attr.split('_');
  1228. if(oslID[1] != 'null')
  1229. {
  1230. arrayName = 'locatorData_'+oslID[1]+'_'+oslID[2];
  1231. if(typeof unsafeWindow[arrayName] != "undefined")
  1232. {
  1233. roadData = unsafeWindow[arrayName][oslID[3]];
  1234. }
  1235. }
  1236. if(roadData == '')
  1237. {
  1238. roadData = "null:null";
  1239. }
  1240.  
  1241. let locatorElements = oslSplitEntry(roadData);
  1242.  
  1243. if(locatorElements[OSL_ELM.RoadName] != 'null')
  1244. {
  1245. selectedName = locatorElements[OSL_ELM.RoadName]+locatorElements[OSL_ELM.RoadNumber];
  1246. oslVisualiseBoundingBox(0,0,0,0,OSL_BBMODE.Init);
  1247. oslVisualiseBoundingBox(locatorElements[OSL_ELM.BoundW],locatorElements[OSL_ELM.BoundE],locatorElements[OSL_ELM.BoundS],locatorElements[OSL_ELM.BoundN],OSL_BBMODE.Match);
  1248.  
  1249. oslHighlightAdjacentSameNameSegments(oslID[1], oslID[2], oslID[3], locatorElements);
  1250. }
  1251. else
  1252. {
  1253. oslBBDiv.innerHTML = '';
  1254. }
  1255. }
  1256. }
  1257. }
  1258. }
  1259. }
  1260. }
  1261.  
  1262. if(selectedName === '')
  1263. {
  1264. oslBBDiv.innerHTML = '';
  1265. return;
  1266. }
  1267. oslVisualiseBoundingBox(0,0,0,0,OSL_BBMODE.Finalise);
  1268. }
  1269. function oslClick()
  1270. {
  1271. oslCityName = '';
  1272. oslCountyName = '';
  1273. oslUsingNewName = false;
  1274. if(document.getElementById('optUseNewManual').checked)
  1275. {
  1276. oslCityName = oslCaseCorrect(document.getElementById('myCityName').value);
  1277. oslUsingNewName = true;
  1278. }
  1279. else if(document.getElementById('optUseExistingWME').checked)
  1280. {
  1281. let oWCN = document.getElementById('oslWMECityNames');
  1282. oslCityName = oWCN.options[oWCN.options.selectedIndex].text;
  1283. if(oslCityName.indexOf(', ') !== -1)
  1284. {
  1285. oslCountyName = oslCityName.split(', ')[1];
  1286. oslCityName = oslCityName.split(', ')[0];
  1287. }
  1288. oslUsingNewName = true;
  1289. }
  1290. else if(document.getElementById('optUseOS').checked)
  1291. {
  1292. let oOCN = document.getElementById('oslOSCityNames');
  1293. oslCityName = oOCN.options[oOCN.options.selectedIndex].text;
  1294. oslCityName = oslCityName.substring(0,oslCityName.indexOf('[')-1);
  1295. if(oslCityName.indexOf(', ') !== -1)
  1296. {
  1297. oslCountyName = oslCityName.split(', ')[1];
  1298. oslCityName = oslCityName.split(', ')[0];
  1299. }
  1300. oslUsingNewName = true;
  1301. }
  1302. if(sessionStorage.myCity === '')
  1303. {
  1304. oslAddLog('Update city name position at '+oslEastings+'x'+oslNorthings);
  1305. sessionStorage.cityChangeEastings = oslEastings;
  1306. sessionStorage.cityChangeNorthings = oslNorthings;
  1307. }
  1308.  
  1309. if(oslCountyName !== '')
  1310. {
  1311. sessionStorage.myCity = oslCityName+', '+oslCountyName;
  1312. }
  1313. else
  1314. {
  1315. sessionStorage.myCity = oslCityName;
  1316. }
  1317.  
  1318. oslUseName = false;
  1319. if((oslCityName.length > 0) && oslUsingNewName)
  1320. {
  1321. oslCheckCityNameDuplicates(oslCityName,0);
  1322.  
  1323. if(oslCityName != sessionStorage.prevCityName)
  1324. {
  1325. oslAddLog('Change of city name at '+oslEastings+'x'+oslNorthings);
  1326. sessionStorage.cityChangeEastings = oslEastings;
  1327. sessionStorage.cityChangeNorthings = oslNorthings;
  1328. sessionStorage.prevCityName = oslCityName;
  1329. oslUseName = true;
  1330. }
  1331. else
  1332. {
  1333. let nameChangeDist = oslCPDistance(oslEastings,oslNorthings,sessionStorage.cityChangeEastings,sessionStorage.cityChangeNorthings);
  1334. oslAddLog('Current name was set '+nameChangeDist+'m away from segment location');
  1335. if(nameChangeDist > 1000)
  1336. {
  1337. oslAddLog('Distance exceeds 1km threshold, name verification required...');
  1338. if(confirm('Confirm continued use of this city name'))
  1339. {
  1340. oslAddLog('Confirm city name at '+oslEastings+'x'+oslNorthings);
  1341. sessionStorage.cityChangeEastings = oslEastings;
  1342. sessionStorage.cityChangeNorthings = oslNorthings;
  1343. oslUseName = true;
  1344. }
  1345. }
  1346. else
  1347. {
  1348. oslUseName = true;
  1349. }
  1350. }
  1351. }
  1352.  
  1353. let oslElements = document.getElementById('oslRoadNameMatches');
  1354. let arrayName;
  1355. for(let loop=0;loop<oslElements.childNodes.length;loop++)
  1356. {
  1357. if(oslElements.childNodes[loop].nodeType == 1)
  1358. {
  1359. let tagname = oslElements.childNodes[loop].tagName;
  1360. if(tagname !== null)
  1361. {
  1362. if(tagname == "LABEL")
  1363. {
  1364. let rbElement = oslElements.childNodes[loop].childNodes[0];
  1365. if((rbElement.name == "oslChoice") && (rbElement.checked))
  1366. {
  1367. let attr = rbElement.attributes.getNamedItem("id").value;
  1368. let useMain = (attr.indexOf('oslID_') === 0);
  1369. oslUseAlt = (attr.indexOf('alt-oslID_') === 0);
  1370. if((useMain === true) || (oslUseAlt === true))
  1371. {
  1372. let roadData = '';
  1373. let oslID = attr.split('_');
  1374. if(oslID[1] != 'null')
  1375. {
  1376. arrayName = 'locatorData_'+oslID[1]+'_'+oslID[2];
  1377. if(typeof unsafeWindow[arrayName] != "undefined")
  1378. {
  1379. roadData = unsafeWindow[arrayName][oslID[3]];
  1380. }
  1381. }
  1382. if(roadData == '')
  1383. {
  1384. oslAddLog(arrayName + "not found");
  1385. for(let i = 0; i < OSL_ELM.MAX; ++i)
  1386. {
  1387. roadData += ':';
  1388. }
  1389. }
  1390. oslLocatorElements = oslSplitEntry(roadData);
  1391.  
  1392. oslRenameSegments();
  1393. }
  1394. }
  1395. }
  1396. }
  1397. }
  1398. }
  1399. }
  1400.  
  1401. function oslGetSegmentIDs()
  1402. {
  1403. let selectedItems = W.selectionManager.getSelectedWMEFeatures();
  1404. let segIDs = [];
  1405. let allSegments = true;
  1406. for(let i = 0; i < selectedItems.length; ++i)
  1407. {
  1408. if(selectedItems[i].featureType === "segment")
  1409. {
  1410. segIDs.push(selectedItems[i].id);
  1411. }
  1412. else
  1413. {
  1414. allSegments = false;
  1415. }
  1416. }
  1417. if(allSegments === false)
  1418. {
  1419. segIDs = [];
  1420. }
  1421. return segIDs;
  1422. }
  1423. function oslGetNewStreetName(getPri)
  1424. {
  1425. let roadName = "";
  1426. let oslName = oslLocatorElements[OSL_ELM.RoadNumber];
  1427. if((oslLocatorElements[OSL_ELM.RoadName].length > 0)&&(oslLocatorElements[OSL_ELM.RoadNumber].length > 0))
  1428. {
  1429. oslName += ' - ';
  1430. }
  1431.  
  1432. if(oslUseAlt === true)
  1433. {
  1434. getPri = !getPri;
  1435. }
  1436. if(getPri === true)
  1437. {
  1438. roadName = oslLocatorElements[OSL_ELM.RoadName];
  1439. }
  1440. else
  1441. {
  1442. roadName = oslLocatorElements[OSL_ELM.AltName];
  1443. if(roadName.length === 0)
  1444. {
  1445. // Don't return anything for an empty alternate name
  1446. oslName = null;
  1447. }
  1448. }
  1449. if((oslName !== null) && (roadName.length > 0))
  1450. {
  1451. oslName += oslWazeifyStreetName(roadName, false);
  1452. oslPrevStreetName = oslName;
  1453. }
  1454.  
  1455. return oslName;
  1456. }
  1457. function oslUpdateCityNameSelectionTracking()
  1458. {
  1459. if(document.getElementById('optUseNewManual').checked === true)
  1460. {
  1461. sessionStorage.cityNameRB = 'optUseNewManual';
  1462. }
  1463. else if(document.getElementById('optUseExistingWME').checked === true)
  1464. {
  1465. sessionStorage.cityNameRB = 'optUseExistingWME';
  1466. }
  1467. else if(document.getElementById('optUseOS').checked === true)
  1468. {
  1469. sessionStorage.cityNameRB = 'optUseOS';
  1470. }
  1471. else if(document.getElementById('optClearExisting').checked === true)
  1472. {
  1473. sessionStorage.cityNameRB = 'optClearExisting';
  1474. }
  1475. else
  1476. {
  1477. sessionStorage.cityNameRB = 'optUseExisting';
  1478. }
  1479. }
  1480. function oslGetCityIDForSegment(segID)
  1481. {
  1482. // Obtain an ID either for an existing city or a newly created on, matching what
  1483. // the user has selected for the given segment.
  1484. //
  1485. // If the user has opted to select a new name from the script UI, oslUseName will
  1486. // have been set to true, and oslCityName set to the new name, by the time we get
  1487. // here. So we only need to do a little bit of extra work here if the user has
  1488. // either opted to retain the existing name or clear it entirely.
  1489. if(oslUseName === false)
  1490. {
  1491. if(document.getElementById('optClearExisting').checked === true)
  1492. {
  1493. // Clear the name, or...
  1494. oslCityName = "";
  1495. }
  1496. else
  1497. {
  1498. // ...use the name already on the segment
  1499. let segAddr = oslSDK.DataModel.Segments.getAddress({segmentId:segID});
  1500. oslCityName = segAddr.city.name;
  1501. }
  1502. }
  1503. // Now we know what the city name string should be, we can do a lookup to find
  1504. // the corresponding ID...
  1505. let city = oslSDK.DataModel.Cities.getCity({cityName:oslCityName});
  1506. if(city === null)
  1507. {
  1508. // City name doesn't already exist, so create it...
  1509. city = oslSDK.DataModel.Cities.addCity({cityName:oslCityName});
  1510. }
  1511. return city.id;
  1512. }
  1513. function oslGetStreetID(streetName, cityID)
  1514. {
  1515. let street = oslSDK.DataModel.Streets.getStreet({streetName:streetName,cityId:cityID});
  1516. if(street === null)
  1517. {
  1518. street = oslSDK.DataModel.Streets.addStreet({streetName:streetName,cityId:cityID});
  1519. }
  1520. return street.id;
  1521. }
  1522. function oslRenameSegments()
  1523. {
  1524. let segIDs = oslGetSegmentIDs();
  1525. if(segIDs.length !== 0)
  1526. {
  1527. oslUpdateCityNameSelectionTracking();
  1528.  
  1529. // Primary and secondary (if selected) street names will be the same for all selected segments
  1530. let priStreetName = oslGetNewStreetName(true);
  1531. let altStreetName = oslGetNewStreetName(false);
  1532.  
  1533. // For each selected segment...
  1534. for(let i = 0; i < segIDs.length; ++i)
  1535. {
  1536. let segID = segIDs[i];
  1537.  
  1538. // Clear any altnames first
  1539. oslSDK.DataModel.Segments.updateAddress({segmentId:segID,alternateStreetIds:[]});
  1540. // Get a city ID
  1541. let cityID = oslGetCityIDForSegment(segID);
  1542. // Get street IDs for the primary (and alternate if required)
  1543. let priStreetID = oslGetStreetID(priStreetName, cityID);
  1544. if(altStreetName !== null)
  1545. {
  1546. let altStreetID = oslGetStreetID(altStreetName, cityID);
  1547. oslSDK.DataModel.Segments.updateAddress({segmentId:segID,primaryStreetId:priStreetID,alternateStreetIds:[altStreetID]});
  1548. }
  1549. else
  1550. {
  1551. oslSDK.DataModel.Segments.updateAddress({segmentId:segID,primaryStreetId:priStreetID});
  1552. }
  1553. }
  1554. }
  1555. }
  1556.  
  1557. function oslMatch(oslLink, oslArea, oslRadioID, oslAltRadioID)
  1558. {
  1559. this.oslLink = oslLink;
  1560. this.oslArea = oslArea;
  1561. this.oslRadioID = oslRadioID;
  1562. this.oslAltRadioID = oslAltRadioID;
  1563. }
  1564. function oslSortCandidates(a,b)
  1565. {
  1566. let x = a.oslArea;
  1567. let y = b.oslArea;
  1568. return((x<y) ? -1 : ((x>y) ? 1 : 0));
  1569. }
  1570. function oslCityNameKeyup()
  1571. {
  1572. oslCheckCityNameDuplicates(oslCaseCorrect(document.getElementById('myCityName').value),0);
  1573. document.getElementById('optUseNewManual').checked = true;
  1574. }
  1575. function oslSelectWMEName()
  1576. {
  1577. let oWCN = document.getElementById('oslWMECityNames');
  1578. let cityName = oWCN.options[oWCN.options.selectedIndex].text;
  1579. oslCheckCityNameDuplicates(cityName,0);
  1580. document.getElementById('optUseExistingWME').checked = true;
  1581. }
  1582. function oslSelectOSName()
  1583. {
  1584. let oOCN = document.getElementById('oslOSCityNames');
  1585. let cityName = oOCN.options[oOCN.options.selectedIndex].text;
  1586. cityName = cityName.substring(0,cityName.indexOf('[')-1);
  1587. document.getElementById('optUseOS').checked = true;
  1588. document.getElementById('oslCNInfo').innerHTML = '';
  1589. }
  1590. function oslBlockCacheObj(blockName)
  1591. {
  1592. this.blockName = blockName;
  1593. this.lastAccessed = Math.floor(new Date().getTime() / 1000);
  1594. }
  1595. function oslLoadBlocks()
  1596. {
  1597. for(let i = 0; i < oslBlocksToLoad.length;)
  1598. {
  1599. // inject block data
  1600. let script = document.createElement("script");
  1601. script.setAttribute('type','text/javascript');
  1602. script.setAttribute('charset','UTF-8');
  1603. script.src = oslBlocksToLoad[i];
  1604. document.head.appendChild(script);
  1605. oslLoadingMsg = true;
  1606. oslBlockCacheList.push(new oslBlockCacheObj(oslBlocksToLoad[i+1]));
  1607. i += 2;
  1608. }
  1609. }
  1610. function oslSplitEntry(entryString)
  1611. {
  1612. let elements = entryString.split(":");
  1613. if(entryString != "null:null")
  1614. {
  1615. let ts=elements[OSL_ELM.Geometry].split(/[ ]+/).map(Number);
  1616. let tsLon=[];
  1617. let tsLat=[];
  1618. ts.forEach((a,i) => {(i % 2 === 0) ? tsLon.push(a) : tsLat.push(a);});
  1619. elements[OSL_ELM.BoundE] = Math.max(...tsLon);
  1620. elements[OSL_ELM.BoundW] = Math.min(...tsLon);
  1621. elements[OSL_ELM.BoundN] = Math.max(...tsLat);
  1622. elements[OSL_ELM.BoundS] = Math.min(...tsLat);
  1623. }
  1624.  
  1625. return elements;
  1626. }
  1627. function oslToOSGrid(lat, lon, mode)
  1628. {
  1629. if(oslInUK === false) return;
  1630.  
  1631. if(oslMousePixelpos == null) return;
  1632.  
  1633. // Correct mouse X pos if map viewport width has changed (e.g. when clicking on segment causing sidepanel to open...)
  1634. let mvpWidth = oslSDK.Map.getMapViewportElement().getBoundingClientRect().width;
  1635. let vpWidthDelta = (oslLastViewportWidth - mvpWidth);
  1636. oslLastViewportWidth = mvpWidth;
  1637. oslMousePixelpos.x -= vpWidthDelta;
  1638.  
  1639. if((lat !== 0) && (mode != OSL_MODE.OpenRoads))
  1640. {
  1641. if(mode == OSL_MODE.NameCheck)
  1642. {
  1643. oslEastings = lat;
  1644. oslNorthings = lon;
  1645. }
  1646. else
  1647. {
  1648. oslLatitude = lat;
  1649. oslLongitude = lon;
  1650. oslWGStoOSGB(lon, lat);
  1651. }
  1652. }
  1653.  
  1654. if((mode == OSL_MODE.OpenNames) || (mode == OSL_MODE.NameCheck))
  1655. {
  1656. let eBlock;
  1657. let nBlock;
  1658.  
  1659. // determine which grid block contains the current mouse position
  1660. let eBlock_point = (Math.floor(oslEastings/oslLocatorBlockSize)) * oslLocatorBlockSize;
  1661. let nBlock_point = (Math.floor(oslNorthings/oslLocatorBlockSize)) * oslLocatorBlockSize;
  1662. let candidates = [];
  1663. oslBlocksToLoad = [];
  1664. let arrayName;
  1665. let oslEvalString;
  1666.  
  1667. for(let x = -1; x < 2; x++)
  1668. {
  1669. for(let y = -1; y < 2; y++)
  1670. {
  1671. eBlock = (eBlock_point + (oslLocatorBlockSize * x));
  1672. nBlock = (nBlock_point + (oslLocatorBlockSize * y));
  1673. // check we're within the outer bounds of the current OS dataset...
  1674. if
  1675. (
  1676. (eBlock >= 64000) &&
  1677. (eBlock <= 655999) &&
  1678. (nBlock >= 8000) &&
  1679. (nBlock <= 1214999)
  1680. )
  1681. {
  1682. // check to see if there's a corresponding array already loaded...
  1683. arrayName = 'locatorData_'+eBlock+'_'+nBlock;
  1684. if(typeof unsafeWindow[arrayName] == "undefined")
  1685. {
  1686. // create a blank placeholder which will get replaced by the actual data if the array is present on the server...
  1687. unsafeWindow[arrayName] = [];
  1688. oslBlocksToLoad.push(oslBlockPath+Math.floor(eBlock / 100000)+'/'+Math.floor(nBlock / 100000)+'/'+arrayName+'.user.js');
  1689. oslBlocksToLoad.push(arrayName);
  1690. }
  1691. }
  1692. }
  1693. }
  1694.  
  1695. if(oslBlocksToLoad.length > 0)
  1696. {
  1697. oslLoadBlocks();
  1698. }
  1699.  
  1700. candidates = [];
  1701. 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);
  1702.  
  1703. for(let x = -1; x < 2; x++)
  1704. {
  1705. for(let y = -1; y < 2; y++)
  1706. {
  1707. eBlock = (eBlock_point + (oslLocatorBlockSize * x));
  1708. nBlock = (nBlock_point + (oslLocatorBlockSize * y));
  1709.  
  1710. // check we're within the outer bounds of the current OS dataset...
  1711. if ((eBlock >= 64000) && (eBlock <= 655999) && (nBlock >= 8000) && (nBlock <= 1214999))
  1712. {
  1713. arrayName = 'locatorData_'+eBlock+'_'+nBlock;
  1714. oslEvalString = arrayName;
  1715. if(typeof unsafeWindow[arrayName] != "undefined")
  1716. {
  1717. oslLoadingMsg = false;
  1718. // yes...
  1719. if((eBlock != oslEvalEBlock) || (nBlock != oslEvalNBlock))
  1720. {
  1721. oslEvalEBlock = eBlock;
  1722. oslEvalNBlock = nBlock;
  1723. oslBlockData = unsafeWindow['locatorData_'+eBlock+'_'+nBlock];
  1724. }
  1725.  
  1726. for (let bcObj in oslBlockCacheList)
  1727. {
  1728. if(oslBlockCacheList[bcObj].blockName == arrayName)
  1729. {
  1730. oslBlockCacheList[bcObj].lastAccessed = Math.floor(new Date().getTime() / 1000);
  1731. }
  1732. }
  1733.  
  1734. let preselect = false;
  1735.  
  1736. for(let loop = 0;loop < oslBlockData.length; loop++)
  1737. {
  1738. let locatorElements = [];
  1739. // for each entry in the array, test the centrepoint position to see if it lies within the bounding box for that entry
  1740. // note that we allow a 30m tolerance on all sides of the box to allow for inaccuracies in the latlon->gridref conversion,
  1741. // 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
  1742.  
  1743. locatorElements = oslSplitEntry(oslBlockData[loop]);
  1744.  
  1745. if((locatorElements[OSL_ELM.RoadName].length > 0) || (locatorElements[OSL_ELM.RoadNumber].length > 0))
  1746. {
  1747. let tolE;
  1748. let tolN;
  1749. if(mode == OSL_MODE.NameCheck)
  1750. {
  1751. // wider tolerance when doing a namecheck lookup, to reduce falsely flagging roads as being mis-named
  1752. tolE = 20;
  1753. tolN = 30;
  1754. }
  1755. else
  1756. {
  1757. // tighter tolerance when doing other lookups
  1758. tolE = 10;
  1759. tolN = 10;
  1760. }
  1761.  
  1762. let streetName = '';
  1763. if(locatorElements[OSL_ELM.RoadNumber].length > 0)
  1764. {
  1765. streetName += locatorElements[OSL_ELM.RoadNumber];
  1766. if(locatorElements[OSL_ELM.RoadName].length > 0)
  1767. {
  1768. streetName += ' - ';
  1769. }
  1770. }
  1771. streetName += oslWazeifyStreetName(locatorElements[OSL_ELM.RoadName], false);
  1772.  
  1773. let altName = '';
  1774. if(locatorElements[OSL_ELM.AltName] != '')
  1775. {
  1776. if(locatorElements[OSL_ELM.RoadNumber].length > 0)
  1777. {
  1778. altName += locatorElements[OSL_ELM.RoadNumber];
  1779. if(locatorElements[OSL_ELM.AltName].length > 0)
  1780. {
  1781. altName += ' - ';
  1782. }
  1783. }
  1784. altName += oslWazeifyStreetName(locatorElements[OSL_ELM.AltName], false);
  1785. }
  1786.  
  1787. if(mode == OSL_MODE.OpenNames)
  1788. {
  1789. // Name search from mouse position...
  1790. let bbPix = oslGetBBCornerPixels(parseFloat(locatorElements[OSL_ELM.BoundW]), parseFloat(locatorElements[OSL_ELM.BoundE]), parseFloat(locatorElements[OSL_ELM.BoundS]), parseFloat(locatorElements[OSL_ELM.BoundN]));
  1791. bbPix[0] -= tolE;
  1792. bbPix[1] += tolE;
  1793. bbPix[2] += tolN;
  1794. bbPix[3] -= tolN;
  1795.  
  1796. if((oslMousePixelpos.x >= bbPix[0])&&(oslMousePixelpos.x <= bbPix[1])&&(oslMousePixelpos.y <= bbPix[2])&&(oslMousePixelpos.y >= bbPix[3]))
  1797. {
  1798. let area = ((bbPix[1] - bbPix[0]) * (bbPix[2] - bbPix[3]));
  1799.  
  1800. let radioID = 'oslID_'+eBlock+'_'+nBlock+'_'+loop;
  1801. let altRadioID = null;
  1802. let oslLink = '<label style="display:inline;"><input type="radio" name="oslChoice" id="'+radioID+'"';
  1803. if((streetName == oslPrevStreetName)&&(preselect === false))
  1804. {
  1805. oslLink += 'checked="true"';
  1806. preselect = true;
  1807. }
  1808. oslLink += '/>';
  1809. oslLink += streetName+'&nbsp;&nbsp;[<i>'+locatorElements[OSL_ELM.AreaName]+'</i>]</label>';
  1810.  
  1811. if(altName != '')
  1812. {
  1813. altRadioID = 'alt-'+radioID;
  1814. oslLink += '<br>&nbsp;<label style="display:inline;"><input type="radio" name="oslChoice" id="'+altRadioID+'"';
  1815. if((altName == oslPrevStreetName)&&(preselect === false))
  1816. {
  1817. oslLink += 'checked="true"';
  1818. preselect = true;
  1819. }
  1820. oslLink += '/>';
  1821. oslLink += '<i>Alt name: '+altName+'</i></label>';
  1822. }
  1823.  
  1824. if(locatorElements[OSL_ELM.Form] == 'Collapsed Dual Carriageway')
  1825. {
  1826. locatorElements[OSL_ELM.Form] = 'Dual Carriageway';
  1827. }
  1828.  
  1829. oslLink += '<br>&nbsp;<i>'+oslRoadClassifications[locatorElements[OSL_ELM.Classification]] + ' - ';
  1830. oslLink += oslRoadFunctions[locatorElements[OSL_ELM.Function]] + ' - ' + oslFormsOfWay[locatorElements[OSL_ELM.Form]];
  1831. if(locatorElements[OSL_ELM.Structure] !== '')
  1832. {
  1833. oslLink += ' ' + oslRoadStructures[locatorElements[OSL_ELM.Structure]];
  1834. }
  1835. if(locatorElements[OSL_ELM.IsPrimary] == 'Y')
  1836. {
  1837. oslLink += ' (PRN)';
  1838. }
  1839. if(locatorElements[OSL_ELM.IsTrunk] == 'Y')
  1840. {
  1841. oslLink += ' (TRN)';
  1842. }
  1843. oslLink += '</i><br>';
  1844. candidates[candidates.length++] = new oslMatch(oslLink,area,radioID,altRadioID);
  1845. }
  1846. }
  1847. else if(mode == OSL_MODE.NameCheck)
  1848. {
  1849. // NameCheck comparisons...
  1850. let bbW = parseFloat(locatorElements[OSL_ELM.BoundW]) - tolE;
  1851. let bbE = parseFloat(locatorElements[OSL_ELM.BoundE]) + tolE;
  1852. let bbS = parseFloat(locatorElements[OSL_ELM.BoundS]) - tolN;
  1853. let bbN = parseFloat(locatorElements[OSL_ELM.BoundN]) + tolN;
  1854.  
  1855. for(let i=0;i<oslOSLNCSegments.length;i++)
  1856. {
  1857. if(oslOSLNCSegments[i].match === false)
  1858. {
  1859. if
  1860. (
  1861. ((oslOSLNCSegments[i].lonA >= bbW) && (oslOSLNCSegments[i].lonA <= bbE)) &&
  1862. ((oslOSLNCSegments[i].lonB >= bbW) && (oslOSLNCSegments[i].lonB <= bbE)) &&
  1863. ((oslOSLNCSegments[i].latA >= bbS) && (oslOSLNCSegments[i].latA <= bbN)) &&
  1864. ((oslOSLNCSegments[i].latB >= bbS) && (oslOSLNCSegments[i].latB <= bbN))
  1865. )
  1866. {
  1867. if((oslOSLNCSegments[i].streetname == streetName) || (oslOSLNCSegments[i].streetname == altName)) oslOSLNCSegments[i].match = true;
  1868. }
  1869. }
  1870. }
  1871. }
  1872. }
  1873. }
  1874. }
  1875. }
  1876. }
  1877. }
  1878.  
  1879. if(mode == OSL_MODE.OpenNames)
  1880. {
  1881. let newHTML = '<b>Matches at '+oslEastings+', '+oslNorthings+'</b>';
  1882.  
  1883. if(candidates.length > 0)
  1884. {
  1885. let btnStyle = "cursor:pointer;";
  1886. btnStyle += "font-size:14px;";
  1887. btnStyle += "border: thin outset black;";
  1888. btnStyle += "padding:2px 10px 2px 10px;";
  1889. btnStyle += "background: #ccccff;";
  1890. newHTML += '<div style="margin:10px;"><span id="oslSelect" style="'+btnStyle+'">';
  1891. newHTML += 'Apply to Properties';
  1892. newHTML += '</span></div>';
  1893. if(candidates.length > 1) candidates.sort(oslSortCandidates);
  1894. for(let loop=0;loop<candidates.length;loop++)
  1895. {
  1896. newHTML += candidates[loop].oslLink;
  1897. }
  1898. newHTML += '<br>City name:<br>';
  1899. newHTML += '<label style="display:inline;"><input type="radio" name="oslCityNameOpt" id="optUseExisting"/>Use existing segment name(s)</label><br>';
  1900. newHTML += '<label style="display:inline;"><input type="radio" name="oslCityNameOpt" id="optClearExisting" />Clear existing segment name(s)</label><br>';
  1901. newHTML += '<label style="display:inline;"><input type="radio" name="oslCityNameOpt" id="optUseNewManual" />Use new name:</label><br>';
  1902. 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"';
  1903. if(sessionStorage.cityNameRB == 'optUseNewManual') newHTML += 'value="'+sessionStorage.myCity+'"/><br>';
  1904. else newHTML += 'value=""/><br>';
  1905. newHTML += '<label style="display:inline;"><input type="radio" name="oslCityNameOpt" id="optUseExistingWME" />Use name from map:</label><br>';
  1906. newHTML += '&nbsp;&nbsp;<select id="oslWMECityNames"></select><br>';
  1907. newHTML += '<label style="display:inline;"><input type="radio" name="oslCityNameOpt" id="optUseOS" />Use name from OS Gazetteer:</label><br>';
  1908. newHTML += '&nbsp;&nbsp;<select id="oslOSCityNames"></select><br>';
  1909. newHTML += '<div id="oslCNInfo"></div><br>';
  1910. oslRoadNameMatches.innerHTML = newHTML;
  1911. oslGetNearbyCityNames();
  1912. let oWCN = document.getElementById('oslWMECityNames');
  1913. var nameList = [];
  1914. let mcLonLat = oslSDK.Map.getMapCenter();
  1915. let mc = oslConvertLonLatXY(mcLonLat.lon, mcLonLat.lat);
  1916. let cityObj = oslSDK.DataModel.Cities.getAll();
  1917. for(let i = 0; i < cityObj.length; ++i)
  1918. {
  1919. let cityAttrs = cityObj[i];
  1920. if(cityAttrs.countryId === 234)
  1921. {
  1922. let cityname = cityAttrs.name;
  1923. if(cityname !== '')
  1924. {
  1925. // WME now pollutes W.model.cities.objects with a myriad of places entirely unconnected to
  1926. // anything in the current WME view, issue tracker list etc. The countryID check above
  1927. // manages to at least filter out the obviously useless entries, however that still leaves
  1928. // all the "local" ones which may well be at t'other end of the country and thus local only
  1929. // insofar as they're within the UK somewhere...
  1930. //
  1931. // To therefore deal with this, and continue presenting a genuinely useful list of potential
  1932. // city names to the user, we now perform a further distance-based filtering step to cull
  1933. // any entries for places more than 10km away from the present centrepoint of the WME view.
  1934. let cLL = oslConvertLonLatXY(cityAttrs.geometry.coordinates[0], cityAttrs.geometry.coordinates[1]);
  1935. let diffX = mc.x - cLL.x;
  1936. let diffY = mc.y - cLL.y;
  1937. let distToSquared = (diffX * diffX) + (diffY * diffY);
  1938. if(distToSquared < (10000 * 10000))
  1939. {
  1940. let countyID = [];
  1941. countyID[0] = cityAttrs.stateId;
  1942. let countyName = W.model.states.getByIds(countyID)[0].attributes.name;
  1943. if(countyName !== '')
  1944. {
  1945. cityname += ', '+countyName;
  1946. }
  1947. nameList.push(cityname);
  1948. }
  1949. }
  1950. }
  1951. }
  1952. nameList.sort();
  1953. let matchedWMEName = false;
  1954. for(let i=0; i<nameList.length; i++)
  1955. {
  1956. let listOpt = document.createElement('option');
  1957. listOpt.text = nameList[i];
  1958. oWCN.add(listOpt,null);
  1959. if(sessionStorage.cityNameRB == 'optUseExistingWME')
  1960. {
  1961. if(nameList[i] == sessionStorage.myCity)
  1962. {
  1963. oWCN.options.selectedIndex = i;
  1964. matchedWMEName = true;
  1965. }
  1966. }
  1967. }
  1968. if((!matchedWMEName) && (sessionStorage.cityNameRB == 'optUseExistingWME'))
  1969. {
  1970. oWCN.options.selectedIndex = 0;
  1971. }
  1972. document.getElementById('oslSelect').addEventListener("click", oslClick, true);
  1973. document.getElementById('oslWMECityNames').addEventListener("click", oslSelectWMEName, true);
  1974. document.getElementById('optUseExistingWME').addEventListener("click", oslSelectWMEName, true);
  1975. document.getElementById('oslOSCityNames').addEventListener("click", oslSelectOSName, true);
  1976. document.getElementById('optUseOS').addEventListener("click", oslSelectOSName, true);
  1977. document.getElementById('myCityName').addEventListener("keyup", oslCityNameKeyup, true);
  1978. document.getElementById('optUseNewManual').addEventListener("click", oslCityNameKeyup, true);
  1979. for(let loop=0;loop<candidates.length;loop++)
  1980. {
  1981. document.getElementById(candidates[loop].oslRadioID).addEventListener("click", oslRadioClick, true);
  1982. if(candidates[loop].oslAltRadioID != null)
  1983. {
  1984. document.getElementById(candidates[loop].oslAltRadioID).addEventListener("click", oslRadioClick, true);
  1985. }
  1986. }
  1987.  
  1988. document.getElementById(sessionStorage.cityNameRB).checked = true;
  1989. oslKeepUIVisible();
  1990. }
  1991. else oslRoadNameMatches.innerHTML = newHTML;
  1992. }
  1993. }
  1994. if(mode == OSL_MODE.OpenRoads)
  1995. {
  1996. // OpenRoads check
  1997. let eastingsLeft = (Math.floor(oslVPLeft/oslLocatorBlockSize) - 1) * oslLocatorBlockSize;
  1998. let eastingsRight = (Math.ceil(oslVPRight/oslLocatorBlockSize) + 1) * oslLocatorBlockSize;
  1999. let northingsTop = (Math.ceil(oslVPTop/oslLocatorBlockSize) + 1) * oslLocatorBlockSize;
  2000. let northingsBottom = (Math.floor(oslVPBottom/oslLocatorBlockSize) - 1) * oslLocatorBlockSize;
  2001.  
  2002. let highlightMode = OSL_ROADRENDERER.Init;
  2003.  
  2004. for(let eLoop = eastingsLeft; eLoop <= eastingsRight; eLoop += oslLocatorBlockSize)
  2005. {
  2006. for(let nLoop = northingsBottom; nLoop <= northingsTop; nLoop += oslLocatorBlockSize)
  2007. {
  2008. oslHighlightOpenRoads(eLoop,nLoop,highlightMode);
  2009. highlightMode = OSL_ROADRENDERER.Render;
  2010. }
  2011. }
  2012. oslHighlightOpenRoads(0,0,OSL_ROADRENDERER.Finalise);
  2013. }
  2014.  
  2015. else return '?e='+oslEastings+'&n='+oslNorthings;
  2016. }
  2017. function oslRemoveDirSuffix(currentName, dirSuffix)
  2018. {
  2019. let dPos = currentName.indexOf(dirSuffix);
  2020. if(dPos != -1)
  2021. {
  2022. let dLength = dirSuffix.length;
  2023. currentName = currentName.substr(0,dPos) + currentName.substr(dPos+dLength);
  2024. }
  2025. return currentName;
  2026. }
  2027. function oslNameCheckTrigger()
  2028. {
  2029. oslOSLNameCheckTimer = 2;
  2030. }
  2031. function oslNameComparison()
  2032. {
  2033. for(;oslONC_E<=oslEBlock_max;)
  2034. {
  2035. for(;oslONC_N<=oslNBlock_max;)
  2036. {
  2037. oslToOSGrid(oslONC_E*oslLocatorBlockSize, oslONC_N*oslLocatorBlockSize, OSL_MODE.NameCheck);
  2038. if(oslLoadingMsg === true)
  2039. {
  2040. window.setTimeout(oslNameComparison,500);
  2041. return;
  2042. }
  2043. oslONC_N++;
  2044. }
  2045. oslONC_N = oslNBlock_min;
  2046. oslONC_E++;
  2047. }
  2048. for(let i=0;i<oslOSLNCSegments.length;i++)
  2049. {
  2050. if(oslOSLNCSegments[i].match === false)
  2051. {
  2052. let pline = oslOSLNCSegments[i].pline;
  2053. pline.setAttribute("stroke","#000000");
  2054. pline.setAttribute("stroke-opacity","0.5");
  2055. pline.setAttribute("stroke-width","10");
  2056. pline.setAttribute("stroke-dasharray","none");
  2057. }
  2058. }
  2059. }
  2060. function oslNCCandidateNew(pline, lonA, latA, lonB, latB, streetname)
  2061. {
  2062. this.pline = pline;
  2063. this.lonA = lonA;
  2064. this.latA = latA;
  2065. this.lonB = lonB;
  2066. this.latB = latB;
  2067. this.streetname = streetname;
  2068. this.match = false;
  2069. }
  2070. function oslNCStateChange()
  2071. {
  2072. if(document.getElementById('_cbNCEnabled').checked === false)
  2073. {
  2074. for(let segObj in W.model.segments.objects)
  2075. {
  2076. if(W.model.segments.objects.hasOwnProperty(segObj))
  2077. {
  2078. let seg = W.model.segments.objects[segObj];
  2079. let pline = W.userscripts.getFeatureElementByDataModel(seg);
  2080. if(pline !== null)
  2081. {
  2082. pline.setAttribute("stroke-width","5");
  2083. pline.setAttribute("stroke","#dd7700");
  2084. pline.setAttribute("stroke-opacity","0.001");
  2085. pline.setAttribute("stroke-dasharray","none");
  2086. }
  2087. }
  2088. }
  2089. }
  2090. else
  2091. {
  2092. if(oslSDK.Map.getZoomLevel() >= 16) oslNameCheck();
  2093. }
  2094. }
  2095. function oslOpenRoadsStateChange(e)
  2096. {
  2097. oslMapIsStaticProcessing();
  2098. oslUpdateHighlightCBs(e.srcElement.id);
  2099. }
  2100. function oslNameCheck()
  2101. {
  2102. if((document.getElementById('_cbNCEnabled').checked === false) || (oslSDK.Map.getZoomLevel() < 16)) return;
  2103.  
  2104. let geoCenter = oslSDK.Map.getMapCenter();
  2105. geoCenter = oslConvertLonLatXY(geoCenter.lon, geoCenter.lat);
  2106. oslToOSGrid(geoCenter.y, geoCenter.x, OSL_MODE.Conversion);
  2107.  
  2108. if(oslLoadingMsg === true)
  2109. {
  2110. window.setTimeout(oslNameCheck,500);
  2111. return;
  2112. }
  2113.  
  2114. oslEBlock_min = 99999;
  2115. oslEBlock_max = -1;
  2116. oslNBlock_min = 99999;
  2117. oslNBlock_max = -1;
  2118.  
  2119. let mapExtents = oslGetExtent();
  2120. let ignoreSegment;
  2121. oslOSLNCSegments = [];
  2122.  
  2123. for(let segObj in W.model.segments.objects)
  2124. {
  2125. if(W.model.segments.objects.hasOwnProperty(segObj))
  2126. {
  2127. ignoreSegment = false;
  2128. let seg = W.model.segments.objects[segObj];
  2129. let segRT = seg.attributes.roadType;
  2130. let segBounds = seg.getOLGeometry().getBounds();
  2131.  
  2132. if
  2133. (
  2134. (segBounds.left > mapExtents.right) ||
  2135. (segBounds.right < mapExtents.left) ||
  2136. (segBounds.top < mapExtents.bottom) ||
  2137. (segBounds.bottom > mapExtents.top)
  2138. )
  2139. {
  2140. // ignore segment as it's not visible...
  2141. ignoreSegment = true;
  2142. }
  2143. if
  2144. (
  2145. (segRT < 1) ||
  2146. ((segRT > 3) && (segRT < 6)) ||
  2147. ((segRT > 8) && (segRT < 17)) ||
  2148. ((segRT > 17) && (segRT < 20)) ||
  2149. (segRT > 21)
  2150. )
  2151. {
  2152. // ignore segment as it's non-driveable...
  2153. ignoreSegment = true;
  2154. }
  2155.  
  2156. if(ignoreSegment === false)
  2157. {
  2158. let streetObj = W.model.streets.objects[seg.attributes.primaryStreetID];
  2159. if(streetObj !== undefined)
  2160. {
  2161. let currentName = streetObj.attributes.name;
  2162. let pline = W.userscripts.getFeatureElementByDataModel(seg);
  2163.  
  2164. if((currentName !== null) && (pline !== null))
  2165. {
  2166. currentName = oslRemoveDirSuffix(currentName,' (N)');
  2167. currentName = oslRemoveDirSuffix(currentName,' (S)');
  2168. currentName = oslRemoveDirSuffix(currentName,' (E)');
  2169. currentName = oslRemoveDirSuffix(currentName,' (W)');
  2170. currentName = oslRemoveDirSuffix(currentName,' (CW)');
  2171. currentName = oslRemoveDirSuffix(currentName,' (ACW)');
  2172.  
  2173. let geoComp = seg.getOLGeometry().components[0];
  2174. let endPointA = oslConvertXYLonLat(geoComp.x, geoComp.y);
  2175. oslToOSGrid(endPointA.lat, endPointA.lon, OSL_MODE.Conversion);
  2176. let eBlock = Math.floor(oslEastings/oslLocatorBlockSize);
  2177. let nBlock = Math.floor(oslNorthings/oslLocatorBlockSize);
  2178. if(eBlock < oslEBlock_min) oslEBlock_min = eBlock;
  2179. if(eBlock > oslEBlock_max) oslEBlock_max = eBlock;
  2180. if(nBlock < oslNBlock_min) oslNBlock_min = nBlock;
  2181. if(nBlock > oslNBlock_max) oslNBlock_max = nBlock;
  2182. geoComp = seg.getOLGeometry().components[seg.getOLGeometry().components.length-1];
  2183. let endPointB = oslConvertXYLonLat(geoComp.x, geoComp.y);
  2184. oslToOSGrid(endPointB.lat, endPointB.lon, OSL_MODE.Conversion);
  2185. oslOSLNCSegments.push(new oslNCCandidateNew(pline, endPointA.lon, endPointA.lat, endPointB.lon, endPointB.lat, currentName));
  2186. eBlock = Math.floor(oslEastings/oslLocatorBlockSize);
  2187. nBlock = Math.floor(oslNorthings/oslLocatorBlockSize);
  2188. if(eBlock < oslEBlock_min) oslEBlock_min = eBlock;
  2189. if(eBlock > oslEBlock_max) oslEBlock_max = eBlock;
  2190. if(nBlock < oslNBlock_min) oslNBlock_min = nBlock;
  2191. if(nBlock > oslNBlock_max) oslNBlock_max = nBlock;
  2192. }
  2193. }
  2194. }
  2195. }
  2196. }
  2197.  
  2198. if(oslOSLNCSegments.length > 0)
  2199. {
  2200. oslONC_E = oslEBlock_min;
  2201. oslONC_N = oslNBlock_min;
  2202. oslNameComparison();
  2203. }
  2204. }
  2205. function oslTenthSecondTick()
  2206. {
  2207. if(oslMOAdded === false)
  2208. {
  2209. if(document.getElementById('edit-panel') !== null)
  2210. {
  2211. oslAddLog('edit-panel mutation observer added...');
  2212. let editPanelMO = new MutationObserver(oslEditPanelCheck);
  2213. editPanelMO.observe(document.getElementById('edit-panel'),{childList:true,subtree:true});
  2214. oslMOAdded = true;
  2215. }
  2216. }
  2217.  
  2218. let hideUI = Boolean
  2219. (
  2220. (document.getElementsByClassName('menu').length > 0) &&
  2221. (document.getElementsByClassName('menu')[0].className.indexOf('not-visible') === -1) &&
  2222. (document.getElementsByClassName('menu')[0].className.indexOf('hide') === -1)
  2223. );
  2224. hideUI = Boolean(hideUI || (document.getElementsByClassName('toolbar-group open').length > 0));
  2225. if(hideUI === false)
  2226. {
  2227. oslWindow.style.zIndex = 2000;
  2228. }
  2229. else
  2230. {
  2231. oslWindow.style.zIndex = -2000;
  2232. }
  2233.  
  2234. if(!oslAdvancedMode) oslEnableAdvancedOptions();
  2235.  
  2236. let oslSelectedItems = W.selectionManager.getSelectedDataModelObjects();
  2237. if(oslSelectedItems.length == 1)
  2238. {
  2239. if(oslPrevSelected === null)
  2240. {
  2241. oslDoOSLUpdate = true;
  2242. }
  2243. else if(oslSelectedItems[0].attributes.id != oslPrevSelected)
  2244. {
  2245. oslDoOSLUpdate = true;
  2246. }
  2247. oslPrevSelected = oslSelectedItems[0].attributes.id;
  2248. }
  2249. else
  2250. {
  2251. oslPrevSelected = null;
  2252. }
  2253.  
  2254. if(document.getElementById('oslSelect') !== null)
  2255. {
  2256. let editDisabled = ((document.getElementsByClassName('full-address disabled').length > 0) || (document.getElementsByClassName('full-address-container').length === 0));
  2257. if(editDisabled === true)
  2258. {
  2259. document.getElementById('oslSelect').style.background = "rgb(160, 160, 160)";
  2260. }
  2261. else
  2262. {
  2263. document.getElementById('oslSelect').style.background = "rgb(204, 204, 255)";
  2264. }
  2265. document.getElementById('oslSelect').disabled = editDisabled;
  2266. }
  2267.  
  2268. if(oslOSLNameCheckTimer > 0)
  2269. {
  2270. if(--oslOSLNameCheckTimer === 0) oslNameCheck();
  2271. }
  2272.  
  2273. if(--oslBlockCacheTestTimer === 0)
  2274. {
  2275. oslBlockCacheTestTimer = (oslCacheDecayPeriod * 10);
  2276. let timeNow = Math.floor(new Date().getTime() / 1000);
  2277. for(let bcIdx = oslBlockCacheList.length-1; bcIdx >= 0; bcIdx--)
  2278. {
  2279. if((timeNow - oslBlockCacheList[bcIdx].lastAccessed) > oslCacheDecayPeriod)
  2280. {
  2281. delete unsafeWindow[oslBlockCacheList[bcIdx].blockName];
  2282. oslBlockCacheList.splice(bcIdx,1);
  2283. }
  2284. }
  2285. }
  2286.  
  2287. if((oslDoOSLUpdate === true) && (oslMousepos !== null))
  2288. {
  2289. // update the OS Locator matches
  2290. oslToOSGrid(oslMousepos.lat,oslMousepos.lon,OSL_MODE.OpenNames);
  2291. oslDoOSLUpdate = oslLoadingMsg;
  2292. if(!oslDoOSLUpdate) oslRadioClick();
  2293. }
  2294.  
  2295. if(oslBlocksToTest.length > 0)
  2296. {
  2297. for(let i=0; i<oslBlocksToTest.length; i++)
  2298. {
  2299. if(typeof unsafeWindow[oslBlocksToTest[i]] != undefined)
  2300. {
  2301. oslBlocksToTest.splice(i, 1);
  2302. }
  2303. }
  2304.  
  2305. if(oslBlocksToTest.length === 0)
  2306. {
  2307. oslMapIsStaticProcessing();
  2308. }
  2309. }
  2310. }
  2311. function oslEnableAdvancedOptions()
  2312. {
  2313. if (oslAdvancedMode) return;
  2314. if(W.loginManager === null) return;
  2315. if(W.loginManager.isLoggedIn() === true)
  2316. {
  2317. let thisUser = W.loginManager.user;
  2318. if (thisUser !== null && thisUser.getRank() >= 2)
  2319. {
  2320. oslAdvancedMode = true;
  2321. oslAddLog('advanced mode enabled');
  2322. }
  2323. }
  2324. }
  2325. function oslUpdateLiveMapLink()
  2326. {
  2327. let lmLink = document.getElementById('livemap-link');
  2328. if(lmLink === null)
  2329. {
  2330. window.setTimeout(oslUpdateLiveMapLink,100);
  2331. return;
  2332. }
  2333.  
  2334. // translate the zoom level between WME and live map.
  2335. let livemap_zoom = parseInt(sessionStorage.zoom);
  2336. if (livemap_zoom > 17) livemap_zoom = 17;
  2337. let livemap_url = 'https://www.waze.com/livemap/?';
  2338. livemap_url += 'lon='+sessionStorage.lon;
  2339. livemap_url += '&lat='+sessionStorage.lat;
  2340. livemap_url += '&zoom='+livemap_zoom;
  2341.  
  2342. // Modify existing livemap link to reference current position in WME
  2343. lmLink.href = livemap_url;
  2344. lmLink.target = '_blank';
  2345. }
  2346. function oslLonLatToPixel(lon, lat)
  2347. {
  2348. lon = parseFloat(lon);
  2349. lat = parseFloat(lat);
  2350. return oslSDK.Map.getMapPixelFromLonLat({lonLat: {lon: lon, lat: lat}});
  2351. }
  2352. function oslOSGridRefToPixel(osEast, osNorth)
  2353. {
  2354. oslOSGBtoWGS(osEast,osNorth);
  2355. return oslLonLatToPixel(oslLongitude, oslLatitude);
  2356. }
  2357. function oslBoundsCheck(pix1, pix2, width, height)
  2358. {
  2359. let xmin = Math.min(pix1.x,pix2.x);
  2360. let xmax = Math.max(pix1.x,pix2.x);
  2361. let ymin = Math.min(pix1.y,pix2.y);
  2362. let ymax = Math.max(pix1.y,pix2.y);
  2363. let retval =
  2364. (
  2365. (xmin <= width) &&
  2366. (xmax >= 0) &&
  2367. (ymin <= height) &&
  2368. (ymax >= 0)
  2369. );
  2370. return retval;
  2371. }
  2372. function oslHighlightOpenRoads(oslEastings, oslNorthings, mode)
  2373. {
  2374. oslBlocksToLoad = [];
  2375. if((mode === OSL_ROADRENDERER.Init) || (mode == OSL_ROADRENDERER.Render))
  2376. {
  2377. let arrayName = 'locatorData_'+oslEastings+'_'+oslNorthings;
  2378. // check to see if there's a corresponding array loaded already
  2379. if(typeof unsafeWindow[arrayName] == "undefined")
  2380. {
  2381. // create a blank placeholder which will get replaced by the actual data if the array is present on the server...
  2382. unsafeWindow[arrayName] = [];
  2383. oslBlocksToLoad.push(oslBlockPath+Math.floor(oslEastings / 100000)+'/'+Math.floor(oslNorthings / 100000)+'/'+arrayName+'.user.js');
  2384. oslBlocksToLoad.push(arrayName);
  2385. oslBlocksToTest.push(arrayName);
  2386. oslLoadBlocks();
  2387. }
  2388.  
  2389. let divWidth = document.getElementById('WazeMap').offsetWidth;
  2390. let divHeight = document.getElementById('WazeMap').offsetHeight;
  2391. let displayPoints = false;
  2392. if(mode === OSL_ROADRENDERER.Init)
  2393. {
  2394. // initialise SVG container
  2395. oslSegGeoDivInnerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="'+divWidth+'px" height="'+divHeight+'px" version="1.1">';
  2396. }
  2397.  
  2398. if(typeof unsafeWindow[arrayName] == "undefined") return;
  2399. let oslOpenRoadsData;
  2400. oslOpenRoadsData = unsafeWindow[arrayName];
  2401.  
  2402. // calculate an appropriate stroke width for the current zoom level
  2403. let strokeWidth = oslSDK.Map.getZoomLevel() - 12;
  2404. if(strokeWidth < 2) strokeWidth = 2;
  2405. if(strokeWidth > 9) strokeWidth = 9;
  2406.  
  2407. let renderByFunction = [];
  2408. renderByFunction.push(true); // Undefined roads always get rendered when the OpenRoads layer is enabled
  2409. renderByFunction.push(document.getElementById('_cbSegGeoMotorway').checked);
  2410. renderByFunction.push(document.getElementById('_cbSegGeoARoad').checked);
  2411. renderByFunction.push(document.getElementById('_cbSegGeoBRoad').checked);
  2412. renderByFunction.push(document.getElementById('_cbSegGeoMinor').checked);
  2413. renderByFunction.push(document.getElementById('_cbSegGeoLocal').checked);
  2414. if(oslAdvancedMode == true)
  2415. {
  2416. renderByFunction.push(document.getElementById('_cbSegGeoLocalAccess').checked);
  2417. renderByFunction.push(document.getElementById('_cbSegGeoRestricted').checked);
  2418. renderByFunction.push(document.getElementById('_cbSegGeoSecondary').checked);
  2419. }
  2420. else
  2421. {
  2422. renderByFunction.push(false);
  2423. renderByFunction.push(false);
  2424. renderByFunction.push(false);
  2425. }
  2426. let useFullGeo = document.getElementById('_cbOpenRoadsUseGeo').checked;
  2427. let enhancePolylines = document.getElementById('_cbOpenRoadsEnhanceGeoVis').checked;
  2428. let highlightPRN = document.getElementById('_cbOpenRoadsHighlightPRN').checked;
  2429.  
  2430. let pix1;
  2431. let pix2;
  2432. let nameColour;
  2433. for(let roadIdx = 0; roadIdx < oslOpenRoadsData.length; roadIdx++)
  2434. {
  2435. let roadEntry = oslOpenRoadsData[roadIdx];
  2436. let elements = oslSplitEntry(roadEntry);
  2437. let roadFunction = parseInt(elements[OSL_ELM.Function]);
  2438.  
  2439. if ((elements[OSL_ELM.RoadName] == '') && (elements[OSL_ELM.RoadNumber] == ''))
  2440. {
  2441. nameColour = 'cyan';
  2442. }
  2443. else
  2444. {
  2445. nameColour = 'magenta';
  2446. }
  2447.  
  2448. let renderPrimary = ((highlightPRN == true) && (elements[OSL_ELM.IsPrimary] == 'Y'));
  2449.  
  2450. if((renderPrimary == true) || (renderByFunction[roadFunction] == true))
  2451. {
  2452. pix1 = oslLonLatToPixel(elements[OSL_ELM.BoundW], elements[OSL_ELM.BoundS]);
  2453. pix2 = oslLonLatToPixel(elements[OSL_ELM.BoundE], elements[OSL_ELM.BoundN]);
  2454. if(oslBoundsCheck(pix1, pix2, divWidth, divHeight) == true)
  2455. {
  2456. if(useFullGeo == true)
  2457. {
  2458. let geoPairs = elements[OSL_ELM.Geometry].split(' ');
  2459. let geoPoints = geoPairs[0].split(' ');
  2460. let isTunnel = (elements[OSL_ELM.Structure] == 1);
  2461. pix1 = oslLonLatToPixel(geoPoints[0], geoPoints[1]);
  2462. let pline = '';
  2463. for(let pts=1; pts < geoPairs.length; pts++)
  2464. {
  2465. geoPoints = geoPairs[pts].split(' ');
  2466. pix2 = oslLonLatToPixel(geoPoints[0],geoPoints[1]);
  2467. if(oslBoundsCheck(pix1, pix2, divWidth, divHeight) == true)
  2468. {
  2469. if(displayPoints === false)
  2470. {
  2471. pline += '<polyline points="';
  2472. displayPoints = true;
  2473. }
  2474. pline += pix1.x+','+pix1.y+' '+pix2.x+','+pix2.y+' ';
  2475. }
  2476. pix1 = pix2;
  2477. }
  2478.  
  2479. if(displayPoints === true)
  2480. {
  2481. let strokeColour = oslStrokeColoursByFunction[roadFunction];
  2482. let strokeArray = '';
  2483. if(isTunnel == true)
  2484. {
  2485. strokeArray = 'stroke-dasharray:10,10;';
  2486. }
  2487. if(renderPrimary == true)
  2488. {
  2489. oslSegGeoDivInnerHTML += pline + '" style="stroke:black';
  2490. oslSegGeoDivInnerHTML += ';'+strokeArray+'stroke-width:'+(strokeWidth+25)+';stroke-linecap:round;fill:none"/>';
  2491. oslSegGeoDivInnerHTML += pline + '" style="stroke:green';
  2492. oslSegGeoDivInnerHTML += ';'+strokeArray+'stroke-width:'+(strokeWidth+21)+';stroke-linecap:round;fill:none"/>';
  2493. }
  2494. if(enhancePolylines == true)
  2495. {
  2496. oslSegGeoDivInnerHTML += pline + '" style="stroke:black';
  2497. oslSegGeoDivInnerHTML += ';'+strokeArray+'stroke-width:'+(strokeWidth+13)+';stroke-linecap:round;fill:none"/>';
  2498. oslSegGeoDivInnerHTML += pline + '" style="stroke:';
  2499. oslSegGeoDivInnerHTML += nameColour;
  2500. oslSegGeoDivInnerHTML += ';'+strokeArray+'stroke-width:'+(strokeWidth+11)+';stroke-linecap:round;fill:none"/>';
  2501. }
  2502. oslSegGeoDivInnerHTML += pline + '" style="stroke:black;'+strokeArray+'stroke-width:'+(strokeWidth+2)+';stroke-linecap:round;fill:none"/>';
  2503. oslSegGeoDivInnerHTML += pline + '" style="stroke:'+strokeColour+';'+strokeArray+'stroke-width:'+strokeWidth+';stroke-linecap:round;fill:none"/>';
  2504. displayPoints = false;
  2505. }
  2506. }
  2507. else
  2508. {
  2509. const minDim = 20;
  2510. let bWidth = Math.abs(pix2.x-pix1.x);
  2511. if(bWidth < minDim) bWidth = minDim;
  2512. let bHeight = Math.abs(pix2.y-pix1.y);
  2513. if(bHeight < minDim) bHeight = minDim;
  2514.  
  2515. oslSegGeoDivInnerHTML += '<rect x="'+pix1.x+'" y="'+pix2.y+'" width="'+bWidth+'" height="'+bHeight+'" style="fill:';
  2516. oslSegGeoDivInnerHTML += nameColour;
  2517. oslSegGeoDivInnerHTML += ';stroke:black;stroke-width:4;fill-opacity:0.25;stroke-opacity:0.25"/>';
  2518. }
  2519. }
  2520. }
  2521. }
  2522. }
  2523. else if(mode == OSL_ROADRENDERER.Finalise)
  2524. {
  2525. // finalise SVG
  2526. oslSegGeoDivInnerHTML += '</svg>';
  2527. oslSegGeoDiv.innerHTML = oslSegGeoDivInnerHTML;
  2528. }
  2529. else if(mode == OSL_ROADRENDERER.Erase)
  2530. {
  2531. // erase SVG
  2532. oslSegGeoDiv.innerHTML = '';
  2533. }
  2534. }
  2535. function oslRepositionOverlays()
  2536. {
  2537. let segLayerDiv = oslGetSegmentLayer().div;
  2538. let divTop = getComputedStyle(segLayerDiv).top;
  2539. let divLeft = getComputedStyle(segLayerDiv).left;
  2540. oslBBDiv.style.top = divTop;
  2541. oslBBDiv.style.left = divLeft;
  2542. oslSegGeoDiv.style.top = divTop;
  2543. oslSegGeoDiv.style.left = divLeft;
  2544. oslNamesDiv.style.top = divTop;
  2545. oslNamesDiv.style.left = divLeft;
  2546.  
  2547. }
  2548. function oslGetCorrectedLonLatFromPixelPos(px, py, toolbarCompensation)
  2549. {
  2550. if((toolbarCompensation) && (oslOffsetToolbar)) py -= document.getElementById('toolbar').clientHeight;
  2551. py -= document.getElementById('topbar-container').clientHeight;
  2552. return(oslSDK.Map.getLonLatFromMapPixel({x:px, y:py}));
  2553. }
  2554. function oslGetOffsetMapCentre(returnAsXY)
  2555. {
  2556. // get lon/lat of viewport centrepoint for modifying the livemap link and for passing to the external mapping sites.
  2557.  
  2558. // shift the longitude pixel offset by half the width of the WME sidebar to account for the lateral offset that would
  2559. // otherwise occur when switching between the WME tab and the other map tabs - all of those use a full-width map view,
  2560. // so their map centre is further to the left within the browser window than the WME centrepoint...
  2561. let mapVPElm = oslSDK.Map.getMapViewportElement();
  2562. let mapVPX = (mapVPElm.clientWidth / 2) - (document.getElementById('sidebar').clientWidth / 2);
  2563. let mapVPY = (mapVPElm.clientHeight / 2);
  2564.  
  2565. let retval = oslGetCorrectedLonLatFromPixelPos(mapVPX, mapVPY, false);
  2566. if(returnAsXY === true)
  2567. {
  2568. retval = oslConvertLonLatXY(retval.lon, retval.lat);
  2569. }
  2570. return retval;
  2571. }
  2572. function oslMouseMoveAndUp(e)
  2573. {
  2574. if(oslUserPrefs.ui.state == 'minimised')
  2575. {
  2576. return;
  2577. }
  2578.  
  2579. if(oslSDK.Editing.isDrawingInProgress() === true)
  2580. {
  2581. return;
  2582. }
  2583.  
  2584. if((e.pageX !== undefined) && (e.pageY !== undefined))
  2585. {
  2586. // Only valid when a mousemove event occurs...
  2587. let mapBCR = document.getElementById('map').getBoundingClientRect();
  2588. let mouseX = e.pageX - mapBCR.left;
  2589. let mouseY = e.pageY - mapBCR.top;
  2590.  
  2591. oslMousePixelpos = {x:mouseX, y:mouseY};
  2592. oslLastViewportWidth = oslSDK.Map.getMapViewportElement().clientWidth;
  2593. let mouseLonLat = oslGetCorrectedLonLatFromPixelPos(mouseX, mouseY, true);
  2594. oslMousepos = mouseLonLat;
  2595. if
  2596. (
  2597. (oslMousepos != JSON.parse(sessionStorage.oslMousepos)) ||
  2598. (
  2599. (oslLoadingMsg == true) &&
  2600. (typeof unsafeWindow[oslEvalString] != undefined)
  2601. )
  2602. )
  2603. {
  2604. oslLoadingMsg = false;
  2605. sessionStorage.oslMousepos = JSON.stringify(oslMousepos);
  2606.  
  2607. oslDoOSLUpdate = false;
  2608. // update the OSL results if there are no selected segments, but there is a highlighted segment
  2609. // which we haven't already done an update for
  2610.  
  2611. oslSegmentHighlighted = false;
  2612. let noneSelected = (W.selectionManager.getSelectedDataModelObjects().length === 0);
  2613. if(noneSelected === true)
  2614. {
  2615. let segLayerFeatures = oslGetSegmentLayer().features;
  2616. for(let slIdx=0; slIdx < segLayerFeatures.length; slIdx++)
  2617. {
  2618. if(segLayerFeatures[slIdx].renderIntent == 'highlight')
  2619. {
  2620. if(slIdx != oslPrevHighlighted)
  2621. {
  2622. oslPrevHighlighted = slIdx;
  2623. }
  2624. oslDoOSLUpdate = true;
  2625. oslSegmentHighlighted = true;
  2626. }
  2627. }
  2628. }
  2629. }
  2630. }
  2631.  
  2632. // Valid for both mousemove and zoomend events...
  2633. let gomc = oslGetOffsetMapCentre(false);
  2634. let lat = gomc.lat;
  2635. let lon = gomc.lon;
  2636. let zoom = oslSDK.Map.getZoomLevel();
  2637. // compare the new parameters against the persistent copies, and update the external links
  2638. // only if there's a change required - the newly-inserted <a> element can't be clicked
  2639. // on until the insertion process is complete, and if we were to re-insert it every timeout
  2640. // then it'd spend a lot of its time giving the appearance of being clickable but without
  2641. // actually doing anything...
  2642. if((zoom != parseInt(sessionStorage.zoom))||(lat != parseFloat(sessionStorage.lat))||(lon != parseFloat(sessionStorage.lon)))
  2643. {
  2644. let country = W.model.getTopCountry()?.attributes?.name;
  2645.  
  2646. if(country == "United Kingdom")
  2647. {
  2648. if (oslInUK === false)
  2649. {
  2650. oslAddLog('location is the UK, enabling full UI...');
  2651. oslInUK = true;
  2652. document.getElementById('oslOSLDiv').style.display = "block";
  2653. document.getElementById('oslNCDiv').style.display = "block";
  2654. document.getElementById('oslSegGeoUIDiv').style.display = "block";
  2655. document.getElementById('oslGazTagsDiv').style.display = "block";
  2656. document.getElementById('_extlinksUK').style.display = "block";
  2657. }
  2658. }
  2659. else
  2660. {
  2661. // ...somewhere not yet supported, or WME isn't telling us just yet...
  2662. oslAddLog('location not recognised, disabling UK-specific parts of UI...');
  2663. oslInUK = false;
  2664. oslInLondon = false;
  2665. document.getElementById('oslOSLDiv').style.display = "none";
  2666. document.getElementById('oslNCDiv').style.display = "none";
  2667. document.getElementById('oslSegGeoUIDiv').style.display = "none";
  2668. document.getElementById('oslGazTagsDiv').style.display = "none";
  2669. document.getElementById('_extlinksUK').style.display = "none";
  2670. }
  2671.  
  2672. if(oslInUK === true)
  2673. {
  2674. // we're in the UK, so test to see if we're within the approximate Greater London bounding box
  2675. if((lon >= -0.55) && (lon <= 0.30) && (lat >= 51.285) && (lat <= 51.695))
  2676. {
  2677. oslInLondon = true;
  2678. document.getElementById('lrrCtrls').style.display='inline';
  2679. }
  2680. else
  2681. {
  2682. oslInLondon = false;
  2683. document.getElementById('lrrCtrls').style.display='none';
  2684. }
  2685. }
  2686.  
  2687. if(zoom != sessionStorage.zoom)
  2688. {
  2689. if(zoom < 16) document.getElementById('_cbNCEnabled').disabled = true;
  2690. else document.getElementById('_cbNCEnabled').disabled = false;
  2691. }
  2692. // update the persistent vars with the new position
  2693. sessionStorage.zoom = zoom;
  2694. sessionStorage.lat = lat;
  2695. sessionStorage.lon = lon;
  2696.  
  2697. if(oslInUK === true)
  2698. {
  2699. // update the external site datalinks with the new coords/zoom
  2700. if(document.getElementById('_cbAutoTrackOSOD').checked == 1)
  2701. {
  2702. oslOSODClick();
  2703. }
  2704. if(document.getElementById('_cbAutoTrackRWO').checked == 1)
  2705. {
  2706. oslUpdateOneKey();
  2707. }
  2708. if(document.getElementById('_cbAutoTrackLRR').checked == 1)
  2709. {
  2710. oslUpdateLRRKey();
  2711. }
  2712. }
  2713.  
  2714. // wait to update the livemap link, as WME now does its own update after this point so any changes we make here
  2715. // end up being wiped out...
  2716. window.setTimeout(oslUpdateLiveMapLink,100);
  2717.  
  2718. document.getElementById('_linkPermalink').href = document.getElementsByClassName('WazeControlPermalink')[0].getElementsByClassName('permalink')[0].href;
  2719. }
  2720. if((e.type == "mouseup") || (e.type == "zoomend"))
  2721. {
  2722. oslMapIsStaticProcessing();
  2723. }
  2724. }
  2725. function oslSetAccessKey(keyName, requiresXY, requiresOSCoords)
  2726. {
  2727. let lat = "0.0";
  2728. let lon = "0.0";
  2729. let zoom = oslSDK.Map.getZoomLevel();
  2730.  
  2731. if(requiresOSCoords === true)
  2732. {
  2733. let geoCenter = oslSDK.Map.getMapCenter();
  2734. oslToOSGrid(geoCenter.lat, geoCenter.lon, OSL_MODE.Conversion);
  2735.  
  2736. lat = oslNorthings;
  2737. lon = oslEastings;
  2738. }
  2739. else
  2740. {
  2741. let gomc = oslGetOffsetMapCentre(requiresXY);
  2742. if(requiresXY === true)
  2743. {
  2744. lat = gomc.y;
  2745. lon = gomc.x;
  2746. }
  2747. else
  2748. {
  2749. lat = gomc.lat.toFixed(8);
  2750. lon = gomc.lon.toFixed(8);
  2751. }
  2752. }
  2753. GM_setValue(keyName,Date.now()+','+lat+','+lon+','+zoom);
  2754. }
  2755. function oslOSODClick()
  2756. {
  2757. oslSetAccessKey('_osodAccessKey', true, false);
  2758. }
  2759. function oslUpdateOneKey()
  2760. {
  2761. oslSetAccessKey('_oneAccessKey', false, false);
  2762. }
  2763. function oslUpdateLRRKey()
  2764. {
  2765. oslSetAccessKey('_lrrAccessKey', false, true);
  2766. }
  2767. function oslMapIsStaticProcessing()
  2768. {
  2769. if(oslUserPrefs.ui.state == 'maximised')
  2770. {
  2771. let tCenter = oslSDK.Map.getMapCenter();
  2772. let tZoom = oslSDK.Map.getZoomLevel();
  2773. if
  2774. (
  2775. (oslRORCenter == null) || (tCenter.lat != oslRORCenter.lat) || (tCenter.lon != oslRORCenter.lon) ||
  2776. (oslRORZoom == null) || (tZoom != oslRORZoom)
  2777. )
  2778. {
  2779. oslRORCenter = tCenter;
  2780. oslRORZoom = tZoom;
  2781. window.setTimeout(oslMapIsStaticProcessing, 50);
  2782. return;
  2783. }
  2784.  
  2785. let geoCenter = oslSDK.Map.getMapCenter();
  2786. oslToOSGrid(geoCenter.lat, geoCenter.lon, OSL_MODE.Conversion);
  2787.  
  2788. oslNameCheck();
  2789. if(oslInUK === true)
  2790. {
  2791. let extent = oslGetExtent();
  2792. // recalculate the map viewport extents in terms of oslEastings/oslNorthings
  2793. let vpHalfWidth = (extent.right - extent.left) / (2 * 1.61);
  2794. let vpHalfHeight = (extent.top - extent.bottom) / (2 * 1.61);
  2795. oslVPLeft = oslEastings - vpHalfWidth;
  2796. oslVPRight = oslEastings + vpHalfWidth;
  2797. oslVPBottom = oslNorthings - vpHalfHeight;
  2798. oslVPTop = oslNorthings + vpHalfHeight;
  2799.  
  2800. if(document.getElementById('_cbOpenRoadsEnabled').checked === true)
  2801. {
  2802. oslToOSGrid(oslEastings,oslNorthings,OSL_MODE.OpenRoads);
  2803. }
  2804. else
  2805. {
  2806. oslHighlightOpenRoads(0,0,OSL_ROADRENDERER.Erase);
  2807. }
  2808. oslGetVisibleCityNames();
  2809. }
  2810. else
  2811. {
  2812. oslHighlightOpenRoads(0,0,OSL_ROADRENDERER.Erase);
  2813. }
  2814. oslRepositionOverlays();
  2815. oslRadioClick(); // this regenerates the bounding boxes on the repositioned overlay...
  2816. }
  2817. }
  2818. function oslTestPointerOutsideMap(mX, mY)
  2819. {
  2820. let mapElm = document.getElementById("map");
  2821. if(mapElm === undefined) return false;
  2822.  
  2823. let bLeft = mapElm.parentElement.offsetLeft;
  2824. let bRight = (bLeft + mapElm.offsetWidth);
  2825. let bTop = (mapElm.parentElement.offsetTop + document.getElementById("topbar-container").clientHeight);
  2826. let bBottom = (mapElm.parentElement.offsetTop + mapElm.offsetHeight + document.getElementById("topbar-container").clientHeight - document.getElementsByClassName("wz-map-ol-footer")[0].clientHeight);
  2827.  
  2828. if((mX < bLeft) || (mX > bRight) || (mY < bTop) || (mY > bBottom)) return true;
  2829. else return false;
  2830. }
  2831. function oslMouseOut(e)
  2832. {
  2833. if(oslTestPointerOutsideMap(e.clientX, e.clientY))
  2834. {
  2835. // when the mouse pointer leaves the map area, WME treats it similarly to a mouseup
  2836. // event without generating an actual mouseup event, so we need to do the same...
  2837. oslMapIsStaticProcessing();
  2838. }
  2839. }
  2840. function oslCancelEvent(e)
  2841. {
  2842. e = e ? e : window.event;
  2843. if(e.stopPropagation)
  2844. e.stopPropagation();
  2845. if(e.preventDefault)
  2846. e.preventDefault();
  2847. e.cancelBubble = true;
  2848. e.cancel = true;
  2849. e.returnValue = false;
  2850. return false;
  2851. }
  2852. function oslOSLDivMouseDown(e)
  2853. {
  2854. oslPrevMouseX = e.pageX;
  2855. oslPrevMouseY = e.pageY;
  2856. oslDivDragging = true;
  2857. oslDragBar.style.cursor = 'move';
  2858. document.body.addEventListener('mousemove', oslOSLDivMouseMove, false);
  2859. document.body.addEventListener('mouseup', oslOSLDivMouseUp, false);
  2860. // lock the UI width during a drag so we can correctly detect for falling off the window edge
  2861. oslWindow.style.width = oslWindow.getBoundingClientRect().width+'px';
  2862. return true;
  2863. }
  2864. function oslOSLDivMouseUp()
  2865. {
  2866. if(oslDivDragging)
  2867. {
  2868. oslDivDragging = false;
  2869. oslUserPrefs.ui.x = oslOSLDivLeft;
  2870. oslUserPrefs.ui.y = oslOSLDivTop;
  2871. oslWriteUserPrefs();
  2872. // unlock the UI width again so we can expand/contract as required based on other script behaviour
  2873. oslWindow.style.width = "auto";
  2874. }
  2875. oslDragBar.style.cursor = 'auto';
  2876. document.body.removeEventListener('mousemove', oslOSLDivMouseMove, false);
  2877. document.body.removeEventListener('mouseup', oslOSLDivMouseUp, false);
  2878. return true;
  2879. }
  2880. function oslOSLDivMouseMove(e)
  2881. {
  2882. let vpHeight = window.innerHeight;
  2883. let vpWidth = window.innerWidth;
  2884.  
  2885. oslOSLDivTop = parseInt(oslOSLDivTop) + parseInt((e.pageY - oslPrevMouseY));
  2886. oslOSLDivLeft = parseInt(oslOSLDivLeft) + parseInt((e.pageX - oslPrevMouseX));
  2887. oslPrevMouseX = e.pageX;
  2888. oslPrevMouseY = e.pageY;
  2889.  
  2890. if(oslOSLDivTop < 0) oslOSLDivTop = 0;
  2891. if(oslOSLDivTop + 16 >= vpHeight) oslOSLDivTop = vpHeight-16;
  2892. if(oslOSLDivLeft < 0) oslOSLDivLeft = 0;
  2893. if(oslOSLDivLeft + 32 >= vpWidth) oslOSLDivLeft = vpWidth-32;
  2894.  
  2895. oslWindow.style.top = oslOSLDivTop+'px';
  2896. oslWindow.style.left = oslOSLDivLeft+'px';
  2897.  
  2898. return oslCancelEvent(e);
  2899. }
  2900. function oslKeepUIVisible()
  2901. {
  2902. let vpHeight = window.innerHeight;
  2903. let topbarHeight = document.getElementById('toolbar').offsetHeight;
  2904. let maxUIHeight = (vpHeight - topbarHeight);
  2905. // Restore auto-sizing so we can see the effect of whatever UI change caused us to get here...
  2906. oslWindow.style.height = "auto";
  2907. oslWindow.style.overflow = "hidden";
  2908.  
  2909. // If the bottom edge of the ui would fall off the bottom edge of the map viewport, nudge
  2910. // the UI up as far as required to keep the whole UI visible. If this would however require
  2911. // the top edge of the UI to be pushed off the top of the map viewport, constrain the UI
  2912. // height to the viewport height and enable a scrollbar...
  2913. if(oslWindow.getBoundingClientRect().bottom >= maxUIHeight)
  2914. {
  2915. let newTop = (vpHeight-oslWindow.getBoundingClientRect().height);
  2916. if(newTop < topbarHeight)
  2917. {
  2918. newTop = topbarHeight;
  2919. oslWindow.style.height = maxUIHeight+'px';
  2920. oslWindow.style.overflow = "scroll";
  2921. }
  2922. oslOSLDivTop = newTop;
  2923. oslUserPrefs.ui.y = oslOSLDivTop;
  2924. oslWindow.style.top = oslOSLDivTop+'px';
  2925. }
  2926. }
  2927. function oslMinimiseDiv(divID)
  2928. {
  2929. let tDiv = document.getElementById(divID);
  2930. if(tDiv != null)
  2931. {
  2932. tDiv.style.height = '0px';
  2933. tDiv.style.padding = '0px';
  2934. tDiv.style.overflow = 'hidden';
  2935. }
  2936. }
  2937. function oslMaximiseDiv(divID)
  2938. {
  2939. let tDiv = document.getElementById(divID);
  2940. if(tDiv != null)
  2941. {
  2942. tDiv.style.height = 'auto';
  2943. tDiv.style.padding = '2px';
  2944. tDiv.style.overflow = 'auto';
  2945. }
  2946. }
  2947. function oslWindowMaximise()
  2948. {
  2949. let tHTML = '';
  2950. tHTML += '<span style="float:left"><a href="'+oslUpdateURL+'" target="_blank"><b>WMEOpenData</a> v'+oslVersion+'</b></span>';
  2951. tHTML += '<span id="_minimax" style="float:right"><i class="fa fa-chevron-circle-up"></i></span>';
  2952. tHTML += '<br>';
  2953. oslDragBar.innerHTML = tHTML;
  2954. document.getElementById('_minimax').addEventListener('click', oslWindowMinimise, false);
  2955.  
  2956. oslMaximiseDiv('oslOSLDiv');
  2957. oslMaximiseDiv('oslNCDiv');
  2958. oslMaximiseDiv('oslGazTagsDiv');
  2959. oslMaximiseDiv('oslSegGeoUIDiv');
  2960. oslMaximiseDiv('oslMLCDiv');
  2961. oslKeepUIVisible();
  2962. oslUserPrefs.ui.state = 'maximised';
  2963. oslWriteUserPrefs();
  2964. oslRepositionOverlays();
  2965. oslMapIsStaticProcessing();
  2966. }
  2967. function oslWindowMinimise()
  2968. {
  2969. let tHTML = '';
  2970. tHTML += '<span style="float:left"><b>WMEOpenData v'+oslVersion+'</b></span>';
  2971. tHTML += '<span id="_minimax" style="float:right"><i class="fa fa-chevron-circle-down"></i></span>';
  2972. tHTML += '<br>';
  2973. oslDragBar.innerHTML = tHTML;
  2974. document.getElementById('_minimax').addEventListener('click', oslWindowMaximise, false);
  2975.  
  2976. oslMinimiseDiv('oslOSLDiv');
  2977. oslMinimiseDiv('oslNCDiv');
  2978. oslMinimiseDiv('oslGazTagsDiv');
  2979. oslMinimiseDiv('oslSegGeoUIDiv');
  2980. oslMinimiseDiv('oslMLCDiv');
  2981. oslKeepUIVisible();
  2982. oslBBDiv.innerHTML = '';
  2983. oslNamesDiv.innerHTML = '';
  2984. oslSegGeoDiv.innerHTML = '';
  2985. oslUserPrefs.ui.state = 'minimised';
  2986. oslWriteUserPrefs();
  2987. }
  2988. function oslSubDivSetState(divID, state)
  2989. {
  2990. let tSubDiv = document.getElementById(divID).children[1];
  2991. let tToggle = document.getElementById(divID).children[0].children[1].children[0];
  2992. if(state == 'minimised')
  2993. {
  2994. tSubDiv.style.height = "0px";
  2995. tSubDiv.style.padding = "0px";
  2996. tSubDiv.style.overflow = "hidden";
  2997. tToggle.className = "fa fa-chevron-circle-down";
  2998. }
  2999. else
  3000. {
  3001. tSubDiv.style.height = "auto";
  3002. tSubDiv.style.width = "100%";
  3003. tSubDiv.style.padding = "2px";
  3004. tSubDiv.style.overflow = "auto";
  3005. tToggle.className = "fa fa-chevron-circle-up";
  3006. }
  3007. }
  3008. function oslUIMinMax(e)
  3009. {
  3010. let headerDivID = e.srcElement.parentElement.parentElement.parentElement.id;
  3011. let tSubDiv = document.getElementById(headerDivID).children[1];
  3012. if(tSubDiv != null)
  3013. {
  3014. let newState;
  3015. if(tSubDiv.style.height == "auto")
  3016. {
  3017. newState = 'minimised';
  3018. }
  3019. else
  3020. {
  3021. newState = 'maximised';
  3022. }
  3023. oslSubDivSetState(headerDivID, newState);
  3024. oslUpdateMinMax(headerDivID, newState);
  3025. }
  3026. oslKeepUIVisible();
  3027. }
  3028. function oslIsLanesTabActive()
  3029. {
  3030. let retval = false;
  3031. if(document.querySelector('wz-tab.lanes-tab') !== null)
  3032. {
  3033. if(document.querySelector('wz-tab.lanes-tab').getAttribute("is-active") !== null)
  3034. {
  3035. retval = true;
  3036. }
  3037. }
  3038. return retval;
  3039. }
  3040. function oslEditPanelCheck()
  3041. {
  3042. if
  3043. (
  3044. (oslIsLanesTabActive() == true) ||
  3045. (document.getElementById('edit-panel')?.getElementsByClassName('map-comment-feature-editor').length)
  3046. )
  3047. {
  3048. document.body.style.overflow = "auto";
  3049. }
  3050. else
  3051. {
  3052. document.body.style.overflow = "hidden";
  3053. }
  3054. }
  3055. function oslSetupBBDiv()
  3056. {
  3057. // add a new div to the map viewport, to hold the bounding box SVG
  3058. oslAddLog('create bounding box DIV');
  3059. oslBBDiv = document.createElement('div');
  3060. oslBBDiv.id = "oslBBDiv";
  3061. oslBBDiv.style.position = 'absolute';
  3062. let segLayerDiv = oslGetSegmentLayer().div;
  3063. oslBBDiv.style.top = getComputedStyle(segLayerDiv).top;
  3064. oslBBDiv.style.left = getComputedStyle(segLayerDiv).left;
  3065. oslBBDiv.style.overflow = 'hidden';
  3066. oslBBDiv.style.width = window.innerWidth;
  3067. oslBBDiv.style.height = window.innerHeight;
  3068. oslWazeMapElement.appendChild(oslBBDiv);
  3069. }
  3070. function oslSetupORDiv()
  3071. {
  3072. // add a new div to the map viewport, to hold the OpenRoads SVG
  3073. oslAddLog('create OpenRoads DIV');
  3074. oslSegGeoDiv = document.createElement('div');
  3075. oslSegGeoDiv.id = "oslSegGeoDiv";
  3076. oslSegGeoDiv.style.position = 'absolute';
  3077. let segLayerDiv = oslGetSegmentLayer().div;
  3078. oslSegGeoDiv.style.top = getComputedStyle(segLayerDiv).top;
  3079. oslSegGeoDiv.style.left = getComputedStyle(segLayerDiv).left;
  3080. oslSegGeoDiv.style.overflow = 'hidden';
  3081. oslSegGeoDiv.style.width = window.innerWidth;
  3082. oslSegGeoDiv.style.height = window.innerHeight;
  3083. oslWazeMapElement.appendChild(oslSegGeoDiv);
  3084. }
  3085. function oslSetupNamesDiv()
  3086. {
  3087. // add a new div to the map viewport, to hold the place names SVG
  3088. oslAddLog('create place names DIV');
  3089. oslNamesDiv = document.createElement('div');
  3090. oslNamesDiv.id = "oslNamesDiv";
  3091. oslNamesDiv.style.position = 'absolute';
  3092. oslNamesDiv.style.pointerEvents = 'none';
  3093. let segLayerDiv = oslGetSegmentLayer().div;
  3094. oslNamesDiv.style.top = getComputedStyle(segLayerDiv).top;
  3095. oslNamesDiv.style.left = getComputedStyle(segLayerDiv).left;
  3096. oslNamesDiv.style.overflow = 'hidden';
  3097. oslNamesDiv.style.width = window.innerWidth;
  3098. oslNamesDiv.style.height = window.innerHeight;
  3099. oslWazeMapElement.appendChild(oslNamesDiv);
  3100. }
  3101. function oslGenerateOpenRoadsCBHTML(isStd, ID, label, indentLevel)
  3102. {
  3103. let tHTML = '';
  3104. let indent = 'padding-left: '+(indentLevel * 0.5)+'em;';
  3105. tHTML += '<label style="display: inline; '+indent+'">';
  3106. tHTML += '<input type="checkbox" name="oslOpenRoads_';
  3107. if(isStd == true) tHTML += 'std';
  3108. else tHTML += 'adv';
  3109. tHTML += '" id="' + ID + '" />';
  3110. tHTML += label + '</label>';
  3111. return tHTML;
  3112. }
  3113. function oslGenerateCollapsibleSubDiv(headerText, subDivHTML)
  3114. {
  3115. let tHTML;
  3116. tHTML = '<div><span style="float:left"><b>'+headerText+'</b></span>';
  3117. tHTML += '<span name="oslMinMaxToggle" style="float:right"><i class="fa fa-chevron-circle-down"></i></span></div>';
  3118. tHTML += '<div style="height: 0px; padding: 0px; overflow: hidden;">';
  3119. tHTML += subDivHTML;
  3120. tHTML += '</div>';
  3121. return tHTML;
  3122. }
  3123. function oslSetupUI()
  3124. {
  3125. let subDivHTML;
  3126.  
  3127. // add a new div to hold the OS Locator results, in the form of a draggable window
  3128. oslAddLog('create lookup results DIV');
  3129. oslWindow = document.createElement('div');
  3130. oslWindow.id = "oslWindow";
  3131. oslWindow.style.position = 'absolute';
  3132. oslWindow.style.border = '1px solid #BBDDBB';
  3133. oslWindow.style.borderRadius = '4px';
  3134. oslWindow.style.overflow = 'hidden';
  3135. oslWindow.style.zIndex = 2000;
  3136. oslWindow.style.opacity = 0;
  3137. oslWindow.style.transitionProperty = "opacity";
  3138. oslWindow.style.transitionDuration = "1000ms";
  3139. oslWindow.style.webkitTransitionProperty = "opacity";
  3140. oslWindow.style.webkitTransitionDuration = "1000ms";
  3141. oslWindow.style.boxShadow = '5px 5px 10px Silver';
  3142. document.body.appendChild(oslWindow);
  3143.  
  3144. // dragbar div
  3145. oslAddLog('create dragbar DIV');
  3146. oslDragBar = document.createElement('div');
  3147. oslDragBar.id = "oslDragBar";
  3148. oslDragBar.style.backgroundColor = '#D0D0D0';
  3149. oslDragBar.style.padding = '4px';
  3150. oslDragBar.style.fontSize = '16px';
  3151. oslDragBar.style.lineHeight = '18px';
  3152. oslWindow.appendChild(oslDragBar);
  3153.  
  3154. // OS results div
  3155. oslAddLog('create results DIV');
  3156. oslOSLDiv = document.createElement('div');
  3157. oslOSLDiv.id = "oslOSLDiv";
  3158. oslOSLDiv.style.backgroundColor = '#DDFFDD';
  3159. oslOSLDiv.style.padding = '2px';
  3160. oslOSLDiv.style.fontSize = '14px';
  3161. oslOSLDiv.style.lineHeight = '16px';
  3162. oslOSLDiv.style.display = 'none';
  3163.  
  3164. subDivHTML = "<div id='oslRoadNameMatches'></div>";
  3165. oslOSLDiv.innerHTML = oslGenerateCollapsibleSubDiv("OS Open Names", subDivHTML);
  3166.  
  3167. oslWindow.appendChild(oslOSLDiv);
  3168.  
  3169. // Segment geometry control div
  3170. oslAddLog('create SegGeo control DIV');
  3171. oslSegGeoUIDiv = document.createElement('div');
  3172. oslSegGeoUIDiv.id = "oslSegGeoUIDiv";
  3173. oslSegGeoUIDiv.style.backgroundColor = '#40A040';
  3174. oslSegGeoUIDiv.style.padding = '2px';
  3175. oslSegGeoUIDiv.style.fontSize = '14px';
  3176. oslSegGeoUIDiv.style.lineHeight = '16px';
  3177. oslSegGeoUIDiv.style.display = 'none';
  3178.  
  3179. subDivHTML = oslGenerateOpenRoadsCBHTML(true, "_cbOpenRoadsEnabled", "Highlight by Classification:", 0) + '<br>';
  3180. subDivHTML += oslGenerateOpenRoadsCBHTML(true, "_cbSegGeoMotorway", "Motorway", 1) + '<br>';
  3181. subDivHTML += oslGenerateOpenRoadsCBHTML(true, "_cbSegGeoARoad", "A Road", 1) + '<br>';
  3182. subDivHTML += oslGenerateOpenRoadsCBHTML(true, "_cbSegGeoBRoad", "B Road", 1) + '<br>';
  3183. subDivHTML += oslGenerateOpenRoadsCBHTML(true, "_cbSegGeoMinor", "Minor Road", 1) + '<br>';
  3184. subDivHTML += oslGenerateOpenRoadsCBHTML(true, "_cbSegGeoLocal", "Local Road", 1) + '<br>';
  3185. if(oslAdvancedMode == true)
  3186. {
  3187. subDivHTML += oslGenerateOpenRoadsCBHTML(false, "_cbSegGeoLocalAccess", "Local Access Road", 1) + '<br>';
  3188. subDivHTML += oslGenerateOpenRoadsCBHTML(false, "_cbSegGeoRestricted", "Restricted Access Road", 1) + '<br>';
  3189. subDivHTML += oslGenerateOpenRoadsCBHTML(false, "_cbSegGeoSecondary", "Secondary Access Road", 1) + '<br>';
  3190. }
  3191. subDivHTML += '<br>' + oslGenerateOpenRoadsCBHTML(true, "_cbOpenRoadsUseGeo", "Show as polylines", 0) + '<br>';
  3192. subDivHTML += oslGenerateOpenRoadsCBHTML(true, "_cbOpenRoadsEnhanceGeoVis", "Enhance polyline visibility", 1) + '<br><br>';
  3193. subDivHTML += oslGenerateOpenRoadsCBHTML(true, "_cbOpenRoadsHighlightPRN", "Highlight PRN", 1);
  3194. oslSegGeoUIDiv.innerHTML = oslGenerateCollapsibleSubDiv("OS Open Roads", subDivHTML);
  3195. oslWindow.appendChild(oslSegGeoUIDiv);
  3196.  
  3197. // Gazetteer Tags div
  3198. oslAddLog('create GazTags DIV');
  3199. oslGazTagsDiv = document.createElement('div');
  3200. oslGazTagsDiv.id = "oslGazTagsDiv";
  3201. oslGazTagsDiv.style.backgroundColor = '#FFFF80';
  3202. oslGazTagsDiv.style.padding = '2px';
  3203. oslGazTagsDiv.style.fontSize = '14px';
  3204. oslGazTagsDiv.style.lineHeight = '16px';
  3205. oslGazTagsDiv.style.display = 'none';
  3206.  
  3207. subDivHTML = oslGenerateOpenRoadsCBHTML(true, "_cbGazTagsEnabled", "Show by type:", 0) + '<br>';
  3208. subDivHTML += oslGenerateOpenRoadsCBHTML(true, "_cbGazTagsCity", "City", 1) + '<br>';
  3209. subDivHTML += oslGenerateOpenRoadsCBHTML(true, "_cbGazTagsTown", "Town", 1) + '<br>';
  3210. subDivHTML += oslGenerateOpenRoadsCBHTML(true, "_cbGazTagsVillage", "Village", 1) + '<br>';
  3211. subDivHTML += oslGenerateOpenRoadsCBHTML(true, "_cbGazTagsHamlet", "Hamlet", 1) + '<br>';
  3212. subDivHTML += oslGenerateOpenRoadsCBHTML(true, "_cbGazTagsOther", "Other", 1);
  3213. oslGazTagsDiv.innerHTML = oslGenerateCollapsibleSubDiv("Gazetteer Tags", subDivHTML);
  3214. oslWindow.appendChild(oslGazTagsDiv);
  3215.  
  3216. // NameCheck div
  3217. oslAddLog('create NameCheck DIV');
  3218. oslNCDiv = document.createElement('div');
  3219. oslNCDiv.id = "oslNCDiv";
  3220. oslNCDiv.style.backgroundColor = '#DDDDFF';
  3221. oslNCDiv.style.padding = '2px';
  3222. oslNCDiv.style.fontSize = '14px';
  3223. oslNCDiv.style.lineHeight = '16px';
  3224. oslNCDiv.style.display = 'none';
  3225.  
  3226. subDivHTML = '<label style="display:inline;"><input type="checkbox" id="_cbNCEnabled" />Highlight potential naming errors</label>';
  3227. subDivHTML += '<br><i>Note: only active at at zoom level 16 and above</i>';
  3228. oslNCDiv.innerHTML = oslGenerateCollapsibleSubDiv("NameCheck", subDivHTML);
  3229. oslWindow.appendChild(oslNCDiv);
  3230.  
  3231. // external links div
  3232. oslAddLog('create extern links DIV');
  3233. oslMLCDiv = document.createElement('div');
  3234. oslMLCDiv.id = "oslMLCDiv";
  3235. oslMLCDiv.style.backgroundColor = '#EEFFEE';
  3236. oslMLCDiv.style.padding = '2px';
  3237. oslMLCDiv.style.fontSize = '14px';
  3238. oslMLCDiv.style.lineHeight = '16px';
  3239. // add the anchors and auto-track checkboxes for external sites. Note that some urls are blank at this stage,
  3240. // they'll be filled in by oslMouseMoveAndUp()...
  3241. subDivHTML = '<div id="_extlinksUK" style="display: none;">';
  3242. subDivHTML += '<a href="https://search-property-information.service.gov.uk/search/search-by-map/" id="_linkOSOD" target="_osopendata">OS OpenData</a> <input type="checkbox" id="_cbAutoTrackOSOD"></input> | ';
  3243. subDivHTML += '<a href="https://one.network?showlinks=true" id="_linkRWO" target="_roadworksorg">one.network</a> <input type="checkbox" id="_cbAutoTrackRWO"></input><br>';
  3244. subDivHTML += '<div id="lrrCtrls"><a href="https://public.londonworks.gov.uk/roadworks/home" id="_linkLRR" target="_londonregister">London Roadworks</a> <input type="checkbox" id="_cbAutoTrackLRR"></input></div>';
  3245. subDivHTML += '</div>';
  3246. subDivHTML += '<br>(Checkboxes enable auto-tracking)';
  3247. subDivHTML += '<br><br><a href="" id="_linkPermalink">Permalink</a>';
  3248. oslMLCDiv.innerHTML = oslGenerateCollapsibleSubDiv("External resources", subDivHTML);
  3249. oslWindow.appendChild(oslMLCDiv);
  3250. }
  3251. function oslSetupEventListeners()
  3252. {
  3253. oslAddLog('adding event listeners');
  3254. oslDragBar.addEventListener('mousedown', oslOSLDivMouseDown, false);
  3255. oslDragBar.addEventListener('mouseup', oslOSLDivMouseUp, false);
  3256. W.map.events.register("zoomend", null, oslMouseMoveAndUp);
  3257. W.map.events.register("mouseout", null, oslMouseOut);
  3258. document.getElementById('_cbNCEnabled').addEventListener('click', oslNCStateChange, false);
  3259. document.getElementById('_linkOSOD').addEventListener('click', oslOSODClick, false);
  3260. document.getElementById('_linkRWO').addEventListener('click', oslUpdateOneKey, false);
  3261. document.getElementById('_linkLRR').addEventListener('click', oslUpdateLRRKey, false);
  3262.  
  3263. for(let idx = 0; idx < document.getElementsByName('oslOpenRoads_std').length; ++idx)
  3264. {
  3265. document.getElementsByName('oslOpenRoads_std')[idx].addEventListener('click', oslOpenRoadsStateChange, false);
  3266. }
  3267. if(oslAdvancedMode == true)
  3268. {
  3269. for(let idx = 0; idx < document.getElementsByName('oslOpenRoads_adv').length; ++idx)
  3270. {
  3271. document.getElementsByName('oslOpenRoads_adv')[idx].addEventListener('click', oslOpenRoadsStateChange, false);
  3272. }
  3273. }
  3274.  
  3275. for(let idx = 0; idx < document.getElementsByName('oslMinMaxToggle').length; ++idx)
  3276. {
  3277. document.getElementsByName('oslMinMaxToggle')[idx].addEventListener('click', oslUIMinMax, false);
  3278. }
  3279.  
  3280. W.map.events.register("mousemove", null, oslMouseMoveAndUp);
  3281. W.map.events.register("mouseup", null, oslMouseMoveAndUp);
  3282.  
  3283. let segLayer = oslGetSegmentLayer();
  3284. segLayer.events.register("featuresadded", null, oslNameCheckTrigger);
  3285. segLayer.events.register("featuresremoved", null, oslNameCheckTrigger);
  3286. }
  3287. function oslSetupUIPosition()
  3288. {
  3289. oslAddLog('adjusting UI position...');
  3290. document.body.style.overflow = 'hidden';
  3291.  
  3292. let vpHeight = window.innerHeight;
  3293. let vpWidth = window.innerWidth;
  3294.  
  3295. if
  3296. (
  3297. (oslUserPrefs.ui.y === null)||
  3298. (oslUserPrefs.ui.x === null)||
  3299. (oslUserPrefs.ui.y > vpHeight)||
  3300. (oslUserPrefs.ui.x > vpWidth)||
  3301. (oslUserPrefs.ui.y < 0)||
  3302. (oslUserPrefs.ui.x < 0)
  3303. )
  3304. {
  3305. oslOSLDivTop = document.getElementById('sidebar').getBoundingClientRect().top + (document.getElementById('sidebar').getBoundingClientRect().height / 2);
  3306. oslOSLDivLeft = 8;
  3307. }
  3308. else
  3309. {
  3310. oslOSLDivTop = oslUserPrefs.ui.y;
  3311. oslOSLDivLeft = oslUserPrefs.ui.x;
  3312. }
  3313.  
  3314. if(oslUserPrefs.ui.state === undefined) oslUserPrefs.ui.state = 'maximised';
  3315. oslOSLDivTopMinimised = oslOSLDivTop;
  3316. oslWindow.style.left = oslOSLDivLeft+'px';
  3317. oslWindow.style.top = oslOSLDivTop+'px';
  3318. if(oslUserPrefs.ui.state == 'maximised') oslWindowMaximise();
  3319. else oslWindowMinimise();
  3320.  
  3321. oslWindow.style.opacity = 1;
  3322. }
  3323. function oslSetLayerZIndices()
  3324. {
  3325. oslAddLog('setting layer zIndices...');
  3326. for(let i=0; i < W.map.layers.length; i++)
  3327. {
  3328. if((W.map.layers[i].uniqueName == 'satellite_imagery')||(W.map.layers[i].name == 'satellite_imagery'))
  3329. {
  3330. let satZIndex = parseInt(W.map.layers[i].div.style.zIndex);
  3331. oslBBDiv.style.zIndex = satZIndex + 1;
  3332. oslSegGeoDiv.style.zIndex = satZIndex + 2;
  3333. oslNamesDiv.style.zIndex = satZIndex + 1000;
  3334. }
  3335. if(W.map.layers[i].name == 'Spotlight') oslOSLMaskLayer = W.map.layers[i].div;
  3336. }
  3337. }
  3338. function oslSetupAccessKey()
  3339. {
  3340. oslAccessKeyUpdate();
  3341.  
  3342. GM_addValueChangeListener("_rwoPositionToWME", function()
  3343. {
  3344. if(arguments[3] === true)
  3345. {
  3346. oslAddLog('WME reposition request from one.network...');
  3347. let posBits = arguments[2].split(',');
  3348. let tPos = {lon:parseFloat(posBits[3]), lat:parseFloat(posBits[2])};
  3349. oslSDK.Map.setMapCenter({lonLat: tPos});
  3350. let tZoom = parseInt(posBits[4]);
  3351. if(tZoom < 12) tZoom = 12;
  3352. if(tZoom > 22) tZoom = 22;
  3353. oslSDK.Map.setZoomLevel({zoomLevel:tZoom});
  3354. }
  3355. });
  3356.  
  3357. window.setInterval(oslAccessKeyUpdate,5000);
  3358. }
  3359. function oslLoadOrInitPrefs()
  3360. {
  3361. // See if this system already has something in localStorage
  3362. let userPrefs = localStorage.oslUserPrefs;
  3363. if(userPrefs != undefined)
  3364. {
  3365. oslUserPrefs = JSON.parse(userPrefs);
  3366. }
  3367.  
  3368. // Fill out any undefined parts of the prefs object with default values - this works both to initialise
  3369. // the object if nothing at all is stored in localStorage, and can also be used to initialise any newer
  3370. // parameters that might get added later on which aren't yet defined in the localStorage copy
  3371. if(oslUserPrefs.minmax == undefined) oslUserPrefs.minmax = [];
  3372. if(oslUserPrefs.openRoads == undefined) oslUserPrefs.openRoads = [];
  3373. if(oslUserPrefs.ui == undefined) oslUserPrefs.ui = {};
  3374. if(oslUserPrefs.ui.x == undefined) oslUserPrefs.ui.x = null;
  3375. if(oslUserPrefs.ui.y == undefined) oslUserPrefs.ui.y = null;
  3376. if(oslUserPrefs.ui.state == undefined) oslUserPrefs.ui.state = null;
  3377.  
  3378. // Remove any old reference to oslWindow from minmax, as this can prevent the OS Open Names section
  3379. // being correctly displayed until the user minimises and maximises the entire script window...
  3380. for(let i = 0; i < oslUserPrefs.minmax.length; ++i)
  3381. {
  3382. if(oslUserPrefs.minmax[i][0] === "oslWindow")
  3383. {
  3384. oslUserPrefs.minmax.splice(i,1);
  3385. break;
  3386. }
  3387. }
  3388.  
  3389. // Import any older preferences set on this system, then remove from localStorage - this should only ever
  3390. // occur the first time this version of the script is run after updating an existing setup, so there
  3391. // wouldn't be anything already stored in these three properties that would get wiped out by the import.
  3392. if(localStorage.oslOSLDivState != undefined)
  3393. {
  3394. oslUserPrefs.ui.state = localStorage.oslOSLDivState;
  3395. localStorage.removeItem("oslOSLDivState");
  3396. }
  3397. if(localStorage.oslOSLDivLeft != undefined)
  3398. {
  3399. oslUserPrefs.ui.x = localStorage.oslOSLDivLeft;
  3400. localStorage.removeItem("oslOSLDivLeft");
  3401. }
  3402. if(localStorage.oslOSLDivTop != undefined)
  3403. {
  3404. oslUserPrefs.ui.y = localStorage.oslOSLDivTop;
  3405. localStorage.removeItem("oslOSLDivTop");
  3406. }
  3407. }
  3408. function oslApplyPrefs()
  3409. {
  3410. // subDiv states
  3411. for(let i = 0; i < oslUserPrefs.minmax.length; ++i)
  3412. {
  3413. oslSubDivSetState(oslUserPrefs.minmax[i][0], oslUserPrefs.minmax[i][1]);
  3414. }
  3415.  
  3416. // Open Roads options
  3417. for(let i = 0; i < oslUserPrefs.openRoads.length; ++i)
  3418. {
  3419. let cbID = oslUserPrefs.openRoads[i][0];
  3420. if(document.getElementById(cbID) != undefined)
  3421. {
  3422. document.getElementById(cbID).checked = oslUserPrefs.openRoads[i][1];
  3423. }
  3424. }
  3425. // Force-disable Open Roads display on startup to avoid problems if the current WME viewport is
  3426. // showing a more densely mapped area than before...
  3427. document.getElementById('_cbOpenRoadsEnabled').checked = false;
  3428. // Similarly, force-disable Gazetteer Tags display - this isn't quite as resource-sapping as
  3429. // the Open Roads display could be, but it's still preferable to leave it off until the user is
  3430. // ready to show the tags during this session
  3431. document.getElementById('_cbGazTagsEnabled').checked = false;
  3432. }
  3433. function oslWriteUserPrefs()
  3434. {
  3435. localStorage.oslUserPrefs = JSON.stringify(oslUserPrefs);
  3436. }
  3437. function oslUpdateMinMax(key, value)
  3438. {
  3439. let found = false;
  3440. for(let i = 0; i < oslUserPrefs.minmax.length; ++i)
  3441. {
  3442. if(oslUserPrefs.minmax[i][0] == key)
  3443. {
  3444. oslUserPrefs.minmax[i][1] = value;
  3445. found = true;
  3446. break;
  3447. }
  3448. }
  3449. if(found == false)
  3450. {
  3451. oslUserPrefs.minmax.push([key, value]);
  3452. }
  3453. oslWriteUserPrefs();
  3454. }
  3455. function oslUpdateHighlightCBs(key)
  3456. {
  3457. let found = false;
  3458. let value = document.getElementById(key).checked;
  3459. for(let i = 0; i < oslUserPrefs.openRoads.length; ++i)
  3460. {
  3461. if(oslUserPrefs.openRoads[i][0] == key)
  3462. {
  3463. oslUserPrefs.openRoads[i][1] = value;
  3464. found = true;
  3465. break;
  3466. }
  3467. }
  3468. if(found == false)
  3469. {
  3470. oslUserPrefs.openRoads.push([key, value]);
  3471. }
  3472. oslWriteUserPrefs();
  3473. }
  3474. function oslFinaliseSetup()
  3475. {
  3476. oslSDK = getWmeSdk({scriptId:"wmeod",scriptName:"WME Open Data"});
  3477.  
  3478. let mvp = oslSDK.Map.getMapViewportElement();
  3479. let cID = mvp.id.replace("ViewPort", "Container");
  3480. oslWazeMapElement = mvp.querySelector('#'+cID);
  3481.  
  3482. oslEnableAdvancedOptions();
  3483. oslLoadOrInitPrefs();
  3484.  
  3485. oslSetupBBDiv();
  3486. oslSetupORDiv();
  3487. oslSetupNamesDiv();
  3488. oslSetupUI();
  3489. oslSetupEventListeners();
  3490. oslSetupUIPosition();
  3491. oslSetLayerZIndices();
  3492. oslSetupAccessKey();
  3493.  
  3494. oslApplyPrefs();
  3495.  
  3496. oslOffsetToolbar = document.getElementById('map').contains(document.getElementById('toolbar'));
  3497. window.setInterval(oslTenthSecondTick,100);
  3498. oslDoneOnload = true;
  3499. }
  3500. function oslWaitForW()
  3501. {
  3502. if(document.getElementsByClassName("sandbox").length > 0)
  3503. {
  3504. oslAddLog('WME practice mode detected, script is disabled...');
  3505. return;
  3506. }
  3507. if(document.location.href.indexOf('user/') !== -1)
  3508. {
  3509. oslAddLog('User profile page detected, script is disabled...');
  3510. return;
  3511. }
  3512.  
  3513. if(typeof W === "undefined")
  3514. {
  3515. window.setTimeout(oslWaitForW, 1000);
  3516. return;
  3517. }
  3518.  
  3519. if (W.userscripts?.state?.isReady)
  3520. {
  3521. oslFinaliseSetup();
  3522. }
  3523. else
  3524. {
  3525. document.addEventListener("wme-ready", oslFinaliseSetup, {once: true});
  3526. }
  3527. }
  3528. function oslAccessKeyUpdate()
  3529. {
  3530. GM_setValue('_rwoAccessKey',Date.now());
  3531. }
  3532. function oslSNATest()
  3533. {
  3534. // Checks the street name abbreviation processing code in oslWazeifyStreetName(),
  3535. // to make sure it still correctly handles names of a form that are particularly
  3536. // problematic, whilst also still correctly handling regular forms...
  3537. const testNames =
  3538. [
  3539. // Standard forms for all the defined abbreviations
  3540. ['Electric Avenue', 'Electric Ave'],
  3541. ['Hollywood Boulevard', 'Hollywood Blvd'],
  3542. ['Ealing Broadway', 'Ealing Bdwy'],
  3543. ['Hillingdon Circus', 'Hillingdon Cir'],
  3544. ['Glenn Close', 'Glenn Cl'],
  3545. ['Crown Court', 'Crown Ct'],
  3546. ['Red Crescent', 'Red Cr'],
  3547. ['Test Drive', 'Test Dr'],
  3548. ['Graham Garden', 'Graham Gdn'],
  3549. ['Kensington Gardens', 'Kensington Gdns'],
  3550. ['Theresa Green', 'Theresa Gn'],
  3551. ['Byker Grove', 'Byker Gr'],
  3552. ['Lois Lane', 'Lois Ln'],
  3553. ['My Cat Mews', 'My Cat Mews'],
  3554. ['Tripod Mount', 'Tripod Mt'],
  3555. ['Last Place', 'Last Pl'],
  3556. ['Gosford Park', 'Gosford Pk'],
  3557. ['Sophy Ridge', 'Sophy Rdg'],
  3558. ['Take The High Road', 'Take The High Rd'],
  3559. ['Four Square', 'Four Sq'],
  3560. ['Baker Street', 'Baker St'],
  3561. ['No Idea Terrace', 'No Idea Ter'],
  3562. ['Happy Valley', 'Happy Val'],
  3563. ['Hyperspace By-pass', 'Hyperspace Bypass'],
  3564. ['This Is The Way', 'This Is The Way'],
  3565. ['Damon Hill', 'Damon Hill'],
  3566.  
  3567. // Forms including cardinals as prefixes
  3568. ["North Street", "North St"],
  3569. ["South Street", "South St"],
  3570. ["East Street", "East St"],
  3571. ["West Street", "West St"],
  3572. ["North Town Street", "North Town St"],
  3573. ["South Town Street", "South Town St"],
  3574. ["East Town Street", "East Town St"],
  3575. ["West Town Street", "West Town St"],
  3576. ["West View Lane", "West View Ln"],
  3577.  
  3578. // Forms including cardinals as suffixes
  3579. ["Aston Boulevard West", "Aston Blvd W"],
  3580. ["Breakspear Road North", "Breakspear Rd N"],
  3581. ["Breakspear Road South", "Breakspear Rd S"],
  3582. ["Breakspear Road East", "Breakspear Rd E"],
  3583. ["Breakspear Road West", "Breakspear Rd W"],
  3584.  
  3585. // Forms including non-cardinal suffixes
  3586. ["High Road Ickenham", "High Rd Ickenham"],
  3587.  
  3588. // Forms including "The"
  3589. ["Orchard On The Green", "Orchard On The Green"],
  3590. ["The Orchard On The Green", "The Orchard On The Green"],
  3591. ["The Avenue", "The Avenue"],
  3592.  
  3593. // Forms including abbreviatable words elsewhere in the name
  3594. ["Green Street", "Green St"],
  3595. ["Kensal Green Way", "Kensal Green Way"],
  3596. ["Green Lane Hill", "Green Lane Hill"],
  3597. ["Court Court", "Court Ct"],
  3598. ["The Street Road", "The Street Rd"],
  3599. ["Great North Road", "Great North Rd"],
  3600. ["North Park Brook Road", "North Park Brook Rd"],
  3601.  
  3602. // Forms where abbreviatable words are found at the start of longer words
  3603. ["Westway Avenue", "Westway Ave"],
  3604. ["Parkway Park", "Parkway Pk"],
  3605. ["Parkway Crescent", "Parkway Cr"],
  3606.  
  3607. // Test for correct abbreviation of Saint -> St.
  3608. ["Saint Albans Way", "St. Albans Way"],
  3609.  
  3610. // Random names taken from forum etc. discussions on this topic...
  3611. ["Bowling Green Road", "Bowling Green Rd"],
  3612. ["Crescent Road Lane", "Crescent Road Ln"],
  3613. ["Edge Lane Road", "Edge Lane Rd"],
  3614. ["North East Road", "North East Rd"]
  3615. ];
  3616.  
  3617. let nTests = testNames.length;
  3618. for(let i = 0; i < nTests; ++i)
  3619. {
  3620. let result = oslWazeifyStreetName(testNames[i][0], false);
  3621. if(result !== testNames[i][1])
  3622. {
  3623. console.log(oslWazeifyStreetName(testNames[i][0], true));
  3624. }
  3625. }
  3626. }
  3627. function oslInitialise()
  3628. {
  3629. oslAddLog('initialise()');
  3630.  
  3631. oslSNATest();
  3632.  
  3633. // inject gazetteer data
  3634. let gazscript = document.createElement("script");
  3635. gazscript.setAttribute('type','text/javascript');
  3636. gazscript.setAttribute('charset','UTF-8');
  3637. gazscript.src = oslModifySrc(oslGazetteerURL);
  3638. document.head.appendChild(gazscript);
  3639. oslMergeGazData = true;
  3640.  
  3641. // initialise persistent vars
  3642. sessionStorage.zoom = 0;
  3643. sessionStorage.lat = '';
  3644. sessionStorage.lon = '';
  3645. sessionStorage.myCity = '';
  3646. sessionStorage.prevCityName = '';
  3647. sessionStorage.cityChangeEastings = 0;
  3648. sessionStorage.cityChangeNorthings = 0;
  3649. sessionStorage.cityNameRB = 'optUseExisting';
  3650. sessionStorage.oslMousepos = '{"x":0,"y":0}';
  3651.  
  3652. oslWaitForW();
  3653. }
  3654.  
  3655. // External site helper functions
  3656. const hlp_ONE =
  3657. {
  3658. // one.network helper functions
  3659. lat: 0,
  3660. lng: 0,
  3661. zoom: 0,
  3662. checkInterval: 10000,
  3663. addLog: function(logtext)
  3664. {
  3665. console.log('ONE: '+logtext);
  3666. },
  3667. checkDatalink: function()
  3668. {
  3669. let lastAccessKey = GM_getValue('_oneAccessKey', null);
  3670. if(lastAccessKey !== null)
  3671. {
  3672. let keyBits = lastAccessKey.split(',');
  3673. if(keyBits.length === 4)
  3674. {
  3675. let keyTS = parseInt(keyBits[0]);
  3676. if((Date.now() - keyTS) <= hlp_ONE.checkInterval)
  3677. {
  3678. // the access key was written recently enough to imply an active WME tab, so reposition if required based on the lat/lon/zoom values in the key
  3679. let lat = parseFloat(keyBits[1]);
  3680. let lng = parseFloat(keyBits[2]);
  3681. let zoom = parseFloat(keyBits[3]);
  3682. if((lat != hlp_ONE.lat) || (lng != hlp_ONE.lng) || (zoom != hlp_ONE.zoom))
  3683. {
  3684. hlp_ONE.lat = lat;
  3685. hlp_ONE.lng = lng;
  3686. hlp_ONE.zoom = zoom;
  3687. hlp_ONE.relocate();
  3688. }
  3689. hlp_ONE.checkInterval = 1000; // reduce the interval after the first check, to minimise the period where manually panning/zooming is overridden
  3690. }
  3691. }
  3692. }
  3693. setTimeout(hlp_ONE.checkDatalink, 50);
  3694. },
  3695. relocate: function()
  3696. {
  3697. Elgin.map.setCenter([hlp_ONE.lng, hlp_ONE.lat]);
  3698. Elgin.map.setZoom(hlp_ONE.zoom - 1);
  3699. },
  3700. clickToWME: function()
  3701. {
  3702. let elginLat = Elgin.map.getCenter().lat;
  3703. let elginLon = Elgin.map.getCenter().lng;
  3704. let elginZoom = Elgin.map.getZoom();
  3705. if(elginZoom < 12) elginZoom = 12;
  3706. if(elginZoom > 22) elginZoom = 22;
  3707. elginZoom += 1;
  3708.  
  3709. // check for an active WME tab...
  3710. let tabFound = false;
  3711. let lastAccessKey = GM_getValue('_rwoAccessKey', null);
  3712. if(lastAccessKey !== null)
  3713. {
  3714. lastAccessKey = parseInt(lastAccessKey);
  3715. if((Date.now() - lastAccessKey) <= 10000)
  3716. {
  3717. // the access key was written within the last 10s which implies an active tab, so send it the repositioning data
  3718. let toWrite = Date.now()+','+lastAccessKey+','+elginLat+','+elginLon+','+elginZoom;
  3719. GM_setValue('_rwoPositionToWME',toWrite);
  3720. tabFound = true;
  3721. }
  3722. }
  3723.  
  3724. if(tabFound === false)
  3725. {
  3726. // couldn't find a WME tab to reposition, so open a new one...
  3727. let wmeURL = 'https://www.waze.com/editor?';
  3728. wmeURL += 'lon='+elginLon;
  3729. wmeURL += '&lat='+elginLat;
  3730. wmeURL += '&zoomLevel='+elginZoom;
  3731. window.open(wmeURL);
  3732. }
  3733. },
  3734. init: function()
  3735. {
  3736. hlp_ONE.addLog('initialise()');
  3737. hlp_ONE.addLog('waiting for map objects...');
  3738. let waitSomeMore = false;
  3739.  
  3740. try
  3741. {
  3742. if(Elgin === undefined)
  3743. {
  3744. waitSomeMore = true;
  3745. }
  3746. else if (Elgin.map === undefined)
  3747. {
  3748. waitSomeMore = true;
  3749. }
  3750. }
  3751. catch
  3752. {
  3753. waitSomeMore = true;
  3754. }
  3755.  
  3756. if(document.getElementById('map-canvas') === null)
  3757. {
  3758. waitSomeMore = true;
  3759. }
  3760.  
  3761. if(waitSomeMore)
  3762. {
  3763. window.setTimeout(hlp_ONE.init,500);
  3764. return;
  3765. }
  3766.  
  3767. hlp_ONE.addLog('all required objects found...');
  3768.  
  3769. // Add "open in WME" button...
  3770. let tBtnDiv = document.createElement('div');
  3771. tBtnDiv.id = 'rwoWMELink';
  3772. let tHTML = '<div class="gmnoprint" draggable="false" controlwidth="40" controlheight="40" style="margin: 10px; user-select: none; position: absolute; bottom: 200px; right: 40px;">';
  3773. tHTML += '<div class="gmnoprint" controlwidth="40" controlheight="40" style="position: absolute; left: 0px; top: 0px;">';
  3774. 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;">';
  3775. 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>';
  3776. tHTML += '</div></div></div>';
  3777. tBtnDiv.innerHTML = tHTML;
  3778. document.getElementById('map-canvas').firstChild.appendChild(tBtnDiv);
  3779.  
  3780. document.getElementById('rwoWMELink').addEventListener('click', hlp_ONE.clickToWME, false);
  3781. setTimeout(hlp_ONE.checkDatalink, 500);
  3782. }
  3783. };
  3784.  
  3785. const hlp_LRR =
  3786. {
  3787. // London Roadworks Register helper functions
  3788. lat: 0,
  3789. lng: 0,
  3790. zoom: 0,
  3791. checkInterval: 10000,
  3792. wmePresent: false,
  3793. addLog: function(logtext)
  3794. {
  3795. console.log('LRR: '+logtext);
  3796. },
  3797. checkDatalink: function()
  3798. {
  3799. let lastAccessKey = GM_getValue('_lrrAccessKey', null);
  3800. if(lastAccessKey !== null)
  3801. {
  3802. let keyBits = lastAccessKey.split(',');
  3803. if(keyBits.length === 4)
  3804. {
  3805. let keyTS = parseInt(keyBits[0]);
  3806. if((Date.now() - keyTS) <= hlp_LRR.checkInterval)
  3807. {
  3808. if(hlp_LRR.wmePresent === false)
  3809. {
  3810. hlp_LRR.wmePresent = true;
  3811. document.querySelector('#messageContainer').style.display = "none";
  3812. }
  3813. // the access key was written recently enough to imply an active WME tab, so reposition if required based on the lat/lon/zoom values in the key
  3814. let lat = parseFloat(keyBits[1]);
  3815. let lng = parseFloat(keyBits[2]);
  3816. let zoom = parseFloat(keyBits[3]);
  3817. if((lat != hlp_LRR.lat) || (lng != hlp_LRR.lng) || (zoom != hlp_LRR.zoom))
  3818. {
  3819. hlp_LRR.lat = lat;
  3820. hlp_LRR.lng = lng;
  3821. hlp_LRR.zoom = zoom;
  3822. hlp_LRR.relocate();
  3823. }
  3824. hlp_LRR.checkInterval = 1000; // reduce the interval after the first check, to minimise the period where manually panning/zooming is overridden
  3825. }
  3826. }
  3827. }
  3828. setTimeout(hlp_LRR.checkDatalink, 50);
  3829. },
  3830. relocate: function()
  3831. {
  3832. let waitSomeMore = false;
  3833.  
  3834. if(map === undefined)
  3835. {
  3836. waitSomeMore = true;
  3837. }
  3838. if(OpenLayers === undefined)
  3839. {
  3840. waitSomeMore = true;
  3841. }
  3842. if(waitSomeMore)
  3843. {
  3844. return;
  3845. }
  3846.  
  3847. let tz = hlp_LRR.zoom - 12;
  3848. if(tz > 5)
  3849. {
  3850. tz = 5;
  3851. }
  3852. map.panTo(new OpenLayers.LonLat(hlp_LRR.lng,hlp_LRR.lat));
  3853. map.zoomTo(tz);
  3854. },
  3855. init: function()
  3856. {
  3857. // trap the page reload that occurs when the map view is generated...
  3858. if(document.location.href.indexOf('home') == -1)
  3859. {
  3860. hlp_LRR.addLog('page reloaded during map generation...');
  3861. setTimeout(hlp_LRR.checkDatalink, 500);
  3862. return;
  3863. }
  3864.  
  3865. hlp_LRR.addLog('initialise()');
  3866. hlp_LRR.addLog('waiting for search page to load...');
  3867. var waitSomeMore = false;
  3868. if(document.getElementsByName('mapResults')[0].length < 1)
  3869. {
  3870. waitSomeMore = true;
  3871. }
  3872. if(document.getElementsByName('postCode')[0].length < 1)
  3873. {
  3874. waitSomeMore = true;
  3875. }
  3876.  
  3877. if(waitSomeMore === true)
  3878. {
  3879. window.setTimeout(hlp_LRR.init,500);
  3880. return;
  3881. }
  3882.  
  3883. hlp_LRR.addLog('accessing map...');
  3884. // first set a "safe" postcode as the search criteria - whilst asking for the map view to be generated without any
  3885. // search terms usually works OK, from time to time the site throws a wobbler and refuses to do anything without
  3886. // having the search narrowed down a bit...
  3887. document.getElementsByName('postCode')[0].value="EC1A 1AA";
  3888. document.getElementsByName('mapResults')[0].click();
  3889.  
  3890. setTimeout(hlp_LRR.checkDatalink, 500);
  3891. }
  3892. };
  3893.  
  3894. const hlp_ODM =
  3895. {
  3896. // OS OpenData map helper functions
  3897. lat: 0,
  3898. lng: 0,
  3899. zoom: 0,
  3900. checkInterval: 10000,
  3901. wmePresent: false,
  3902. lastAccessKey: null,
  3903. addLog: function(logtext)
  3904. {
  3905. console.log('ODM: '+logtext);
  3906. },
  3907. checkDatalink: function()
  3908. {
  3909. let thisAccessKey = GM_getValue('_osodAccessKey', null);
  3910. if((thisAccessKey !== null) && (thisAccessKey !== hlp_ODM.lastAccessKey))
  3911. {
  3912. hlp_ODM.lastAccessKey = thisAccessKey;
  3913. let keyBits = thisAccessKey.split(',');
  3914. if(keyBits.length === 4)
  3915. {
  3916. let keyTS = parseInt(keyBits[0]);
  3917. let keyInterval = Date.now() - keyTS;
  3918. if(keyInterval <= hlp_ODM.checkInterval)
  3919. {
  3920. if(hlp_ODM.wmePresent === false)
  3921. {
  3922. hlp_ODM.addLog("WME detected");
  3923. hlp_ODM.wmePresent = true;
  3924. hlp_ODM.fakeOnload();
  3925. }
  3926. // the access key was written recently enough to imply an active WME tab, so reposition if required based on the lat/lon/zoom values in the key
  3927. let lat = parseFloat(keyBits[1]);
  3928. let lng = parseFloat(keyBits[2]);
  3929. let zoom = parseFloat(keyBits[3]);
  3930. if((lat != hlp_ODM.lat) || (lng != hlp_ODM.lng) || (zoom != hlp_ODM.zoom))
  3931. {
  3932. hlp_ODM.lat = lat;
  3933. hlp_ODM.lng = lng;
  3934. hlp_ODM.zoom = zoom;
  3935. hlp_ODM.relocate();
  3936. }
  3937. hlp_ODM.checkInterval = 1000; // reduce the interval after the first check, to minimise the period where manually panning/zooming is overridden
  3938. }
  3939. }
  3940. }
  3941. setTimeout(hlp_ODM.checkDatalink, 50);
  3942. },
  3943. hideElement: function(elm)
  3944. {
  3945. let i = document.querySelectorAll(elm).length;
  3946. while(i > 0)
  3947. {
  3948. document.querySelectorAll(elm)[--i].style.display="none";
  3949. }
  3950. },
  3951. fakeOnload: function()
  3952. {
  3953. // maximise the map view
  3954. hlp_ODM.hideElement('.govuk-grid-row');
  3955. hlp_ODM.hideElement('.govuk-phase-banner');
  3956. hlp_ODM.hideElement('.before-content-links');
  3957. hlp_ODM.hideElement('.govuk-heading-l');
  3958. hlp_ODM.hideElement('.govuk-header ');
  3959. hlp_ODM.hideElement('.govuk-footer');
  3960. hlp_ODM.hideElement('.govuk-list');
  3961. hlp_ODM.hideElement('.govuk-body');
  3962. hlp_ODM.hideElement('.govuk-heading-m');
  3963. hlp_ODM.hideElement('#gcd-container');
  3964.  
  3965. document.querySelector('.govuk-main-wrapper ').style.padding="0px 0px 0px 0px";
  3966.  
  3967. let i = document.querySelectorAll('.govuk-width-container ').length;
  3968. while(i > 0)
  3969. {
  3970. document.querySelectorAll('.govuk-width-container ')[--i].style.maxWidth="100%";
  3971. }
  3972.  
  3973. document.querySelector('#map').style.position="absolute";
  3974. document.querySelector('#map').style.height="100%";
  3975. },
  3976. relocate: function()
  3977. {
  3978. map.getView().setCenter([hlp_ODM.lng, hlp_ODM.lat]);
  3979. map.getView().setZoom(hlp_ODM.zoom);
  3980. },
  3981. init: function()
  3982. {
  3983. hlp_ODM.addLog('initialise()');
  3984. if(map === undefined) window.setTimeout(hlp_ODM.init,100);
  3985. else window.setTimeout(hlp_ODM.checkDatalink, 500);
  3986. }
  3987. };
  3988.  
  3989. oslBootstrap();