1// vim: tabstop=4:softtabstop=4:shiftwidth=4:noexpandtab 2 3// GLOBAL VARIABLES 4var overpassapi = "http://overpass-api.de/api/interpreter?data="; 5var datasets = { 6 "Belgium": { 7 "http://raw.githubusercontent.com/sanderd17/sanderd17.github.io/master/haltes_de_lijn_dataset.json": "Haltes De Lijn", 8 }, 9}; 10var datasetSettings = {}; 11var tiledData = {}; 12var appSettings = {}; 13var queryStatus = {"busy": false, "waiting": false}; 14 15function loadDatasets() 16{ 17 for (var country in datasets) 18 { 19 for (var dataset in datasets[country]) 20 { 21 htmlHelper.addDataset(country, datasets[country][dataset], dataset); 22 var req = new XMLHttpRequest(); 23 req.overrideMimeType("application/json"); 24 req.onreadystatechange = function() 25 { 26 if (req.readyState != 4) 27 return; 28 var settings = JSON.parse(req.responseText); 29 settings.layer = new L.LayerGroup(); 30 datasetSettings[dataset] = settings; 31 var el = document.getElementById(dataset + "Dataset"); 32 if (el.checked) 33 toggleDataset(dataset, el); 34 } 35 req.open("GET", dataset, true); 36 req.send(null); 37 } 38 } 39} 40 41function toggleDataset(dataset, element) 42{ 43 if (!datasetSettings[dataset] || !datasetSettings[dataset].layer) 44 return; // wait until the page is loaded 45 46 if (element.checked) 47 mapObj.addLayer(datasetSettings[dataset].layer); 48 else 49 mapObj.removeLayer(datasetSettings[dataset].layer); 50 loadData(); 51} 52 53function changeSetting(element) 54{ 55 var id = element.id; 56 if (id.slice(-7) != "Setting") 57 return; 58 id = id.slice(0, -7); 59 appSettings[id] = element.checked; 60 if (id == "hideCompletePOI") // re-render points 61 for (var dataset in tiledData) 62 for (var tileName in tiledData[dataset]) 63 if (tiledData[dataset][tileName].data) 64 for (var p = 0; p < tiledData[dataset][tileName].data.length; p++) 65 displayPoint(dataset, tileName, p) 66 saveAppState(); 67} 68 69function loadData() 70{ 71 var mapBounds = mapObj.getBounds(); 72 var mapCenter = mapObj.getCenter(); 73 var mapZoom = mapObj.getZoom(); 74 // get center in tile coordinates 75 for (var datasetName in datasetSettings) 76 { 77 if (!tiledData[datasetName]) 78 tiledData[datasetName] = {}; 79 var settings = datasetSettings[datasetName]; 80 81 if (!mapBounds.intersects([[settings.bbox.b,settings.bbox.l],[settings.bbox.t,settings.bbox.r]])) 82 continue; 83 if (!mapObj.hasLayer(settings.layer)) 84 continue; 85 86 if (!settings.icons) 87 loadIcons(settings); 88 89 var tileCoordinates = geoHelper.latlonToTilenumber( 90 mapCenter.lat, 91 mapCenter.lng, 92 settings.data.zoom); 93 94 // Load 9 tiles around the center 95 for (var x = tileCoordinates.x - 1; x <= tileCoordinates.x + 1; x++) 96 { 97 for (var y = tileCoordinates.y - 1; y <= tileCoordinates.y + 1; y++) 98 { 99 var tileName = x + "_" + y; 100 if (tiledData[datasetName][tileName]) 101 continue; // tile already loaded or loading 102 tiledData[datasetName][tileName] = {}; 103 104 (function(datasetName, tileName, source) 105 { 106 var req = new XMLHttpRequest(); 107 req.overrideMimeType("application/json"); 108 req.onreadystatechange = function() 109 { 110 if (req.readyState != 4) 111 return; 112 var data = geojsonToPointlist(JSON.parse(req.responseText)); 113 tiledData[datasetName][tileName].data = data; 114 for (var p = 0; p < data.length; p++) 115 displayPoint(datasetName, tileName, p); 116 loadOverpass(); 117 } 118 req.open("GET", source + "/" + tileName + ".json", true); 119 try { req.send(null); } catch (e) {} 120 })(datasetName, tileName, settings.data.source); 121 } 122 } 123 loadOverpass(); 124 } 125 saveAppState(); 126} 127 128function loadOverpass() 129{ 130 // Overpass only accepts one query at a time, schedule new query if query is waiting 131 if (queryStatus.busy) 132 { 133 queryStatus.waiting = true; 134 return; 135 } 136 queryStatus.waiting = false; 137 var mapZoom = mapObj.getZoom(); 138 var mapBounds = mapObj.getBounds(); 139 var mapCenter = mapObj.getCenter(); 140 // Make query 141 var query = "[out:json];\n"; 142 var types = ["node"/*,"way","rel"*/]; 143 var queriedDatasets = []; 144 for (var datasetName in datasetSettings) 145 { 146 if (!tiledData[datasetName]) 147 continue; 148 var settings = datasetSettings[datasetName]; 149 var tileCoordinates = geoHelper.latlonToTilenumber( 150 mapCenter.lat, 151 mapCenter.lng, 152 settings.data.zoom); 153 154 for (var x = tileCoordinates.x - 1; x <= tileCoordinates.x + 1; x++) 155 { 156 for (var y = tileCoordinates.y - 1; y <= tileCoordinates.y + 1; y++) 157 { 158 var tileName = x + "_" + y; 159 var tileBbox = geoHelper.tilenumberToBbox(x, y, settings.data.zoom); 160 // add a margin to the bbox 161 geoHelper.padBbox(tileBbox, settings.dist); 162 163 if (!tiledData[datasetName][tileName] || 164 !tiledData[datasetName][tileName].data || 165 !tiledData[datasetName][tileName].data.length) 166 continue; 167 if (tiledData[datasetName][tileName].overpassQueried) 168 continue; 169 tiledData[datasetName][tileName].overpassQueried = true; 170 171 query += "("; 172 for (var t = 0; t < types.length; t++) 173 query += types[t] + settings.query + "(" + tileBbox.b + "," + tileBbox.l + "," + tileBbox.t + "," + tileBbox.r + ");"; 174 query += "); out center; out count;\n"; 175
176 queriedDatasets.push({"tileName": tileName, "datasetName": datasetName}); 177 } 178 } 179 } 180 181 if (!queriedDatasets.length) 182 return; 183 184 console.log("overpass query:\n" + query); 185 186 // Send query to overpass 187 var req = new XMLHttpRequest(); 188 req.onreadystatechange = function() 189 { 190 if (req.readyState != 4) 191 return; 192 if (req.status != 200) 193 return; 194 var osmData = JSON.parse(req.responseText).elements; 195 compareData(queriedDatasets, osmData); 196 queryStatus.busy = false; 197 if (queryStatus.waiting) 198 loadOverpass(); 199 } 200 queryStatus.busy = true; 201 req.open("GET", overpassapi + encodeURIComponent(query), true); 202 req.send(null); 203} 204 205function displayPoint(datasetName, tileName, idx) 206{ 207 var point = tiledData[datasetName][tileName].data[idx]; 208 var settings = datasetSettings[datasetName]; 209 // add marker to the data for future reference 210 if (!point.marker) 211 point.marker = L.marker(point.coordinates, {icon: settings.greyIcon}) 212 .addTo(settings.layer); 213 214 if (point.score == undefined) 215 return; // only initial display 216 217 // if the point has been compared, change its colour 218 if (point.score == point.maxScore && appSettings.hideCompletePOI) 219 point.marker.setOpacity(0); 220 else 221 point.marker.setOpacity(1); 222 223 point.marker.setIcon(settings.icons[Math.floor(10 * point.score/point.maxScore)]); 224 point.marker.bindPopup(htmlHelper.getPopup(datasetName, tileName, idx), {"maxWidth": 900}); 225} 226 227function loadIcons(settings) 228{ 229 // create the icon in 11 colours from red to green + a grey one 230 settings.icons = []; 231 for (var i = 0; i <= 10; i++) 232 { 233 var colour = hslToRgb(i / 30, 1, 0.5); 234 settings.icons.push(L.MakiMarkers.icon({icon: settings.icon, color: colour, size: "m"})); 235 } 236 settings.greyIcon = L.MakiMarkers.icon({icon: settings.icon, color: "#808080", size: "m"}); 237} 238 239function geojsonToPointlist(geojson) 240{ 241 var results = []; 242 if (!geojson.features) 243 return results; 244 245 for (var i = 0; i < geojson.features.length; i++) 246 { 247 // TODO skip non-point features 248 var point = {}; 249 // switch lat and lon to the OSM standard order 250 point.coordinates = {}; 251 point.coordinates.lat = geojson.features[i].geometry.coordinates[1]; 252 point.coordinates.lon = geojson.features[i].geometry.coordinates[0]; 253 point.properties = geojson.features[i].properties; 254 results.push(point); 255 } 256 return results; 257} 258 259/** 260 * Converts an HSL color value to RGB. Conversion formula 261 * adapted from http://en.wikipedia.org/wiki/HSL_color_space. 262 * Assumes h, s, and l are contained in the set [0, 1] and 263 * returns r, g, and b in the set [0, 255]. 264 * 265 * @param Number h The hue 266 * @param Number s The saturation 267 * @param Number l The lightness 268 * @return Array The RGB representation 269 */ 270function hslToRgb(h, s, l){ 271 var r, g, b; 272 273 if(s == 0) 274 { 275 r = g = b = l; // achromatic 276 } 277 else 278 { 279 function hue2rgb(p, q, t) { 280 if(t < 0) t += 1; 281 if(t > 1) t -= 1; 282 if(t < 1/6) return p + (q - p) * 6 * t; 283 if(t < 1/2) return q; 284 if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; 285 return p; 286 } 287 288 var q = l < 0.5 ? l * (1 + s) : l + s - l * s; 289 var p = 2 * l - q; 290 r = hue2rgb(p, q, h + 1/3); 291 g = hue2rgb(p, q, h); 292 b = hue2rgb(p, q, h - 1/3); 293 } 294 295 function toHex(n) { 296 var h = Math.round(n * 255).toString(16); 297 if (h.length < 2) 298 h = "0" + h; 299 return h; 300 } 301 302 return "#" + toHex(r) + toHex(g) + toHex(b); 303} 304 305 306function escapeXML(str) 307{ 308 return str.replace(/&/g, "&") 309 .replace(/'/g, "'") 310 .replace(/>/g, ">") 311 .replace(/</g, "<"); 312} 313 314function saveAppState() 315{ 316 var date = new Date(); 317 date.setFullYear(date.getFullYear() + 1); 318 var stateString = getStateString(); 319 document.cookie = "state=" + encodeURIComponent(stateString) + ";expires=" + date.toUTCString(); 320 window.location.hash = stateString; 321} 322 323function loadAppState() 324{ 325 var stateString; 326 if (window.location.hash) 327 stateString = window.location.hash.slice(1); 328 else if (document.cookie) 329 stateString = decodeURIComponent(document.cookie.slice(6)); 330 if (stateString) 331 applyStateString(stateString); 332} 333 334function getStateString() 335{ 336 var r = ""; 337 var center = mapObj.getCenter(); 338 r += "map=" + mapObj.getZoom() + "/" + center.lat.toFixed(4) + "/" + center.lng.toFixed(4); 339 340 var loadedDatasets = []; 341 for (var dataset in datasetSettings) 342 if (mapObj.hasLayer(datasetSettings[dataset].layer)) 343 loadedDatasets.push(encodeURIComponent(dataset)); 344 if (loadedDatasets.length > 0) 345 r+= "&datasets=" + loadedDatasets.join(";"); 346 347 var activatedSettings = [];
348 for (var key in appSettings) 349 if (appSettings[key] == true) 350 activatedSettings.push(encodeURIComponent(key)); 351 if (activatedSettings.length > 0) 352 r += "&settings=" + activatedSettings.join(";"); 353 return r; 354} 355 356function applyStateString(state) 357{ 358 var splitState = state.split("&"); 359 for (var i = 0; i < splitState.length; i++) 360 { 361 if (splitState[i].indexOf("map=") == 0) 362 { 363 var mapState = splitState[i].match(/[0-9\.]+/g); 364 mapObj.setView([+mapState[1], +mapState[2]], +mapState[0]); 365 } 366 else if (splitState[i].indexOf("datasets=") == 0) 367 { 368 var loadedDatasets = splitState[i].substr(9).split(";"); 369 for (var d = 0; d < loadedDatasets.length; d++) 370 { 371 var id = decodeURIComponent(loadedDatasets[d]) 372 var el = document.getElementById(id + "Dataset"); 373 el.checked = true; 374 toggleDataset(id, el); 375 } 376 } 377 else if (splitState[i].indexOf("settings=") == 0) 378 { 379 var activatedSettings = splitState[i].substr(9).split(";"); 380 for (var s = 0; s < activatedSettings.length; s++) 381 { 382 var id = decodeURIComponent(activatedSettings[s]); 383 var el = document.getElementById(id + "Setting"); 384 el.checked = true; 385 changeSetting(el); 386 } 387 } 388 } 389}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.