WIP WIP WIP ** first git - markers work as expected
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
log
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JavaScriptLibraryMappings">
|
||||||
|
<includedPredefinedLibrary name="Node.js Core" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="MarkdownSettingsMigration">
|
||||||
|
<option name="stateVersion" value="1" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/laerm_web_sensorapi.iml" filepath="$PROJECT_DIR$/.idea/laerm_web_sensorapi.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+19
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="MessDetectorOptionsConfiguration">
|
||||||
|
<option name="transferred" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PHPCSFixerOptionsConfiguration">
|
||||||
|
<option name="transferred" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PHPCodeSnifferOptionsConfiguration">
|
||||||
|
<option name="highlightLevel" value="WARNING" />
|
||||||
|
<option name="transferred" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PhpStanOptionsConfiguration">
|
||||||
|
<option name="transferred" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PsalmOptionsConfiguration">
|
||||||
|
<option name="transferred" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+11
@@ -0,0 +1,11 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="bin/www" type="NodeJSConfigurationType" node-parameters="--experimental-json-modules" path-to-js-file="bin/www.js" working-dir="$PROJECT_DIR$">
|
||||||
|
<envs>
|
||||||
|
<env name="DEBUG" value="laerm-web-sensorapi:*" />
|
||||||
|
</envs>
|
||||||
|
<EXTENSION ID="com.jetbrains.nodejs.run.NodeStartBrowserRunConfigurationExtension">
|
||||||
|
<browser url="http://localhost:3000/" />
|
||||||
|
</EXTENSION>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
Generated
+25
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectTasksOptions">
|
||||||
|
<TaskOptions isEnabled="true">
|
||||||
|
<option name="arguments" value="$FileName$:$FileNameWithoutExtension$.css" />
|
||||||
|
<option name="checkSyntaxErrors" value="true" />
|
||||||
|
<option name="description" />
|
||||||
|
<option name="exitCodeBehavior" value="ERROR" />
|
||||||
|
<option name="fileExtension" value="sass" />
|
||||||
|
<option name="immediateSync" value="false" />
|
||||||
|
<option name="name" value="Sass" />
|
||||||
|
<option name="output" value="$FileNameWithoutExtension$.css:$FileNameWithoutExtension$.css.map" />
|
||||||
|
<option name="outputFilters">
|
||||||
|
<array />
|
||||||
|
</option>
|
||||||
|
<option name="outputFromStdout" value="false" />
|
||||||
|
<option name="program" value="$PROJECT_DIR$/node_modules/sass/sass.js" />
|
||||||
|
<option name="runOnExternalChanges" value="false" />
|
||||||
|
<option name="scopeName" value="Project Files" />
|
||||||
|
<option name="trackOnlyRoot" value="true" />
|
||||||
|
<option name="workingDir" value="$FileDir$" />
|
||||||
|
<envs />
|
||||||
|
</TaskOptions>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import createError from 'http-errors'
|
||||||
|
import logger from 'morgan'
|
||||||
|
import express from 'express'
|
||||||
|
import cookieParser from 'cookie-parser'
|
||||||
|
import path from 'path'
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
const app = express()
|
||||||
|
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
import indexRouter from './routes/index.js'
|
||||||
|
// import getdataRouter from './routes/getdata.js'
|
||||||
|
// import putdataRouter from './routes/putdata.js'
|
||||||
|
|
||||||
|
// view engine setup
|
||||||
|
app.set('views', path.join(__dirname, 'views'));
|
||||||
|
app.set('view engine', 'pug');
|
||||||
|
|
||||||
|
app.use(logger('dev'));
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(express.urlencoded({ extended: false }));
|
||||||
|
app.use(cookieParser());
|
||||||
|
app.use(express.static(path.join(__dirname, 'public')));
|
||||||
|
|
||||||
|
app.use('/', indexRouter);
|
||||||
|
// app.use('/users', usersRouter);
|
||||||
|
|
||||||
|
// catch 404 and forward to error handler
|
||||||
|
app.use(function(req, res, next) {
|
||||||
|
next(createError(404));
|
||||||
|
});
|
||||||
|
|
||||||
|
// error handler
|
||||||
|
app.use(function(err, req, res, next) {
|
||||||
|
// set locals, only providing error in development
|
||||||
|
res.locals.message = err.message;
|
||||||
|
res.locals.error = req.app.get('env') === 'development' ? err : {};
|
||||||
|
|
||||||
|
// render the error page
|
||||||
|
res.status(err.status || 500);
|
||||||
|
res.render('error');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export default app
|
||||||
Executable
+97
@@ -0,0 +1,97 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import app from '../app.js'
|
||||||
|
import Debug from 'debug'
|
||||||
|
import http from 'http'
|
||||||
|
const debug = Debug('laerm-web-sensorapi:server')
|
||||||
|
|
||||||
|
|
||||||
|
/* var app = require('../app');
|
||||||
|
var debug = require('debug')('laerm-web-sensorapi:server');
|
||||||
|
var http = require('http');
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get port from environment and store in Express.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var port = normalizePort(process.env.PORT || '3000');
|
||||||
|
app.set('port', port);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create HTTP server.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var server = http.createServer(app);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen on provided port, on all network interfaces.
|
||||||
|
*/
|
||||||
|
|
||||||
|
server.listen(port);
|
||||||
|
server.on('error', onError);
|
||||||
|
server.on('listening', onListening);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a port into a number, string, or false.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function normalizePort(val) {
|
||||||
|
var port = parseInt(val, 10);
|
||||||
|
|
||||||
|
if (isNaN(port)) {
|
||||||
|
// named pipe
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (port >= 0) {
|
||||||
|
// port number
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event listener for HTTP server "error" event.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onError(error) {
|
||||||
|
if (error.syscall !== 'listen') {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bind = typeof port === 'string'
|
||||||
|
? 'Pipe ' + port
|
||||||
|
: 'Port ' + port;
|
||||||
|
|
||||||
|
// handle specific listen errors with friendly messages
|
||||||
|
switch (error.code) {
|
||||||
|
case 'EACCES':
|
||||||
|
console.error(bind + ' requires elevated privileges');
|
||||||
|
process.exit(1);
|
||||||
|
break;
|
||||||
|
case 'EADDRINUSE':
|
||||||
|
console.error(bind + ' is already in use');
|
||||||
|
process.exit(1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event listener for HTTP server "listening" event.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onListening() {
|
||||||
|
var addr = server.address();
|
||||||
|
var bind = typeof addr === 'string'
|
||||||
|
? 'pipe ' + addr
|
||||||
|
: 'port ' + addr.port;
|
||||||
|
debug('Listening on ' + bind);
|
||||||
|
}
|
||||||
Generated
+1394
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "laerm-web-sensorapi",
|
||||||
|
"version": "3.0.0",
|
||||||
|
"date": "2023-03-14 16:30 UTC",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"start": "node bin/www.js >>log/laerm_web.log 2>&1"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"bin": {
|
||||||
|
"www": "./bin/www.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cookie-parser": "~1.4.4",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"debug": "~2.6.9",
|
||||||
|
"express": "~4.16.1",
|
||||||
|
"http-errors": "~1.6.3",
|
||||||
|
"morgan": "~1.9.1",
|
||||||
|
"pug": "2.0.0-beta11",
|
||||||
|
"sass": "^1.58.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// all date and time functions
|
||||||
|
import { DateTime } from './luxon.min.js'
|
||||||
|
|
||||||
|
// Show date and time (every minute)
|
||||||
|
export const showDate = (sofort) => {
|
||||||
|
let d = DateTime.now()
|
||||||
|
if(sofort || d.second === 0) {
|
||||||
|
$('#h1datum').text(d.toFormat('yyyy-MM-dd HH:mm'))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const formatJSDate = (d) => {
|
||||||
|
return DateTime.fromJSDate(d).toFormat('yyyy-LL-dd HH:mm:ss')
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// Client side javascript
|
||||||
|
"use strict"
|
||||||
|
|
||||||
|
import * as map from './map.js'
|
||||||
|
import * as dt from './datetime.js'
|
||||||
|
|
||||||
|
$(async() => {
|
||||||
|
// global constants
|
||||||
|
const Stuttgart = [48.779, 9.16]
|
||||||
|
const MAXROWS_IN_TABLE = 1000
|
||||||
|
const defaultStartime = '2023-01-01 00:00'
|
||||||
|
// END global constants
|
||||||
|
|
||||||
|
// global variables
|
||||||
|
let params = { // set defaults
|
||||||
|
drawlines: false,
|
||||||
|
nbrentries: 0,
|
||||||
|
device: '',
|
||||||
|
coordinates: false,
|
||||||
|
center: Stuttgart,
|
||||||
|
zoom: 13,
|
||||||
|
refresh: 0,
|
||||||
|
starttime: '2023-01-01T00:00:00Z', //date2ISO(defaultStartime),
|
||||||
|
endtime: '2023-03-01T00:00:00Z' //date2ISO(emptyTimes.emptyHMtime)
|
||||||
|
}
|
||||||
|
// END global variables
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
// show dat/time at start
|
||||||
|
dt.showDate(true)
|
||||||
|
// and show date/time every minute
|
||||||
|
setInterval(() => dt.showDate(false), 1000)
|
||||||
|
|
||||||
|
// show version and version-date
|
||||||
|
$('#versn').text(`Version: ${sysparams.version} vom ${sysparams.date.slice(0,10)}`)
|
||||||
|
|
||||||
|
// show the map
|
||||||
|
map.showMap(params)
|
||||||
|
await map.buildMarkers()
|
||||||
|
}
|
||||||
|
main().catch(console.error)
|
||||||
|
|
||||||
|
})
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,300 @@
|
|||||||
|
// 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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
.marker-cluster-small {
|
||||||
|
background-color: rgba(181, 226, 140, 0.6);
|
||||||
|
}
|
||||||
|
.marker-cluster-small div {
|
||||||
|
background-color: rgba(110, 204, 57, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker-cluster-medium {
|
||||||
|
background-color: rgba(241, 211, 87, 0.6);
|
||||||
|
}
|
||||||
|
.marker-cluster-medium div {
|
||||||
|
background-color: rgba(240, 194, 12, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker-cluster-large {
|
||||||
|
background-color: rgba(253, 156, 115, 0.6);
|
||||||
|
}
|
||||||
|
.marker-cluster-large div {
|
||||||
|
background-color: rgba(241, 128, 23, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* IE 6-8 fallback colors */
|
||||||
|
.leaflet-oldie .marker-cluster-small {
|
||||||
|
background-color: rgb(181, 226, 140);
|
||||||
|
}
|
||||||
|
.leaflet-oldie .marker-cluster-small div {
|
||||||
|
background-color: rgb(110, 204, 57);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-oldie .marker-cluster-medium {
|
||||||
|
background-color: rgb(241, 211, 87);
|
||||||
|
}
|
||||||
|
.leaflet-oldie .marker-cluster-medium div {
|
||||||
|
background-color: rgb(240, 194, 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-oldie .marker-cluster-large {
|
||||||
|
background-color: rgb(253, 156, 115);
|
||||||
|
}
|
||||||
|
.leaflet-oldie .marker-cluster-large div {
|
||||||
|
background-color: rgb(241, 128, 23);
|
||||||
|
}
|
||||||
|
|
||||||
|
.marker-cluster {
|
||||||
|
background-clip: padding-box;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
.marker-cluster div {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-top: 5px;
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 15px;
|
||||||
|
font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
.marker-cluster span {
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
.leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow {
|
||||||
|
-webkit-transition: -webkit-transform 0.3s ease-out, opacity 0.3s ease-in;
|
||||||
|
-moz-transition: -moz-transform 0.3s ease-out, opacity 0.3s ease-in;
|
||||||
|
-o-transition: -o-transform 0.3s ease-out, opacity 0.3s ease-in;
|
||||||
|
transition: transform 0.3s ease-out, opacity 0.3s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-cluster-spider-leg {
|
||||||
|
/* stroke-dashoffset (duration and function) should match with leaflet-marker-icon transform in order to track it exactly */
|
||||||
|
-webkit-transition: -webkit-stroke-dashoffset 0.3s ease-out, -webkit-stroke-opacity 0.3s ease-in;
|
||||||
|
-moz-transition: -moz-stroke-dashoffset 0.3s ease-out, -moz-stroke-opacity 0.3s ease-in;
|
||||||
|
-o-transition: -o-stroke-dashoffset 0.3s ease-out, -o-stroke-opacity 0.3s ease-in;
|
||||||
|
transition: stroke-dashoffset 0.3s ease-out, stroke-opacity 0.3s ease-in;
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
body {
|
||||||
|
font-family: Verdana, "Lucida Sans Unicode", sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
width: 100%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgb(146, 154, 146);
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#wrapper {
|
||||||
|
margin: auto;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
background-color: #e9e9e9;
|
||||||
|
margin-top: 5px;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #0000EE;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
width: 98%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
header #hline1 {
|
||||||
|
width: 98%;
|
||||||
|
margin: 0px auto 0px;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
header #h1name {
|
||||||
|
font-size: 148%;
|
||||||
|
clear: both;
|
||||||
|
float: left;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
header #h1datum {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#map {
|
||||||
|
margin-left: 1vw;
|
||||||
|
margin-top: 5px;
|
||||||
|
width: 98vw;
|
||||||
|
height: 66vh;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: solid 1px seagreen;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#author {
|
||||||
|
font-size: 80%;
|
||||||
|
width: 95%;
|
||||||
|
margin: auto;
|
||||||
|
padding-bottom: 30px;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
#author #mailadr {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
#author #versn {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
padding: 6px 8px;
|
||||||
|
font: 12px Arial, Helvetica, sans-serif;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info h4 {
|
||||||
|
margin: 0 0 5px;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
line-height: 18px;
|
||||||
|
color: #555;
|
||||||
|
font-size: 110%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend i {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
float: left;
|
||||||
|
margin-right: 0px;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leglabel {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ndmarker {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*# sourceMappingURL=style.css.map */
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sourceRoot":"","sources":["style.sass"],"names":[],"mappings":"AAUA;EACE;EACA;EACA;EACA;EACA;EACA;EACA,kBAfW;;;AAiBb;EACE;;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAEF;EACE;;;AAEF;EACE;EACA;;AAEA;EACE;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;;AAEF;EACE;;;AAGJ;EACE;EACA;EACA;EACA;EACA,eA1Dc;EA2Dd;EACA;EACA;;;AAEF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEF;EACE;;;AAGJ;EACE;EACA;EACA;EACA;EACA;;;AAEF;EACE;EACA;;;AAEF;EACE;EACA;EACA;;;AAEF;EACE;EACA;EACA;EACA;EACA;;;AAEF;EACE;;;AAEF;EACE;EACA","file":"style.css"}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
$corner-radius: 10px
|
||||||
|
$border-color: seagreen
|
||||||
|
$background: rgb(146, 154, 146)
|
||||||
|
$divcolor: rgb(162, 227, 162)
|
||||||
|
$table-stripes: rgb(157, 179, 157)
|
||||||
|
$fieldset-color: rgb(208, 235, 208)
|
||||||
|
$button-color: darkslategrey
|
||||||
|
$checkboxsize: 1.5
|
||||||
|
$inboxwidth: 70px
|
||||||
|
|
||||||
|
body
|
||||||
|
font-family: Verdana, 'Lucida Sans Unicode', sans-serif
|
||||||
|
font-size: 16px
|
||||||
|
width: 100%
|
||||||
|
margin-left: auto
|
||||||
|
margin-right: auto
|
||||||
|
height: 100%
|
||||||
|
background-color: $background
|
||||||
|
|
||||||
|
html
|
||||||
|
height: 100%
|
||||||
|
|
||||||
|
#wrapper
|
||||||
|
margin: auto
|
||||||
|
width: 100%
|
||||||
|
text-align: center
|
||||||
|
background-color: #e9e9e9
|
||||||
|
margin-top: 5px
|
||||||
|
height: 100%
|
||||||
|
position: relative
|
||||||
|
|
||||||
|
a
|
||||||
|
color: #0000EE
|
||||||
|
|
||||||
|
header
|
||||||
|
width: 98%
|
||||||
|
margin: auto
|
||||||
|
|
||||||
|
#hline1
|
||||||
|
width: 98%
|
||||||
|
margin: 0px auto 0px
|
||||||
|
padding-top: 10px
|
||||||
|
|
||||||
|
#h1name
|
||||||
|
font-size: 148%
|
||||||
|
clear: both
|
||||||
|
float: left
|
||||||
|
text-align: left
|
||||||
|
|
||||||
|
#h1datum
|
||||||
|
float: right
|
||||||
|
|
||||||
|
|
||||||
|
#map
|
||||||
|
margin-left: 1vw
|
||||||
|
margin-top: 5px
|
||||||
|
width: 98vw
|
||||||
|
height: 66vh
|
||||||
|
border-radius: $corner-radius
|
||||||
|
border: solid 1px $border-color
|
||||||
|
overflow: hidden
|
||||||
|
z-index: 1
|
||||||
|
|
||||||
|
#author
|
||||||
|
font-size: 80%
|
||||||
|
width: 95%
|
||||||
|
margin: auto
|
||||||
|
padding-bottom: 30px
|
||||||
|
padding-top: 10px
|
||||||
|
|
||||||
|
#mailadr
|
||||||
|
float: left
|
||||||
|
|
||||||
|
#versn
|
||||||
|
float: right
|
||||||
|
|
||||||
|
|
||||||
|
.info
|
||||||
|
padding: 6px 8px
|
||||||
|
font: 12px Arial, Helvetica, sans-serif
|
||||||
|
background: rgba(255,255,255,0.8)
|
||||||
|
box-shadow: 0 0 15px rgba(0,0,0,0.2)
|
||||||
|
border-radius: 5px
|
||||||
|
|
||||||
|
.info h4
|
||||||
|
margin: 0 0 5px
|
||||||
|
color: #777
|
||||||
|
|
||||||
|
.legend
|
||||||
|
line-height: 18px
|
||||||
|
color: #555
|
||||||
|
font-size: 110%
|
||||||
|
|
||||||
|
.legend i
|
||||||
|
width: 18px
|
||||||
|
height: 18px
|
||||||
|
float: left
|
||||||
|
margin-right: 0px
|
||||||
|
opacity: 1
|
||||||
|
|
||||||
|
.leglabel
|
||||||
|
padding-top: 20px
|
||||||
|
|
||||||
|
.ndmarker
|
||||||
|
position: absolute
|
||||||
|
font-size: 24px
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import express from 'express'
|
||||||
|
import pkg from '../package.json' assert { type: "json" }
|
||||||
|
|
||||||
|
const router = express.Router()
|
||||||
|
const { name, version, date } = pkg
|
||||||
|
|
||||||
|
/* GET home page. */
|
||||||
|
router.get('/', function(req, res, next) {
|
||||||
|
res.render('index', {
|
||||||
|
title: name,
|
||||||
|
version: version,
|
||||||
|
date: date
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
var express = require('express');
|
||||||
|
var router = express.Router();
|
||||||
|
|
||||||
|
/* GET users listing. */
|
||||||
|
router.get('/', function(req, res, next) {
|
||||||
|
res.send('respond with a resource');
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
extends layout
|
||||||
|
|
||||||
|
block content
|
||||||
|
h1= message
|
||||||
|
h2= error.status
|
||||||
|
pre #{error.stack}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
extends layout
|
||||||
|
|
||||||
|
block content
|
||||||
|
header
|
||||||
|
#hline1
|
||||||
|
#h1name Lärm-Messung
|
||||||
|
#h1datum
|
||||||
|
|
||||||
|
|
||||||
|
#map
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
doctype html
|
||||||
|
html
|
||||||
|
head
|
||||||
|
title= title
|
||||||
|
meta(name="viewport" content="width=device-width, initial-scale=1" charset="utf-8")
|
||||||
|
link(href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"
|
||||||
|
rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65"
|
||||||
|
crossorigin="anonymous")
|
||||||
|
link(rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css"
|
||||||
|
integrity="sha256-kLaT2GOSpHechhsozzB+flnD+zUyjE2LlfWPgU04xyI="
|
||||||
|
crossorigin="")
|
||||||
|
link(rel='stylesheet', href='/stylesheets/MarkerCluster.css')
|
||||||
|
link(rel='stylesheet', href='/stylesheets/style.css')
|
||||||
|
|
||||||
|
script.
|
||||||
|
let sysparams = {
|
||||||
|
version: '#{version}',
|
||||||
|
date: '#{date}'
|
||||||
|
}
|
||||||
|
let urlParams = {
|
||||||
|
// drawlines: '#{drawlines}' === 'true',
|
||||||
|
// starttime: '#{starttime}',
|
||||||
|
// endtime: '#{endtime}',
|
||||||
|
// nbrentries: '#{nbrentries}',
|
||||||
|
// device: '#{device}',
|
||||||
|
// coordinates: '#{coordinates}' === 'true',
|
||||||
|
// center: JSON.parse('#{center}'),
|
||||||
|
// zoom: '#{zoom}',
|
||||||
|
// refresh: '#{refresh}'
|
||||||
|
}
|
||||||
|
|
||||||
|
body
|
||||||
|
#wrapper
|
||||||
|
block content
|
||||||
|
#author
|
||||||
|
#mailadr
|
||||||
|
a(href="mailto:rexfue@gmail.com") mailto:rexfue@gmail.com
|
||||||
|
#versn
|
||||||
|
script(src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"
|
||||||
|
integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4"
|
||||||
|
crossorigin="anonymous")
|
||||||
|
script(src="https://code.jquery.com/jquery-3.6.4.min.js"
|
||||||
|
integrity="sha256-oP6HI9z1XaZNBrJURtCoUT5SUnxFr8s3BzRl+cbzUq8=" crossorigin="anonymous")
|
||||||
|
script(src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"
|
||||||
|
integrity="sha256-WBkoXOwTeyKclOHuWtc+i2uENFpDZ9YPdf5Hf+D7ewM="
|
||||||
|
crossorigin="")
|
||||||
|
script(src="/javascripts/leaflet.markercluster.js")
|
||||||
|
script(type="module" src="/javascripts/global.js")
|
||||||
Reference in New Issue
Block a user