xteejx, remind me never to try reading the wiki when tired - I could have sworn that page was nowhere to be found last night...
Anyhoo, another incremental update brings the following changes:
- results are now sorted by distance from the mouse click gridref to the segment centrepoint gridref
- a city name field has been added to the search results area, allowing this to be set manually once and then auto-populated into every new edit thereafter until you decide to change it
- the results area now has a distinctive background colour, to differentiate it from the standard WME editing controls
- Clicking the "Copy to Properties" button now also causes the "Edit Address" link to be auto-clicked, which is one less click to be performed manually...
Sorting by distance isn't quite as successful as I'd originally thought it would be - it certainly makes a big difference in many areas, but doesn't cope too well when highlighting part of a long road adjacent to a short road. With the benefit of hindsight, this was kinda obvious... I've got some other ideas on how to improve this, but realistically it'll never be 100% accurate given the overlapping nature of the bounding box data. Figuring out a way to visualise the bounding boxes may well have to be the next challenge.
- Code: Select all
// ==UserScript==
// @name WME to OS link
// @namespace http://greasemonkey.chizzum.com
// @description Adds link to WME to open up various mapping sites at the same map location
// @include https://world.waze.com/editor/*
// @include https://world.waze.com/map-editor/*
// @include https://descartesw.waze.com/beta/*
// @require http://greasemonkey.chizzum.com/osl_10km.js
// @version 0.8.2
// ==/UserScript==
// Contains Ordnance Survey data Crown copyright and database right 2012
// Contents of the osl_10km.js file are derived under the Open Government Licence from the OS Locator dataset
//-----------------------------------------------------------------------------------------------------------------------------------------
// all code between here and the next ------------- marker line is a stripped down version of the original from Paul Dixon
//
// * GeoTools javascript coordinate transformations
// * http://files.dixo.net/geotools.html
// *
// * This file copyright (c)2005 Paul Dixon (paul@elphin.com)
// *
// * This program is free software; you can redistribute it and/or
// * modify it under the terms of the GNU General Public License
// * as published by the Free Software Foundation; either version 2
// * of the License, or (at your option) any later version.
// *
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// * GNU General Public License for more details.
// *
// * You should have received a copy of the GNU General Public License
// * along with this program; if not, write to the Free Software
// * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// *
// * ---------------------------------------------------------------------------
// *
// * Credits
// *
// * The algorithm used by the script for WGS84-OSGB36 conversions is derived
// * from an OSGB spreadsheet (www.gps.gov.uk) with permission. This has been
// * adapted into Perl by Ian Harris, and into PHP by Barry Hunter. Conversion
// * accuracy is in the order of 7m for 90% of Great Britain, and should be
// * be similar to the conversion made by a typical GPSr
// *
// * See accompanying documentation for more information
// * http://files.dixo.net/geotools.html
var northings;
var eastings;
var latitude;
var longitude;
var helmX, helmY, helmZ;
var Pi = 3.14159265358979;
function wgs_to_osgb()
{
LatLon_to_HelmXYZ();
var latitude2 = XYZ_to_Lat();
var longitude2 = Math.atan2(helmY , helmX) * (180 / Pi);
LatLon_to_OSGrid(latitude2,longitude2);
}
function LatLon_to_HelmXYZ()
{
var a = 6378137.0;
var b = 6356752.313;
var DX = -446.448;
var DY = 125.157;
var DZ = -542.060;
var rotX = -0.1502;
var rotY = -0.2470;
var rotZ = -0.8421;
var sfactor = 20.4894 * 0.000001;
// perform initial lat-lon to cartesian coordinate translation
var RadPHI = latitude * (Pi / 180);
var RadLAM = longitude * (Pi / 180);
var e2 = (Math.pow(a,2) - Math.pow(b,2)) / Math.pow(a,2);
var V = a / (Math.sqrt(1 - (e2 * ( Math.pow(Math.sin(RadPHI),2)))));
var cartX = V * (Math.cos(RadPHI)) * (Math.cos(RadLAM));
var cartY = V * (Math.cos(RadPHI)) * (Math.sin(RadLAM));
var cartZ = (V * (1 - e2)) * (Math.sin(RadPHI));
// Compute Helmert transformed coordinates
var RadX_Rot = (rotX / 3600) * (Pi / 180);
var RadY_Rot = (rotY / 3600) * (Pi / 180);
var RadZ_Rot = (rotZ / 3600) * (Pi / 180);
helmX = (cartX + (cartX * sfactor) - (cartY * RadZ_Rot) + (cartZ * RadY_Rot) + DX);
helmY = (cartX * RadZ_Rot) + cartY + (cartY * sfactor) - (cartZ * RadX_Rot) + DY;
helmZ = (-1 * cartX * RadY_Rot) + (cartY * RadX_Rot) + cartZ + (cartZ * sfactor) + DZ;
}
function XYZ_to_Lat()
{
var a = 6377563.396;
var b = 6356256.910;
var RootXYSqr = Math.sqrt(Math.pow(helmX,2) + Math.pow(helmY,2));
var e2 = (Math.pow(a,2) - Math.pow(b,2)) / Math.pow(a,2);
var PHI1 = Math.atan2(helmZ , (RootXYSqr * (1 - e2)) );
var PHI = Iterate_XYZ_to_Lat(a, e2, PHI1, helmZ, RootXYSqr);
return PHI * (180 / Pi);
}
function Iterate_XYZ_to_Lat(a, e2, PHI1, Z, RootXYSqr)
{
var V = a / (Math.sqrt(1 - (e2 * Math.pow(Math.sin(PHI1),2))));
var PHI2 = Math.atan2((Z + (e2 * V * (Math.sin(PHI1)))) , RootXYSqr);
while (Math.abs(PHI1 - PHI2) > 0.000000001)
{
PHI1 = PHI2;
V = a / (Math.sqrt(1 - (e2 * Math.pow(Math.sin(PHI1),2))));
PHI2 = Math.atan2((Z + (e2 * V * (Math.sin(PHI1)))) , RootXYSqr);
}
return PHI2;
}
function Marc(bf0, n, PHI0, PHI)
{
return bf0 * (((1 + n + ((5 / 4) * Math.pow(n,2)) + ((5 / 4) * Math.pow(n,3))) * (PHI - PHI0)) - (((3 * n) + (3 * Math.pow(n,2)) +
((21 / 8) * Math.pow(n,3))) * (Math.sin(PHI - PHI0)) * (Math.cos(PHI + PHI0))) + ((((15 / 8) * Math.pow(n,2)) + ((15 / 8) *
Math.pow(n,3))) * (Math.sin(2 * (PHI - PHI0))) * (Math.cos(2 * (PHI + PHI0)))) - (((35 / 24) * Math.pow(n,3)) *
(Math.sin(3 * (PHI - PHI0))) * (Math.cos(3 * (PHI + PHI0)))));
}
function LatLon_to_OSGrid(PHI, LAM)
{
var a = 6377563.396;
var b = 6356256.910;
var e0 = 400000;
var n0 = -100000;
var f0 = 0.999601272;
var PHI0 = 49.00000;
var LAM0 = -2.00000;
var RadPHI = PHI * (Pi / 180);
var RadLAM = LAM * (Pi / 180);
var RadPHI0 = PHI0 * (Pi / 180);
var RadLAM0 = LAM0 * (Pi / 180);
var af0 = a * f0;
var bf0 = b * f0;
var e2 = (Math.pow(af0,2) - Math.pow(bf0,2)) / Math.pow(af0,2);
var n = (af0 - bf0) / (af0 + bf0);
var nu = af0 / (Math.sqrt(1 - (e2 * Math.pow(Math.sin(RadPHI),2) )));
var rho = (nu * (1 - e2)) / (1 - (e2 * Math.pow(Math.sin(RadPHI),2) ));
var eta2 = (nu / rho) - 1;
var p = RadLAM - RadLAM0;
var M = Marc(bf0, n, RadPHI0, RadPHI);
var I = M + n0;
var II = (nu / 2) * (Math.sin(RadPHI)) * (Math.cos(RadPHI));
var III = ((nu / 24) * (Math.sin(RadPHI)) * (Math.pow(Math.cos(RadPHI),3))) * (5 - (Math.pow(Math.tan(RadPHI),2)) + (9 * eta2));
var IIIA = ((nu / 720) * (Math.sin(RadPHI)) * (Math.pow(Math.cos(RadPHI),5))) * (61 - (58 * (Math.pow(Math.tan(RadPHI),2))) + (Math.pow(Math.tan(RadPHI),4)));
var IV = nu * (Math.cos(RadPHI));
var V = (nu / 6) * ( Math.pow(Math.cos(RadPHI),3)) * ((nu / rho) - (Math.pow(Math.tan(RadPHI),2)));
var VI = (nu / 120) * (Math.pow(Math.cos(RadPHI),5)) * (5 - (18 * (Math.pow(Math.tan(RadPHI),2))) + (Math.pow(Math.tan(RadPHI),4)) + (14 * eta2) - (58 * (Math.pow(Math.tan(RadPHI),2)) * eta2));
eastings = Math.round(e0 + (p * IV) + (Math.pow(p,3) * V) + (Math.pow(p,5) * VI));
northings = Math.round(I + (Math.pow(p,2) * II) + (Math.pow(p,4) * III) + (Math.pow(p,6) * IIIA));
}
//-----------------------------------------------------------------------------------------------------------------------------------------
function caseCorrect(wrongcase)
{
var loop;
var correctedCase = '';
for(loop=0;loop<wrongcase.length;loop++)
{
if((loop == 0)||(wrongcase[loop-1] == ' ')) correctedCase += wrongcase[loop].toUpperCase();
else correctedCase += wrongcase[loop].toLowerCase();
}
return correctedCase;
}
function wazeifyStreetName(oslName)
{
var wazeName = '';
var loop;
wazeName = caseCorrect(oslName);
var namePieces = wazeName.split(' ');
if(namePieces.length > 1)
{
var roadType;
var dirSuffix;
var thePrefix;
var namePrefix = '';
if((namePieces[namePieces.length-1] == 'North')||(namePieces[namePieces.length-1] == 'South')||(namePieces[namePieces.length-1] == 'East')||(namePieces[namePieces.length-1] == 'West'))
{
dirSuffix = namePieces[namePieces.length-1][0];
roadType = namePieces[namePieces.length-2];
if(namePieces.length > 2)
{
if(namePieces[namePieces.length-3] == 'The') thePrefix = true;
else thePrefix = false;
}
for(loop=0;loop<namePieces.length-2;loop++) namePrefix += (namePieces[loop] + ' ');
}
else
{
dirSuffix = '';
roadType = namePieces[namePieces.length-1];
if(namePieces[namePieces.length-2] == 'The') thePrefix = true;
else thePrefix = false;
for(loop=0;loop<namePieces.length-1;loop++) namePrefix += (namePieces[loop] + ' ');
}
if(thePrefix == false)
{
roadType = roadType.replace('Avenue','Ave');
roadType = roadType.replace('Boulevard','Blvd');
roadType = roadType.replace('Broadway','Bdwy');
roadType = roadType.replace('Close','Cl');
roadType = roadType.replace('Court','Ct');
roadType = roadType.replace('Crescent','Cr');
roadType = roadType.replace('Drive','Dr');
roadType = roadType.replace('Gardens','Gdns');
roadType = roadType.replace('Garden','Gdn');
roadType = roadType.replace('Green','Gn');
roadType = roadType.replace('Grove','Gr');
roadType = roadType.replace('Lane','Ln');
roadType = roadType.replace('Mount','Mt');
roadType = roadType.replace('Place','Pl');
roadType = roadType.replace('Park','Pk');
roadType = roadType.replace('Road','Rd');
roadType = roadType.replace('Square','Sq');
roadType = roadType.replace('Street','St');
roadType = roadType.replace('Terrace','Ter');
}
wazeName = namePrefix + roadType + ' ' + dirSuffix;
}
return wazeName;
}
function cpDistance(cpE, cpN, posE, posN)
{
return Math.sqrt(((posE - cpE) * (posE - cpE)) + ((posN - cpN) * (posN - cpN)));
}
function gradMinRatio(bbW, bbE, bbS, bbN, posE, posN)
{
var gradPosToSW = (posN - bbS) / (posE - bbW);
var gradPosToSE = (posN - bbS) / (posE - bbE);
var gradNEToSW = (bbN - bbS) / (bbE - bbW);
var gradNWToSE = (bbN - bbS) / (bbW - bbE);
var ratioSW = gradPosToSW / gradNEToSW;
var ratioSE = gradPosToSE / gradNWToSE;
if(ratioSW < 1) ratioSW = 1 / ratioSW;
if(ratioSE < 1) ratioSE = 1 / ratioSE;
if(ratioSW < ratioSE) return ratioSW;
else return ratioSE;
}
function oslClick()
{
var cityName = caseCorrect(document.getElementById('myCityName').value);
sessionStorage.myCity = cityName;
var oslElements = document.getElementById('oslDiv');
for(var loop=0;loop<oslElements.childNodes.length;loop++)
{
if(oslElements.childNodes[loop].nodeType == 1)
{
attr = oslElements.childNodes[loop].attributes.getNamedItem("type");
if(attr != null)
{
if(attr.value == "radio")
{
if(oslElements.childNodes[loop].checked)
{
attr = oslElements.childNodes[loop].attributes.getNamedItem("id").value;
oslID = attr.split('_');
evalstr = 'var roadData = locatorData_'+oslID[1]+'_'+oslID[2]+'['+oslID[3]+']';
eval(evalstr);
var locatorElements = roadData.split(":");
// auto-click the Edit Address link...
var editAddress = document.getElementById('editSegmentsApplyChanges');
var editButtons = editAddress.getElementsByClassName('edit-button');
editButtons[0].click();
// fill in the City field
//oslName = caseCorrect(locatorElements[9]);
//if(locatorElements[8].length > 0) oslName += ' ('+caseCorrect(locatorElements[8])+')';
var snelms = document.getElementsByName('cityName');
//snelms[0].value = oslName;
snelms[0].value = cityName;
snelms[0].disabled = false;
snelms = document.getElementsByName('emptyCity');
snelms[0].checked = false;
// and the Street field
oslName = locatorElements[1];
if((locatorElements[0].length > 0)&&(locatorElements[1].length > 0)) oslName += ' - ';
oslName += wazeifyStreetName(locatorElements[0]);
snelms = document.getElementsByName('streetName');
snelms[0].value = oslName;
snelms[0].disabled = false;
snelms = document.getElementsByName('emptyStreet');
snelms[0].checked = false;
}
}
}
}
}
}
function oslMatch(oslLink, oslDistance, oslMinGradRatio)
{
this.oslLink = oslLink;
this.oslDistance = oslDistance;
this.oslMinGradRatio = oslMinGradRatio;
}
function sortCandidates(a,b)
{
var x = a.oslDistance;
var y = b.oslDistance;
return((x<y) ? -1 : ((x>y) ? 1 : 0));
}
function toOSGrid(lat, lon, mode)
{
latitude = lat;
longitude = lon;
wgs_to_osgb();
if(mode == 1) // OS Locator lookup
{
// determine which 10km grid block contains the current WME centrepoint
var eBlock = (Math.floor(eastings/10000)) * 10000;
var nBlock = (Math.floor(northings/10000)) * 10000;
// check to see if there's a corresponding array in the osl_10km data
var evalstr = 'typeof locatorData_'+eBlock+'_'+nBlock;
if(eval(evalstr) != "undefined")
{
// yes... make a local copy to avoid having an eval() in each iteration of the loop
evalstr = 'var blockData = locatorData_'+eBlock+'_'+nBlock;
eval(evalstr);
oslDiv.innerHTML = 'OSL matches at '+eastings+','+northings+':<br>';
var candidates = new Array();
for(var loop = 0;loop < blockData.length; loop++)
{
// for each entry in the array, test the centrepoint position to see if it lies within the bounding box for that entry
// note that we allow a 10m tolerance on all sides of the box to allow for inaccuracies in the latlon->gridref conversion,
// and to increase the chance of a successful match when the road runs E-W or N-S and thus has a long but narrow bounding box
var locatorElements = blockData[loop].split(":");
var bbW = parseInt(locatorElements[4]);
var bbE = parseInt(locatorElements[5]);
var bbS = parseInt(locatorElements[6]);
var bbN = parseInt(locatorElements[7]);
if((eastings>=(bbW-10))&&(eastings<=(bbE+10))&&(northings>=(bbS-10))&&(northings<=(bbN+10)))
{
var oslLink = '<input type="radio" name="oslChoice" id="oslID_'+eBlock+'_'+nBlock+'_'+loop+'"></input>';
if(locatorElements[1].length > 0)
{
oslLink += locatorElements[1];
if(locatorElements[0].length > 0)
{
oslLink += ' - ';
}
}
oslLink += wazeifyStreetName(locatorElements[0])+'<br>';
var dist = cpDistance(parseInt(locatorElements[2]),parseInt(locatorElements[3]),eastings,northings);
var ratio = gradMinRatio(bbW, bbE, bbS, bbN, eastings, northings);
candidates[candidates.length++] = new oslMatch(oslLink,dist,ratio);
}
}
if(candidates.length > 0)
{
if(candidates.length > 1) candidates.sort(sortCandidates);
for(var loop=0;loop<candidates.length;loop++)
{
oslDiv.innerHTML += candidates[loop].oslLink;
//oslDiv.innerHTML += candidates[loop].oslLink + candidates[loop].oslDistance + ' ' + candidates[loop].oslMinGradRatio + '<br>';
}
oslDiv.innerHTML += '<br>City: <input id="myCityName" type="text" value="'+sessionStorage.myCity+'"/><br>';
oslDiv.innerHTML += '<br><input id="oslSelect" type="button" value="Copy to Properties" />';
document.getElementById('oslSelect').addEventListener("click", oslClick, true);
}
}
}
else return '?e='+eastings+'&n='+northings;
}
function processPermalink()
{
var mousepos = document.getElementById("OpenLayers.Control.MousePosition_26").innerHTML;
if(mousepos != sessionStorage.mousepos)
{
sessionStorage.mousepos = mousepos;
if(document.getElementById("editPanel").innerHTML.indexOf("editSegmentStreetDetailsForm") == -1) // segment properties panel isn't open...
{
if(document.getElementById("OpenLayers.Layer.Vector.RootContainer_221").innerHTML.indexOf('#03b9da') != -1) // ...and segment is highlighted
{
// so update the OS Locator matches
mouselatlon = mousepos.split(",");
toOSGrid(mouselatlon[1],mouselatlon[0],1);
}
}
}
// extract current lat/lon & zoom level from the permalink URL
var plsrc = document.getElementById("permalink-container").innerHTML;
var zoompos = plsrc.indexOf("?zoom=");
var latpos = plsrc.indexOf("&lat=");
var lonpos = plsrc.indexOf("&lon=");
var layerpos = plsrc.indexOf("&layers=");
// does the URL contain all three parameters?
if((zoompos != -1)&&(latpos != -1)&&(lonpos != -1)&&(layerpos != -1))
{
// yes, so extract them...
var zoom = parseInt(plsrc.substr(zoompos+6,latpos-(zoompos+6)));
// accommodate both the original permalink ordering of zoom, lat then lon, and the present
// ordering (as of early July 2012) of zoom, lon then lat...
if(latpos < lonpos)
{
var lat = plsrc.substr(latpos+9,lonpos-(latpos+9));
var lon = plsrc.substr(lonpos+9,layerpos-(lonpos+9));
}
else
{
var lat = plsrc.substr(latpos+9,layerpos-(latpos+9));
var lon = plsrc.substr(lonpos+9,latpos-(lonpos+9));
}
// compare the freshly extracted parameters against the persistent copies, and update the
// links to OSMC & OSOD only if there's a change required - the newly-inserted <a> element
// can't be clicked on until the insertion process is complete, and if we were to re-insert
// it every 250ms then it'd spend a lot of its time giving the appearance of being clickable
// but without actually doing anything...
if((zoom != sessionStorage.zoom)||(lat != sessionStorage.lat)||(lon != sessionStorage.lon))
{
// update the persistent vars with the new position
sessionStorage.zoom = zoom;
sessionStorage.lat = lat;
sessionStorage.lon = lon;
// translate the zoom level between WME and Musical Chairs - this gives a pretty close match
var mczoom = zoom + 12;
if(mczoom > 18) mczoom = 18;
// generate the Musical Chairs URL
var osmc_url = 'http://ris.dev.openstreetmap.org/oslmusicalchairs/map?zoom='+mczoom+'&lat='+lat+'&lon='+lon+'&layers=B0TT&view_mode=pseudorandom';
// translate the zoom level between WME and OpenData - the match here isn't quite so good...
var odzoom = zoom + 5;
if(odzoom < 6) odzoom = 6;
if(odzoom > 10) odzoom = 10;
// generate the OpenData URL - requires the support of os_opendata_fullheight.user.js
var osod_url = 'http://www.ordnancesurvey.co.uk/oswebsite/opendata/viewer/'+toOSGrid(lat,lon,0)+'&z='+odzoom;
// generate the Cartouche URL
var cartouche_url = 'http://world.waze.com/cartouche_old/?zoom='+zoom+'&lon='+lon+'&lat='+lat;
// translate the zoom level between WME and live map. The only correlation is (WME)=[Live] (0 or 1)=[7], (2 or 3)=[8], (4 or more)=[9]
var livemap_zoom = Math.floor(zoom/2)+7;
if (livemap_zoom > 9 ) livemap_zoom = 9;
var livemap_url = 'https://world.waze.com/livemap/?zoom='+livemap_zoom+'&lat='+lat+'&lon='+lon+'&layers=BTTTT';
// Modify existing livemap link to reference current position in WME
document.getElementById("livemap").href = livemap_url;
document.getElementById("livemap").target = '_blank';
// update the link URLs
document.getElementById("_linkOSOD").href = osod_url;
document.getElementById("_linkOSMC").href = osmc_url;
document.getElementById("_linkCartouche").href = cartouche_url;
// refresh any of the site tabs/windows we've checked for auto-tracking
if(document.getElementById('_cbAutoTrackOSOD').checked == 1) window.open(osod_url,'_osopendata');
if(document.getElementById('_cbAutoTrackOSMC').checked == 1) window.open(osmc_url,'_osmusicalchairs');
if(document.getElementById('_cbAutoTrackCartouche').checked == 1) window.open(cartouche_url,'_cartouche');
}
}
}
// initialise persistent vars
sessionStorage.zoom = 0;
sessionStorage.lat = '';
sessionStorage.lon = '';
// get the colour attribute for the permalink, so that our new links are visually consistent
var pl_colour = document.defaultView.getComputedStyle(document.getElementById("map-footer"),"").getPropertyValue("color");
// create a new child div in the existing map footer div, so we don't have to borrow the bing attribution one any more...
var mlcDiv = document.createElement('div');
mlcDiv.setAttribute('id','MapLinkControls');
// add the anchors and auto-track checkboxes for OS OpenData, Musical Chairs and Cartouche. Note that the urls are blank at this stage,
// they'll be filled in as soon as we've done our first processPermalink() call
mlcDiv.innerHTML = '<a href="" id="_linkOSOD" target=_osopendata style="color:' + pl_colour +'">OS OpenData</a> <input type="checkbox" id="_cbAutoTrackOSOD"></input> | ';
mlcDiv.innerHTML += '<a href="" id="_linkOSMC" target=_osmusicalchairs style="color:' + pl_colour +'">OS Musical Chairs</a> <input type="checkbox" id="_cbAutoTrackOSMC"></input> | ';
mlcDiv.innerHTML += '<a href="" id="_linkCartouche" target=_cartouche style="color:' + pl_colour +'">Cartouche</a> <input type="checkbox" id="_cbAutoTrackCartouche"></input>';
mlcDiv.innerHTML += '<br>(Checkboxes enable auto-tracking)';
document.getElementById('map-footer').appendChild(mlcDiv);
// add a new div to the edit properties panel, to hold the OS Locator results
var oslDiv = document.createElement('div');
oslDiv.id = "oslDiv";
oslDiv.style.backgroundColor = '#DDFFDD';
document.getElementById('editPanel').appendChild(oslDiv);
// set up a check for new map co-ords every 250ms
setInterval(processPermalink,250);