var widths = [12, 13, 13, 13, 13, 13, 13, 13]; var opas = [0.8, 0.6, 0.5, 0.5, 0.4]; var mwidths = [1, 1, 1, 1.5, 2, 3, 5, 6, 6, 4, 3, 2]; var stCols = ['#78f378', '#0000c3', 'red']; var osmUrl = "//www.openstreetmap.org/"; var grIdx, stIdx, selectedRes, prevSearch, delayTimer; var reqs = {}; var openedGr = -1; var openedSt = -1; var sgMvOrNew = "Move into a new relation public_transport=stop_area."; var sgMvOrEx = "Move into relation ${toid}."; var sgMvRelNew = "Move from relation ${ooid} into a new relation public_transport=stop_area."; var sgMvRelRel = "Move from relation ${ooid} into relation ${toid}."; var sgMvOutRel = "Move out of relation ${ooid}"; var sgFixAttr = "Fix attribute ${attr}."; var sgAddName = "Consider adding a name attribute."; var sgAttrTr = "Attribute ${attr} seems to be a track number. Use ref for this and set ${attr} to the station name."; var suggsMsg = [sgMvOrNew, sgMvOrEx, sgMvRelNew, sgMvRelRel, sgMvOutRel, sgFixAttr, sgAddName, sgAttrTr]; function $(a){return a[0] == "#" ? document.getElementById(a.substr(1)) : a[0] == "." ? document.getElementsByClassName(a.substr(1)) : document.getElementsByTagName(a)} function $$(t){return document.createElement(t) } function ll(g){return {"lat" : g[0], "lng" : g[1]}} function hasCl(e, c){return e.className.split(" ").indexOf(c) != -1} function addCl(e, c){if (!hasCl(e, c)) e.className += " " + c;e.className = e.className.trim()} function delCl(e, c){var a = e.className.split(" "); delete a[a.indexOf(c)]; e.className = a.join(" ").trim()} function stCol(s){return s.e ? stCols[2] : s.s ? stCols[1] : stCols[0]} function tmpl(s, r){for (var p in r) s = s.replace(new RegExp("\\${" + p + "}", "g"), r[p]); return s} function req(id, u, cb) { if (reqs[id]) reqs[id].abort(); reqs[id] = new XMLHttpRequest(); reqs[id].onreadystatechange = function() { if (this.readyState == 4 && this.status == 200 && this == reqs[id]) cb(JSON.parse(this.responseText))}; reqs[id].open("GET", u, 1); reqs[id].send(); } function marker(stat, z) { if (stat.g.length == 1) { if (z > 15) { return L.circle( stat.g[0], { color: '#000', fillColor: stCol(stat), radius: mwidths[23 - z], fillOpacity: 1, weight: z > 17 ? 1.5 : 1, id: stat.i } ); } else { return L.polyline( [stat.g[0], stat.g[0]], { color: stCol(stat), fillColor: stCol(stat), weight: widths[15 - z], opacity: opas[15 - z], id: stat.i } ); } } else { return L.polygon( stat.g, { color: z > 15 ? '#000': stCol(stat), fillColor: stCol(stat), smoothFactor: 0, fillOpacity: 0.75, weight: z > 17 ? 1.5 : 1, id: stat.i } ); } } function poly(group, z) { var col = group.e ? 'red' : group.s ? '#0000c3' : '#85f385'; var style = { color: col, fillColor: col, smoothFactor: 0.4, fillOpacity: 0.2, id: group.i }; if (z < 16) { style.weight = 11; style.opacity = 0.5; style.fillOpacity = 0.5; } return L.polygon(group.g, style) } function sugArr(sug, z) { return L.polyline(sug.a, { id: sug.i, color: '#0000c3', smoothFactor: 0.1, weight: 4, opacity: 0.5 }); } function rndrSt(stat) { openedSt = stat.id; stHl(stat.id); var attrrows = {}; var way = stat.osmid < 0; var osmid = Math.abs(stat.osmid); var ident = way ? "Way" : "Node"; var con = $$('div'); con.setAttribute("id", "nav") var suggD = $$('div'); suggD.setAttribute("id", "sugg") con.innerHTML = ident + " " + osmid + ""; if (stat.attrs.name) con.innerHTML += " (\"" + stat.attrs.name + "\")"; con.innerHTML += ""; var attrTbl = $$('table'); attrTbl.setAttribute("id", "attr-tbl") con.appendChild(attrTbl); con.appendChild(suggD); var tbody = $$('tbody'); attrTbl.appendChild(tbody); for (var key in stat.attrs) { var row = $$('tr'); var col1 = $$('td'); var col2 = $$('td'); addCl(col2, "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]]; addCl(row, "err-" + Math.round(err.conf * 10)); var info = $$('div'); if (err.other_grp) { // the mismatch was with a group name if (err.other_osmid > 1) info.innerHTML = "Does not match " + err.other_attr[0] + " in relation " + Math.abs(err.other_osmid) + ""; else info.innerHTML = "Does not match " + err.other_attr[0] + " in relation " + Math.abs(err.other_osmid) + ""; } else { // the mismatch was with another station if (err.other_osmid != stat.osmid) { var lident = err.other_osmid < 0 ? "way" : "node"; info.innerHTML = "Does not match " + err.other_attr[0] + " in " + lident + " " + Math.abs(err.other_osmid) + ""; } else { info.innerHTML = "Does not match " + err.other_attr[0] + " = '" + err.other_attr[1] + "'"; } } addCl(info, 'attr-err-info'); row.childNodes[1].appendChild(info); } var suggList = $$('ul'); if (stat.su.length) { var a = $$('span'); addCl(a, "sugtit"); a.innerHTML = "Suggestions"; suggD.appendChild(a); } suggD.appendChild(suggList); for (var i = 0; i < stat.su.length; i++) { var sg = stat.su[i]; var sgDiv = $$('li'); sgDiv.innerHTML = tmpl(suggsMsg[sg.type - 1], {"attr" : sg.attr, "tid" : sg.target_gid, "ooid" : sg.orig_osm_rel_id, "toid" : sg.target_osm_rel_id, "oid" : sg.orig_gid}); suggList.appendChild(sgDiv); } L.popup({opacity: 0.8}) .setLatLng(stat) .setContent(con) .openOn(map) .on('remove', function() {if (openedSt == stat.id) {openedSt = -1; stUnHl(stat.id)}}); } function openSt(id) {req("s", "/stat?id=" + id, function(c) {rndrSt(c)});} function rndrGr(grp, ll) { openedGr = grp.id; var attrrows = {}; grHl(grp.id); var con = $$('div'); con.setAttribute("id", "nav"); var newMembers = $$('div'); newMembers.setAttribute("id", "group-stations-new") newMembers.innerHTML = "New Members"; var oldMembers = $$('div'); oldMembers.setAttribute("id", "group-stations-old") oldMembers.innerHTML = "Existing Members"; if (grp.osmid == 1) { con.innerHTML = "New relation public_transport=stop_area"; } else { con.innerHTML = "OSM relation " + grp.osmid + ""; if (grp.attrs.name) con.innerHTML += " (\"" + grp.attrs.name + "\")"; con.innerHTML += ""; } var attrTbl = $$('table'); attrTbl.setAttribute("id", "attr-tbl") con.appendChild(attrTbl); var tbody = $$('tbody'); attrTbl.appendChild(tbody); var suggD = $$('div'); suggD.setAttribute("id", "sugg") for (var key in grp.attrs) { var row = $$('tr'); var col1 = $$('td'); var col2 = $$('td'); addCl(col2, "err-wrap"); tbody.appendChild(row); row.appendChild(col1); row.appendChild(col2); col1.innerHTML = "" + key + ""; for (var i = 0; i < grp.attrs[key].length; i++) col2.innerHTML += "" + grp.attrs[key][i] + "" + "
"; attrrows[key] = row; } for (var i = 0; i < grp.attrerrs.length; i++) { var err = grp.attrerrs[i]; var row = attrrows[err.attr[0]]; addCl(row, "err-" + Math.round(err.conf * 10)); var info = $$('div'); if (err.other_grp) { // the mismatch was with a group name if (err.other_osmid != grp.osmid) { if (err.other_osmid > 1) info.innerHTML = "Does not match " + err.other_attr[0] + " in relation " + Math.abs(err.other_osmid) + ""; else info.innerHTML = "Does not match " + err.other_attr[0] + " in relation " + Math.abs(err.other_osmid) + ""; } else info.innerHTML = "Does not match " + err.other_attr[0] + " = '" + err.other_attr[1] + "'"; } else { // the mismatch was with another station var ident = err.other_osmid < 0 ? "way" : "node"; info.innerHTML = "Does not match " + err.other_attr[0] + " in " + ident + " " + Math.abs(err.other_osmid) + ""; } addCl(info, 'attr-err-info'); row.childNodes[1].appendChild(info); } con.appendChild(newMembers); if (grp.osmid != 1) con.appendChild(oldMembers); for (var key in grp.stations) { var stat = grp.stations[key]; var row = $$('div'); var ident = stat.osmid < 0 ? "Way" : "Node"; row.innerHTML = ident + " " + Math.abs(stat.osmid) + ""; if (stat.attrs.name) row.innerHTML += " (\"" + stat.attrs.name + "\")"; row.style.backgroundColor = stat.e ? '#f58d8d' : stat.s ? '#b6b6e4' : '#c0f7c0'; if (grp.osmid == 1 || stat.orig_group != grp.id) newMembers.appendChild(row); else { oldMembers.appendChild(row); if (stat.group != grp.id) addCl(row, "del-stat"); } } var suggList = $$('ul'); if (grp.su.length) { var a = $$('span'); addCl(a, "sugtit"); a.innerHTML = "Suggestions"; suggD.appendChild(a); } suggD.appendChild(suggList); for (var i = 0; i < grp.su.length; i++) { var sugg = grp.su[i]; var suggDiv = $$('li'); if (sugg.type == 6) suggDiv.innerHTML = "Fix attribute " + sugg.attr + "."; else if (sugg.type == 7) suggDiv.innerHTML = "Consider adding a name attribute."; else if (sugg.type == 8) suggDiv.innerHTML = "Attribute " + sugg.attr + " seems to be a track number. Use ref for this and set " + sugg.attr + " to the station name."; suggList.appendChild(suggDiv); } con.appendChild(suggD); L.popup({opacity: 0.8}) .setLatLng(ll) .setContent(con) .openOn(map) .on('remove', function() {if (openedGr == grp.id) {openedGr = -1; grUnHl(grp.id)}}); } function openGr(id, ll) { req("g", "/group?id=" + id, function(c) {rndrGr(c, ll)}); } function grHl(id) { !grIdx[id] || grIdx[id].setStyle({'weight': 6, 'color': "#eecc00"}); } function grUnHl(id) { !grIdx[id] || grIdx[id].setStyle({ 'weight': 3, 'color': grIdx[id].options["fillColor"] }); } function stHl(id) { if (!stIdx[id]) return; if (map.getZoom() > 15) { stIdx[id].setStyle({ 'weight': 5, 'color': "#eecc00" }); } else { stIdx[id].setStyle({ 'color': "#eecc00" }); } } function stUnHl(id) { if (!stIdx[id]) return; if (map.getZoom() > 15) { stIdx[id].setStyle({ 'weight': map.getZoom() > 17 ? 1.5 : 1, 'color': "#000" }); } else { stIdx[id].setStyle({ 'color': stIdx[id].options["fillColor"] }); } } var map = L.map('m', {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' })); map.on('popupopen', function(e) { var z = Math.max(map.getZoom(), 16); var px = map.project(e.target._popup._latlng, z); px.y -= e.target._popup._container.clientHeight/2; map.setView(map.unproject(px, z), z, {animate: true}); s(); }); L.tileLayer('http://{s}.tile.stamen.com/toner-lite/{z}/{x}/{y}.png', { maxZoom: 20, attribution: '© OpenStreetMap', opacity: 0.8 }).addTo(map); var l = L.featureGroup().addTo(map); map.on("moveend", function() {render();}); map.on("click", function() {s()}); function render() { if (map.getZoom() < 11) { var b = map.getBounds(); var sw = b.getSouthWest(); var ne = b.getNorthEast(); req("m", "/heatmap?z=" + map.getZoom() + "&bbox=" + [sw.lat, sw.lng, ne.lat, ne.lng].join(","), function(re) { l.clearLayers(); var blur = 22 - map.getZoom(); var rad = 25 - map.getZoom(); l.addLayer(L.heatLayer(re.ok, { max: 500, gradient: { 0: '#cbf7cb', 0.5: '#78f378', 1: '#29c329' }, minOpacity: 0.65, blur: blur, radius: rad })); l.addLayer(L.heatLayer(re.sugg, { max: 500, gradient: { 0: '#7f7fbd', 0.5: '#4444b3', 1: '#0606c1' }, minOpacity: 0.65, blur: blur - 3, radius: Math.min(12, rad - 3) })); l.addLayer(L.heatLayer(re.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 })); } ) } else { req("m", "/map?z=" + map.getZoom() + "&bbox=" + [map.getBounds().getSouthWest().lat, map.getBounds().getSouthWest().lng, map.getBounds().getNorthEast().lat, map.getBounds().getNorthEast().lng].join(","), function(re) { l.clearLayers(); grIdx = {}; stIdx = {}; var stats = []; for (var i = 0; i < re.stats.length; i++) { stIdx[re.stats[i].i] = stats[stats.push(marker(re.stats[i], map.getZoom())) - 1]; } var groups = []; for (var i = 0; i < re.groups.length; i++) { grIdx[re.groups[i].i] = groups[groups.push(poly(re.groups[i], map.getZoom())) - 1];; } var suggs = []; for (var i = 0; i < re.su.length; i++) { suggs.push(sugArr(re.su[i], map.getZoom())); } if (map.getZoom() > 13) { l.addLayer(L.featureGroup(groups).on('click', function(a) { openGr(a.layer.options.id, a.layer.getBounds().getCenter()); })); } l.addLayer(L.featureGroup(stats).on('click', function(a) { openSt(a.layer.options.id); })); if (map.getZoom() > 15) { l.addLayer(L.featureGroup(suggs).on('click', function(a) { openSt(a.layer.options.id); })); } grHl(openedGr); stHl(openedSt); } ) }; } function rowClick(row) { if (!isSearchOpen()) return; if (row.stat) openSt(row.stat.i, ll(row.stat.g[0])); else openGr(row.group.i, ll(row.group.g[0])); } function select(row) { if (!row) return; if (!isSearchOpen()) return; unselect(selectedRes); selectedRes = row; addCl(row, "selres"); if (row.stat) stHl(row.stat.i); if (row.group) grHl(row.group.i); } function unselect(row) { selectedRes = undefined; if (!row) return; delCl(row, "selres"); if (row.stat && row.stat.i != openedSt) stUnHl(row.stat.i); if (row.group && row.group.i != openedGr) grUnHl(row.group.i); } function isSearchOpen() { return $("#sres").className == "res-open"; } function s(q) { var delay = 0; if (q == prevSearch) return; clearTimeout(delayTimer); prevSearch = q; unselect(selectedRes); if (!q) { $('#si').value = ""; $("#sres").className = ""; $("#sres").innerHTML = ""; return; } delayTimer = setTimeout(function() { req("sr", "/search?q=" + q, function(c) { var res = $("#sres"); addCl(res, "res-open"); res.innerHTML = ""; for (var i = 0; i < c.length; i++) { var e = c[i]; var row = $$('span'); addCl(row, "sres"); row.innerHTML = e.n; if (e.w) addCl(row, "res-way"); if (e.s) { row.stat = e.s; addCl(row, "res-stat"); if (e.s.s) addCl(row, "res-sugg"); if (e.s.e) addCl(row, "res-err"); } else { row.group = e.g; addCl(row, "res-group"); if (e.g.s) addCl(row, "res-sugg"); if (e.g.e) addCl(row, "res-err"); } row.onmouseover = function(){select(this)}; row.onclick = function(){rowClick(this)}; var dist = $$('span'); addCl(dist, "dist"); dist.innerHTML = dstr(e.s ? e.s.g : e.g.g); row.appendChild(dist); if (e.v && e.v != e.name) { var via = $$('span'); addCl(via, "via"); via.innerHTML = e.v; row.appendChild(via); } res.appendChild(row); } } )}, delay); } function dstr(s) { if (map.getBounds().contains(ll(s[0]))) return ""; var d = map.distance(map.getCenter(), ll(s[0])); if (d < 500) return Math.round(d).toFixed(0) + "m"; if (d < 5000) return (d / 1000.0).toFixed(1) + "km"; return Math.round(d / 1000.0).toFixed(0) + "km"; } function kp(e) { if (e.keyCode == 40 || (!e.shiftKey && e.keyCode == 9)) { var sels = $('.selres') if (sels.length) select(sels[0].nextSibling); else select($('.sres')[0]); e.preventDefault(); } else if (e.keyCode == 38 || (e.shiftKey && e.keyCode == 9)) { var sels = $('.selres') if (sels.length) { if (sels[0].previousSibling) select(sels[0].previousSibling); else unselect(sels[0]); e.preventDefault(); } } if (e.keyCode == 13) { var sels = $('.selres'); if (sels.length) rowClick(sels[0]); } } $('#del').onclick = function() {s();$("#si").focus()} render();