Files
noise/public/javascripts/map.js
T
2023-03-24 13:49:54 +01:00

293 lines
9.9 KiB
JavaScript

// all function related to the map
import * as dt from './datetime.js'
import * as chart_live from "./chart_live.js";
let map = null
let bounds
let popuptext = ''
let clickedSensor = 0
const colorscale = ['#d53e4f', '#fc8d59', '#fee08b', '#e6f598', '#99d594', '#3288bd', '#808080'];
const dba = [100, 80, 60, 40, 20, 0, -999];
export const showMap = async (params) => {
if (map) return
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: '&copy; <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>' +
'&nbsp;&nbsp;' + dba[i] + (i == 0 ? '+' : '') + '<br />'
}
div_color.innerHTML += '&nbsp;<i style="background:' + getColor(dba[dba.length - 1]) + '"></i> offline'
return div
};
legend.addTo(map)
let lastdate = await buildMarkers()
showLastDate(lastdate);
map.on('popupopen', function () {
document.querySelector('.speciallink').addEventListener('click', async function (x) {
console.log(`Event clicked: ${clickedSensor}`)
document.querySelector('#navi').style.display = 'block'
params.sid = clickedSensor
let triggerEl = document.querySelector('#livetab')
bootstrap.Tab.getInstance(triggerEl).show() // Select tab by name
await chart_live.showLive(params)
})
})
}
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
});
;
}
// 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 > 0) {
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) {
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 = `/api/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) {
if (x.value <= -4) {
continue
}
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.formatISODate(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);
return sensors.lastdate
}
async function onMarkerClick(e, click) {
let item = e.target.options;
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">Show chart</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);
}
// Show the last date below tha map grafics
async function showLastDate(ld) {
// const url = `http://localhost:3004/getproperties?type=noise&box=${bounds.toBBoxString()}`
//
// let ret = await fetch(url)
// .catch(e => {
// console.log(e)
// });
// let sensors = await ret.json()
//
// let url = '/api/getdata';
// let ld = moment(dt);
// let last = ld.subtract(1,'h')
// let erg;
// let allsens
// try {
// erg = await $.getJSON(url + `?data=props&last=1900-01-01`);
// allsens = erg.count
// erg = await $.getJSON(url + `?data=props&last=${last.format('YYYY-MM-DD HH:mm')}`);
// if ((erg != undefined) && (erg.values[0].error != undefined)) {
// showError(3,erg.values[0].error,aktsensorid);
// return;
// }
// }
// catch(e) {
// console.log(e)
// }
// fetch all sensors
const url = `/api/getmapdata?type=noise`
let ret = await fetch(url)
.catch(e => {
console.log(e)
});
let sensors = await ret.json()
let actsensors = 0
for (let x of sensors.values) {
if (x.value >= 0) {
actsensors++
}
}
document.querySelector('#mapdate').innerText = 'Values from ' + dt.formatISODate(ld).slice(0,-3)
document.querySelector('#actsensors').innerText = `Active sensors: ${actsensors} (registered: ${sensors.count})`
// $('#actsensors').html(`${erg.count} aktive Sensoren (angemeldet ${allsens})`)
}