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() {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() {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 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: '© 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) 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();