widths = [12, 13, 13, 13, 13, 13, 13, 13]; opas = [0.8, 0.6, 0.5, 0.5, 0.4]; mwidths = [1, 1, 1, 1.5, 2, 3, 5, 6, 6, 4, 3, 2]; backend = ""; function marker(stat, z) { if (z > 15) { return L.circle( [stat.lat, stat.lon], { color: 'black', fillColor: stat.attrerrs ? 'red' : stat.su ? '#0000c3' : '#78f378', radius: mwidths[23 - z], fillOpacity: 1, weight: 1, id: stat.id } ); } else { return L.polyline( [ [stat.lat, stat.lon], [stat.lat, stat.lon] ], { color: stat.attrerrs ? 'red' : stat.su ? '#0000c3' : '#78f378', weight: widths[15 - z], opacity: opas[15 - z], id: stat.id } ); } } function poly(group, z) { var style = { color: "#85f385", smoothFactor: 0.4, fillOpacity: 0.2, id: group.id }; if (group.new) { style.color = "#0000c3"; } if (z < 16) { style.weight = 11; style.opacity = 0.5; style.fillOpacity = 0.5; } return L.polygon(group.poly, style) } function sugArr(sug, z) { return L.polyline(sug.arrow, { id: sug.id, color: '#0000c3', smoothFactor: 0.1, weight: 4, opacity: 0.5 }); } function renderStat(stat) { var attrrows = {}; var ll = { lat: stat.lat, lon: stat.lon }; var content = document.createElement('div'); content.setAttribute("id", "nav") var attrTbl = document.createElement('table'); attrTbl.setAttribute("id", "attr-tbl") var suggD = document.createElement('div'); suggD.setAttribute("id", "sugg") if (stat.attrs.name) { content.innerHTML = "Node " + stat.osmid + " (\"" + stat.attrs.name + "\")"; } else { content.innerHTML = "Node " + stat.osmid + ""; } content.appendChild(attrTbl); content.appendChild(suggD); var tbody = document.createElement('tbody'); attrTbl.appendChild(tbody); for (var key in stat.attrs) { var row = document.createElement('tr'); var col1 = document.createElement('td'); var col2 = document.createElement('td'); col2.className = "err-wrap"; tbody.appendChild(row); row.appendChild(col1); row.appendChild(col2); col1.innerHTML = "" + key + ""; for (var i = 0; i < stat.attrs[key].length; i++) col2.innerHTML += "" + stat.attrs[key][i] + "" + "
"; attrrows[key] = row; } for (var i = 0; i < stat.attrerrs.length; i++) { var err = stat.attrerrs[i]; var row = attrrows[err.attr[0]]; row.className = "err-" + Math.round(err.conf * 10); var info = document.createElement('div'); if (err.other_osmid != stat.osmid) { info.innerHTML = "Does not match " + err.other_attr[0] + " in node " + err.other_osmid + ""; } else { info.innerHTML = "Does not match '" + err.other_attr[0] + "' = '" + err.other_attr[1]; } info.className = 'attr-err-info'; row.childNodes[1].appendChild(info); } var suggList = document.createElement('ul'); if (stat.su.length) { var a = document.createElement('span'); a.className = "sugtit"; a.innerHTML = "Suggestions"; suggD.appendChild(a); } suggD.appendChild(suggList); for (var i = 0; i < stat.su.length; i++) { var sugg = stat.su[i]; var suggDiv = document.createElement('li'); if (sugg.type == 1) { suggDiv.innerHTML = "Move node into a new relation public_transport=stop_area"; } else if (sugg.type == 2) { suggDiv.innerHTML = "Move node into existing relation " + sugg.target_osm_rel_id + "."; } else if (sugg.type == 3) { suggDiv.innerHTML = "Move node from existing relation " + sugg.orig_osm_rel_id + " into a new relation public_transport=stop_area"; } else if (sugg.type == 4) { suggDiv.innerHTML = "Move node from existing relation " + sugg.orig_osm_rel_id + " into existing relation " + sugg.target_osm_rel_id + "."; } else if (sugg.type == 5) { suggDiv.innerHTML = "Move node out of existing relation " + sugg.orig_osm_rel_id + ""; } else if (sugg.type == 6) { suggDiv.innerHTML = "Fix attribute " + sugg.attr + ""; } suggList.appendChild(suggDiv); } if (map.getZoom() < 18) { map.setView(ll, 18, { animate: true }); } L.popup({ opacity: 0.8 }) .setLatLng(ll) .setContent(content) .openOn(map); } function openStat(id) { window.xmlhttp = new XMLHttpRequest(); window.xmlhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200 && this == window.xmlhttp) { var content = JSON.parse(this.responseText); renderStat(content); } }; window.xmlhttp.open("GET", backend + "/stat?id=" + id, true); window.xmlhttp.send(); } function renderGroup(grp, ll) { var attrrows = {}; var content = document.createElement('div'); content.setAttribute("id", "nav"); var newMembers = document.createElement('div'); newMembers.setAttribute("id", "group-stations-new") newMembers.innerHTML = "New Members"; var oldMembers = document.createElement('div'); oldMembers.setAttribute("id", "group-stations-old") oldMembers.innerHTML = "Existing Members"; if (grp.osmid == 1) { content.innerHTML = "New relation public_transport=stop_area"; } else { content.innerHTML = "OSM relation " + grp.osmid + ""; } content.appendChild(newMembers); if (grp.osmid != 1) content.appendChild(oldMembers); for (var key in grp.stations) { var stat = grp.stations[key]; var row = document.createElement('div'); if (stat.attrs.name) { row.innerHTML = "Node " + stat.osmid + " (\"" + stat.attrs.name + "\")"; } else { row.innerHTML = "Node " + stat.osmid + ""; } if (grp.osmid == 1 || stat.orig_group != grp.id) newMembers.appendChild(row); else { oldMembers.appendChild(row); if (stat.group != grp.id) { row.className = "del-stat"; } } } if (map.getZoom() < 18) { map.setView(ll, 18, { animate: true }); } L.popup({ opacity: 0.8 }) .setLatLng(ll) .setContent(content) .openOn(map); } function openGroup(id, ll) { window.xmlhttp = new XMLHttpRequest(); window.xmlhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200 && this == window.xmlhttp) { var content = JSON.parse(this.responseText); renderGroup(content, ll); } }; window.xmlhttp.open("GET", backend + "/group?id=" + id, true); window.xmlhttp.send(); } function groupHl(id) { if (!document.groupIdx[id]) return; document.groupIdx[id].setStyle({ 'weight': 6, 'fillOpacity': 0.5 }); } function groupUnHl(id) { if (!document.groupIdx[id]) return; document.groupIdx[id].setStyle({ 'weight': 3, 'fillOpacity': 0.2 }); } function nodeHl(id) { if (!document.nodeIdx[id]) return; document.nodeIdx[id].setStyle({ 'weight': 5, 'color': "#eecc00" }); } function nodeUnHl(id) { if (!document.nodeIdx[id]) return; document.nodeIdx[id].setStyle({ 'weight': 1, 'color': "black" }); } var map = L.map('map', { renderer: L.canvas(), attributionControl: false, }).setView([47.9965, 7.8469], 13); map.addControl(L.control.attribution({ position: 'bottomright', prefix: '© University of Freiburg, Chair of Algorithms and Data Structures' })); L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png', { maxZoom: 23, attribution: '© OpenStreetMap, © CARTO', id: 'mapbox.streets' }).addTo(map); var layer = L.featureGroup().addTo(map); var labelLayer = L.featureGroup().addTo(map); map.on("moveend", function() { render(); }); function render() { window.xmlhttp = new XMLHttpRequest(); if (map.getZoom() < 11) { window.xmlhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200 && this == window.xmlhttp) { var content = JSON.parse(this.responseText); layer.clearLayers(); labelLayer.clearLayers(); var blur = 22 - map.getZoom(); var rad = 25 - map.getZoom(); layer.addLayer(L.heatLayer(content.ok, { max: 500, gradient: { 0: '#cbf7cb', 0.5: '#78f378', 1: '#29c329' }, minOpacity: 0.65, blur: blur, radius: rad })); layer.addLayer(L.heatLayer(content.sugg, { max: 500, gradient: { 0: '#7f7fbd', 0.5: '#4444b3', 1: '#0606c1' }, minOpacity: 0.65, blur: blur - 3, radius: Math.min(12, rad - 3) })); layer.addLayer(L.heatLayer(content.err, { max: 500, gradient: { 0: '#f39191', 0.5: '#ff5656', 1: '#ff0000' }, minOpacity: 0.75, blur: blur - 3, radius: Math.min(10, rad - 3), maxZoom: 15 })); } }; window.xmlhttp.open("GET", backend + "/heatmap?z=" + map.getZoom() + "&bbox=" + [map.getBounds().getSouthWest().lat, map.getBounds().getSouthWest().lng, map.getBounds().getNorthEast().lat, map.getBounds().getNorthEast().lng].join(","), true); window.xmlhttp.send(); } else { window.xmlhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200 && this == window.xmlhttp) { var content = JSON.parse(this.responseText); layer.clearLayers(); labelLayer.clearLayers(); document.groupIdx = {}; document.nodeIdx = {}; var stations = []; var labels = []; for (var i = 0; i < content.stats.length; i++) { stat = content.stats[i]; var ndMarker = marker(stat, map.getZoom()); stations.push(ndMarker); document.nodeIdx[stat.id] = ndMarker; } var groups = []; for (var i = 0; i < content.groups.length; i++) { group = content.groups[i]; var groupPoly = poly(group, map.getZoom()); groups.push(groupPoly); document.groupIdx[group.id] = groupPoly; } var suggs = []; for (var i = 0; i < content.su.length; i++) { sugg = content.su[i]; var a = sugArr(sugg, map.getZoom()); suggs.push(a); } if (map.getZoom() > 13) { layer.addLayer(L.featureGroup(groups).on('click', function(a) { openGroup(a.layer.options.id, a.layer.getBounds().getCenter()); })); } if (map.getZoom() > 15) { labelLayer.addLayer(L.featureGroup(labels)); layer.addLayer(L.featureGroup(suggs).on('click', function(a) { openStat(a.layer.options.id); })); } layer.addLayer(L.featureGroup(stations).on('click', function(a) { openStat(a.layer.options.id); })); } }; window.xmlhttp.open("GET", backend + "/map?bbox=" + [map.getBounds().getSouthWest().lat, map.getBounds().getSouthWest().lng, map.getBounds().getNorthEast().lat, map.getBounds().getNorthEast().lng].join(","), true); window.xmlhttp.send(); } } render();