301 lines
10 KiB
JavaScript
301 lines
10 KiB
JavaScript
// all function related to the map
|
|
|
|
import * as dt from './datetime.js'
|
|
|
|
let map
|
|
let bounds
|
|
let popuptext = ''
|
|
|
|
const colorscale = ['#d53e4f', '#fc8d59', '#fee08b', '#e6f598', '#99d594', '#3288bd', '#808080'];
|
|
const dba = [100, 80, 60, 40, 20, 0, -999];
|
|
|
|
export const showMap = (params) => {
|
|
map = L.map('map',{ scrollWheelZoom: false}).setView(params.center, parseInt(params.zoom))
|
|
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
maxZoom: 19,
|
|
attribution: '© <a href="http://www.js.openstreetmap.org/copyright">OpenStreetMap</a>'
|
|
}).addTo(map)
|
|
bounds = map.getBounds();
|
|
|
|
map.on('moveend', async function () {
|
|
bounds = map.getBounds()
|
|
await buildMarkers(bounds)
|
|
});
|
|
|
|
let legend = L.control({position: 'bottomright'})
|
|
legend.onAdd = function (map) {
|
|
let div = L.DomUtil.create('div', 'info legend')
|
|
let div_color = L.DomUtil.create('div', 'info legend inner', div)
|
|
div_color.innerHTML += 'dbA<br />';
|
|
// loop through our density intervals and generate a label with a colored square for each interval
|
|
for (let i = 0; i < dba.length - 1; i++) {
|
|
div_color.innerHTML +=
|
|
'<i style="background:' + colorscale[i] + '"></i>' +
|
|
' ' + dba[i] + (i == 0 ? '+' : '') + '<br />'
|
|
}
|
|
div_color.innerHTML += ' <i style="background:' + getColor(dba[dba.length - 1]) + '"></i> offline'
|
|
return div
|
|
};
|
|
legend.addTo(map)
|
|
|
|
}
|
|
|
|
|
|
function getColor(d) {
|
|
let val = parseInt(d);
|
|
for (let i = 0; i < dba.length; i++) {
|
|
if (val >= dba[i]) {
|
|
return (colorscale[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
function calcPolygon(bounds) {
|
|
return L.polygon([[bounds.getNorth(), bounds.getWest()], [bounds.getNorth(), bounds.getEast()], [bounds.getSouth(), bounds.getEast()], [bounds.getSouth(), bounds.getWest()]], {
|
|
color: 'black',
|
|
fillOpacity: 0.5
|
|
});
|
|
;
|
|
}
|
|
|
|
async function plotMap(cid, poly) {
|
|
// if sensor nbr is give, find coordinates, else use Stuttgart center
|
|
debug_log('plotMap()');
|
|
let myLatLng;
|
|
if (cid != -1) {
|
|
myLatLng = await getSensorKoords(cid);
|
|
} else {
|
|
let stgt = await getCoords('Stuttgart');
|
|
myLatLng = {lat: parseFloat(stgt.lat), lng: parseFloat(stgt.lon)};
|
|
}
|
|
|
|
// generate map centered on Stuttgart
|
|
map = L.map('map').setView(myLatLng, firstZoom);
|
|
|
|
L.tileLayer('https://{s}.tile.openstreetmap.de/{z}/{x}/{y}.png', {
|
|
maxZoom: 17,
|
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
|
}).addTo(map);
|
|
|
|
bounds = map.getBounds();
|
|
|
|
map.scrollWheelZoom.disable();
|
|
|
|
map.on('moveend', async function () {
|
|
bounds = map.getBounds();
|
|
await buildMarkers(bounds)
|
|
});
|
|
|
|
|
|
let legend = L.control({position: 'bottomright'});
|
|
legend.onAdd = function (map) {
|
|
let div = L.DomUtil.create('div', 'info legend');
|
|
let div_color = L.DomUtil.create('div', 'info legend inner', div);
|
|
div_color.innerHTML += 'dbA<br />';
|
|
// loop through our density intervals and generate a label with a colored square for each interval
|
|
for (let i = 0; i < dba.length - 1; i++) {
|
|
div_color.innerHTML +=
|
|
'<i style="background:' + colorscale[i] + '"></i>' +
|
|
' ' + dba[i] + (i == 0 ? '+' : '') + '<br />';
|
|
}
|
|
div_color.innerHTML += ' <i style="background:' + getColor(dba[dba.length - 1]) + '"></i> offline';
|
|
return div;
|
|
};
|
|
legend.addTo(map);
|
|
|
|
let infobutton = L.control({position: 'topright'});
|
|
infobutton.onAdd = function (map) {
|
|
let div = L.DomUtil.create('div');
|
|
div.innerHTML = '<button class="cb centerbutt">neu zentrieren</button>';
|
|
div.onclick = function () {
|
|
dialogCenter.dialog('open');
|
|
console.log('Clicked on Zentrieren');
|
|
}
|
|
return div;
|
|
}
|
|
infobutton.addTo(map);
|
|
|
|
if (useStgtBorder) {
|
|
fetchStuttgartBounds();
|
|
}
|
|
|
|
if (myLatLng.error != undefined) {
|
|
showError(3, "Kein Lärmsensor", cid);
|
|
} else {
|
|
await buildMarkers(bounds);
|
|
map.on('popupopen', function () {
|
|
$('.speciallink').click(function (x) {
|
|
showGrafik(clickedSensor);
|
|
});
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// With all Markers in cluster (markers) calculate the median
|
|
// of the values. With this median fetch the color and return it.
|
|
// If there are 'offline' sensors (value == -1) strip then before
|
|
// calculating the median. If there are only offline sensor, return
|
|
// color of value==-1 (dark gray).
|
|
function getMedian(markers) {
|
|
markers.sort(function (a, b) { // first sort, the lowest first
|
|
let y1 = a.options.value;
|
|
let y2 = b.options.value;
|
|
if (y1 < y2) {
|
|
return -1;
|
|
}
|
|
if (y2 < y1) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
});
|
|
console.log(markers);
|
|
let i = 0; // now find the 'offlines' (value == -1)
|
|
for (i = 0; i < markers.length; i++) {
|
|
if (markers[i].options.value != -1) {
|
|
break;
|
|
}
|
|
}
|
|
markers.splice(0, i); // remove these from array
|
|
let lang = markers.length;
|
|
if (lang > 1) { //
|
|
if ((lang % 2) == 1) { // uneven ->
|
|
return getColor(markers[(Math.floor(lang / 2))].options.value); // median is in the middle
|
|
} else { // evaen ->
|
|
lang = lang / 2; // median is mean of both middle values
|
|
console.log(lang);
|
|
let wert = (markers[lang - 1].options.value +
|
|
markers[lang].options.value) / 2;
|
|
return getColor(wert);
|
|
}
|
|
} else if (lang == 1) { // only one marker -> return its color
|
|
return getColor(markers[0].options.value);
|
|
}
|
|
return getColor(-1); // only offlines
|
|
}
|
|
|
|
|
|
function buildIcon(color, n, indoor) {
|
|
let x = 100;
|
|
if (n < 10) {
|
|
x = 200;
|
|
} else if (n < 100) {
|
|
x = 150;
|
|
}
|
|
let txtColor = "black";
|
|
if (color == colorscale[5]) {
|
|
txtColor = "white"
|
|
}
|
|
let circIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600">' +
|
|
'<circle cx="300" cy="300" r="250" fill="' + color + '" stroke="black" stroke-width="20" />';
|
|
if (n !== 0) {
|
|
circIcon +=
|
|
'<text id="marker_text" x="' + x + '" ' +
|
|
'y="400" font-size="1500%" font-family="Verdana,Lucida Sans Unicode,sans-serif" ' +
|
|
'fill="' + txtColor + '">' + n + '</text>';
|
|
}
|
|
if(indoor !== 0) {
|
|
circIcon += '<line x1="125" y1="475" x2="475" y2="125" stroke="black" stroke-width="20" />'
|
|
}
|
|
circIcon += '</svg>';
|
|
let circIconUrl = encodeURI("data:image/svg+xml," + circIcon).replace(new RegExp('#', 'g'), '%23');
|
|
return circIconUrl;
|
|
}
|
|
|
|
|
|
export async function buildMarkers() {
|
|
// debug_log('buildMarkers(bounds)');
|
|
const box = {
|
|
west: bounds.getWest(), south: bounds.getSouth(),
|
|
east: bounds.getEast(), north: bounds.getNorth()
|
|
}
|
|
const url = `http://localhost:3004/getmapdata?type=noise&box=${bounds.toBBoxString()}`
|
|
|
|
let ret = await fetch(url)
|
|
.catch(e => {
|
|
console.log(e)
|
|
});
|
|
let sensors = await ret.json()
|
|
let markers = L.markerClusterGroup({
|
|
spiderfyOnMaxZoom: true,
|
|
showCoverageOnHover: false,
|
|
zoomToBoundsOnClick: true,
|
|
// disableClusteringAtZoom: 14,
|
|
iconCreateFunction: function (cluster) {
|
|
let mymarkers = cluster.getAllChildMarkers();
|
|
let color = getMedian(mymarkers);
|
|
return new L.Icon({
|
|
iconUrl: buildIcon(color, cluster.getChildCount(), 0),
|
|
iconSize: [35, 35]
|
|
});
|
|
},
|
|
});
|
|
|
|
for (let x of sensors.values) {
|
|
let marker = L.marker([x.location[1], x.location[0]], {
|
|
icon: new L.Icon({
|
|
iconUrl: buildIcon(getColor(parseInt(x.value)),0,x.indoor),
|
|
iconSize: [35, 35],
|
|
}),
|
|
name: x.id,
|
|
value: x.value,
|
|
url: '/graph?sid=' + x.id,
|
|
lastseen: dt.formatJSDate(x.lastseen),
|
|
indoor: x.indoor
|
|
})
|
|
.on('click', e => onMarkerClick(e, true)) // define click- and
|
|
.bindPopup(popuptext); // and bint the popup text
|
|
markers.addLayer(marker);
|
|
}
|
|
map.addLayer(markers);
|
|
showLastDate(sensors.lastDate);
|
|
|
|
}
|
|
|
|
async function onMarkerClick(e, click) {
|
|
let item = e.target.options;
|
|
let clickedSensor = item.name;
|
|
|
|
let offlinetext = `
|
|
<tr><td colspan="2"><span style="color:red;">offline</span></td></tr>
|
|
<tr><td>Last seen:</td><td>${item.lastseen}</td></tr>`
|
|
let normaltext = `
|
|
<tr></tr><tr><td>LA_max:</td><td>${item.value}</td></tr>`
|
|
|
|
let popuptext = `
|
|
<div id="infoTitle">
|
|
<h4>Sensor: ${item.name}</h4>
|
|
<div id="infoTable">
|
|
<table>
|
|
<tr><td colspan="2"><span style="color:limegreen;">${item.indoor==1 ? "indoor" : ""}</span></td></tr>
|
|
${item.value < 0 ? offlinetext : normaltext}
|
|
</table>
|
|
<div id="infoBtn">
|
|
<a href="#" class="speciallink">Grafik anzeigen</a>
|
|
</div>
|
|
</div>
|
|
</div>`
|
|
let popup = e.target.getPopup();
|
|
popup.setContent(popuptext); // set text into popup
|
|
e.target.openPopup(); // show the popup
|
|
if (click == true) { // if we clicked
|
|
e.target.closePopup(); // show the popup
|
|
}
|
|
}
|
|
|
|
async function getCoords(city) {
|
|
let url = NOMINATIM_URL + city;
|
|
const response = await axios.get(encodeURI(url));
|
|
const data = response.data;
|
|
return data[0];
|
|
}
|
|
|
|
// Map auf Stadt setzen
|
|
async function setCenter(adr) {
|
|
let data = await getCoords(adr);
|
|
map.setView([parseFloat(data.lat), parseFloat(data.lon)]);
|
|
console.log(data);
|
|
}
|