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 <span class='grouplink' onmouseover='grHl(${tid})' onmouseout='grUnHl(${tid})'>new relation</span> <tt>public_transport=stop_area</tt>."; var sgMvOrEx = "Move into relation <a onmouseover='grHl(${tid})' onmouseout='grUnHl(${tid})' href=\"" + osmUrl + "relation/${toid}\" target=\"_blank\">${toid}</a>."; var sgMvRelNew = "Move from relation <a onmouseover='grHl(${oid})' onmouseout='grUnHl(${oid})' href=\"" + osmUrl + "relation/${ooid}\" target=\"_blank\">${ooid}</a> into a <span class='grouplink' onmouseover='grHl(${tid})' onmouseout='grUnHl(${tid})'>new relation</span> <tt>public_transport=stop_area</tt>."; var sgMvRelRel = "Move from relation <a onmouseover='grHl(${oid})' onmouseout='grUnHl(${oid})' href=\"" + osmUrl + "relation/${ooid}\" target=\"_blank\">${ooid}</a> into relation <a onmouseover='grHl(${tid})' onmouseout='grUnHl(${tid})' href=\"" + osmUrl + "relation/${toid}\" target=\"_blank\">${toid}</a>."; var sgMvOutRel = "Move out of relation <a onmouseover='grHl(${oid})' onmouseout='grUnHl(${oid})' href=\"" + osmUrl + "relation/${ooid}\" target=\"_blank\">${ooid}</a>"; var sgFixAttr = "Fix attribute <tt>${attr}</tt>."; var sgAddName = "Consider adding a <tt><a target='_blank' href='https://wiki.openstreetmap.org/wiki/Key:name'>name</a></tt> attribute."; var sgAttrTr = "Attribute <tt>${attr}</tt> seems to be a track number. Use <tt>ref</tt> for this and set <tt>${attr}</tt> 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 + " <a target='_blank' href='" + osmUrl + ident.toLowerCase()+"/" + osmid + "'>" + osmid + "</a>"; if (stat.attrs.name) con.innerHTML += " (<b>\"" + stat.attrs.name + "\"</b>)"; con.innerHTML += "<a class='ebut' target='_blank' href='" + osmUrl + ident.toLowerCase() + "=" + osmid +"'>✎</a>"; 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 = "<a href=\"https://wiki.openstreetmap.org/wiki/Key:" + key + "\" target=\"_blank\"><tt>" + key + "</tt></a>"; for (var i = 0; i < stat.attrs[key].length; i++) col2.innerHTML += "<span class='attrval'>" + stat.attrs[key][i] + "</span>" + "<br>"; 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 <tt>" + err.other_attr[0] + "</tt> in relation <a onmouseover='grHl( " + err.other_grp + ")' onmouseout='grUnHl( " + err.other_grp + ")' target=\"_blank\" href=\"" + osmUrl + "relation/" + Math.abs(err.other_osmid) + "\">" + Math.abs(err.other_osmid) + "</a>"; else info.innerHTML = "Does not match <tt>" + err.other_attr[0] + "</tt> in relation <span onmouseover='grHl( " + err.other_grp + ")' onmouseout='grUnHl( " + err.other_grp + ")'>" + Math.abs(err.other_osmid) + "</span>"; } 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 <tt>" + err.other_attr[0] + "</tt> in " + lident + " <a onmouseover='stHl( " + err.other + ")' onmouseout='stUnHl( " + err.other + ")' target=\"_blank\" href=\"" + osmUrl + lident+"/" + Math.abs(err.other_osmid) + "\">" + Math.abs(err.other_osmid) + "</a>"; } else { info.innerHTML = "Does not match <tt>" + err.other_attr[0] + "</tt> = '" + 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() {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 = "<span class='newmemberstit'>New Members</span>"; var oldMembers = $$('div'); oldMembers.setAttribute("id", "group-stations-old") oldMembers.innerHTML = "<span class='oldmemberstit'>Existing Members</span>"; if (grp.osmid == 1) { con.innerHTML = "<span class='grouplink'>New relation</span> <tt>public_transport=stop_area</tt>"; } else { con.innerHTML = "OSM relation <a target='_blank' href='https://www.openstreetmap.org/relation/" + grp.osmid + "'>" + grp.osmid + "</a>"; if (grp.attrs.name) con.innerHTML += " (<b>\"" + grp.attrs.name + "\"</b>)"; con.innerHTML += "<a class='ebut' target='_blank' href='https://www.openstreetmap.org/edit?relation=" + grp.osmid +"'>✎</a>"; } 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 = "<a href=\"https://wiki.openstreetmap.org/wiki/Key:" + key + "\" target=\"_blank\"><tt>" + key + "</tt></a>"; for (var i = 0; i < grp.attrs[key].length; i++) col2.innerHTML += "<span class='attrval'>" + grp.attrs[key][i] + "</span>" + "<br>"; 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 <tt>" + err.other_attr[0] + "</tt> in relation <a onmouseover='grHl( " + err.other_grp + ")' onmouseout='grUnHl( " + err.other_grp + ")' target=\"_blank\" href=\"" + osmUrl + "relation/" + Math.abs(err.other_osmid) + "\">" + Math.abs(err.other_osmid) + "</a>"; else info.innerHTML = "Does not match <tt>" + err.other_attr[0] + "</tt> in relation <span onmouseover='grHl( " + err.other_grp + ")' onmouseout='grUnHl( " + err.other_grp + ")'>" + Math.abs(err.other_osmid) + "</span>"; } else info.innerHTML = "Does not match <tt>" + err.other_attr[0] + "</tt> = '" + err.other_attr[1] + "'"; } else { // the mismatch was with another station var ident = err.other_osmid < 0 ? "way" : "node"; info.innerHTML = "Does not match <tt>" + err.other_attr[0] + "</tt> in " + ident + " <a onmouseover='stHl( " + err.other + ")' onmouseout='stUnHl( " + err.other + ")' target=\"_blank\" href=\"" + osmUrl + ident+"/" + Math.abs(err.other_osmid) + "\">" + Math.abs(err.other_osmid) + "</a>"; } 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 + " <a onmouseover='stHl( " + stat.id + ")' onmouseout='stUnHl( " + stat.id + ")' target='_blank' href='" + osmUrl + ident.toLowerCase() + "/" + Math.abs(stat.osmid) + "'>" + Math.abs(stat.osmid) + "</a>"; if (stat.attrs.name) row.innerHTML += " (<b>\"" + stat.attrs.name + "\"</b>)"; 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 <tt>" + sugg.attr + "</tt>."; else if (sugg.type == 7) suggDiv.innerHTML = "Consider adding a <tt><a target='_blank' href='https://wiki.openstreetmap.org/wiki/Key:name'>name</a></tt> attribute."; else if (sugg.type == 8) suggDiv.innerHTML = "Attribute <tt>" + sugg.attr + "</tt> seems to be a track number. Use <tt>ref</tt> for this and set <tt>" + sugg.attr + "</tt> to the station name."; suggList.appendChild(suggDiv); } con.appendChild(suggD); L.popup({opacity: 0.8}) .setLatLng(ll) .setContent(con) .openOn(map) .on('remove', function() {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: '© <a href="http://ad.cs.uni-freiburg.de">University of Freiburg, Chair of Algorithms and Data Structures</a>' })); map.on('popupopen', function(e) { var px = map.project(e.target._popup._latlng); px.y -= e.target._popup._container.clientHeight/2; map.panTo(map.unproject(px),{animate: true}); s(); }); L.tileLayer('http://{s}.tile.stamen.com/toner-lite/{z}/{x}/{y}.png', { maxZoom: 20, attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>', 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) stUnHl(row.stat.i); if (row.group) 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 = ""; 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)}; 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 kp(e) { if (e.keyCode == 40) { var sels = $('.selres') if (sels.length) select(sels[0].nextSibling); else select($('.sres')[0]); e.preventDefault(); } else if (e.keyCode == 38) { 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();