Files
zjui-ece445/webpage/index.htm

353 lines
14 KiB
HTML
Executable File

<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Air Quality Visualization</title>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="resources/bootstrap.min.css">
<link rel="stylesheet" href="resources/leaflet.css">
<script src="resources/jquery-3.5.1.min.js"></script>
<script src="resources/bootstrap.bundle.min.js"></script>
<script src="resources/leaflet.js"></script>
<style type="text/css">
body {
padding: 0;
margin: 0;
}
html, body, #map {
height: 100%;
}
body {
overflow: hidden;
}
.leaflet-marker-icon{
background:none;
color:#fff;
white-space:nowrap;
padding:2px;
border:none;
}
.name{
margin:-12px 0 0 10px;
background-color:rgba(0,17,35,0.5);
border-radius:5px;
padding:3px 5px;
}
.name a{
color:#fff !important;
text-decoration:none;
}
.leaflet-popup-content td{
vertical-align:top;
}
.image-overlay {
image-rendering: crisp-edges;
}
#forecast-slider-wrapper {
flex-grow: 1;
margin-left: 5px;
display: flex;
vertical-align: middle;
}
#forecast-slider {
width: 100%;
}
#preload {
z-index: -1;
position: fixed;
}
#windrose {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 1000;
background-color: rgba(255, 255, 255, 0.8);
}
#colorbar {
display: flex;
position: fixed;
left: 0;
width: 50px;
top: 0;
bottom: 0;
margin-top: 56px;
z-index: 1000;
flex-direction: column;
font-size: 12px;
}
#colorbar div {
flex-grow: 1;
color: #fff;
text-align:center;
overflow: hidden;
line-height: 1;
display:flex;
align-items: center;
}
#colorbar div span {
width: 100%;
text-align: center;
}
#map {
margin-left: 50px;
margin-top: 56px;
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light fixed-top">
<a class="navbar-brand" href="#">Air Quality Visualization</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Metrics
</a>
<div class="dropdown-menu" id="metric">
</div>
</li>
<li class="nav-item">
<a class="nav-link" href="#" id="windrose-link">Windrose</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#">|</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Forecast</a>
</li>
</ul>
<div id="forecast-slider-wrapper">
<input type="range" min="0" max="1000" value="0" id="forecast-slider">
</div>
<ul class="navbar-nav mr-auto float-right">
<li class="nav-item">
<a class="nav-link disabled" href="#">|</a>
</li>
<li class="nav-item">
<a class="nav-link" href="results/all.csv" target="_blank">Download Data</a>
</li>
</ul>
</div>
</nav>
<div id="map"></div>
<div id="windrose" class="d-none">
<img src="results/windrose.png"/>
</div>
<div id="colorbar">
<div id="colorbar-0" style="background-color: #ff2900"></div>
<div id="colorbar-1" style="background-color: #ff8200"></div>
<div id="colorbar-2" style="background-color: #ffd700"></div>
<div id="colorbar-3" style="background-color: #c7ff30"></div>
<div id="colorbar-4" style="background-color: #7aff7d"></div>
<div id="colorbar-5" style="background-color: #30ffc7"></div>
<div id="colorbar-6" style="background-color: #00c5ff"></div>
<div id="colorbar-7" style="background-color: #0069ff"></div>
<div id="colorbar-8" style="background-color: #0009ff"></div>
<div id="colorbar-9" style="background-color: #0000b2"></div>
</div>
<div id="preload"></div>
<script>
"use strict";
window.onload = function() {
let map = L.map("map", {
center: [30.5186465, 120.7249815],
zoom: 16,
zoomControl: false,
minZoom: 15,
maxZoom: 20,
maxBounds: [[30.48,120.68],[30.55,120.77]],
});
let mapLayers = {
'Amap / Streets':L.tileLayer('//webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}', {
maxZoom: 20,
maxNativeZoom: 18,
minZoom: 3,
attribution: "Amap.cn",
subdomains: "1234"
}).addTo(map),
'GeoQ / Streets':L.tileLayer('//map.geoq.cn/ArcGIS/rest/services/ChinaOnlineStreetPurplishBlue/MapServer/tile/{z}/{y}/{x}', {
maxZoom: 20,
maxNativeZoom: 16,
minZoom: 3,
attribution: "GeoQ.cn"
})
}
L.control.layers(mapLayers, {}, {
position: 'topright',
collapsed: true
}).addTo(map);
L.control.zoom({
zoomInTitle: 'Zoom In',
zoomOutTitle: 'Zoom Out'
}).addTo(map);
// map.on('click', function(e) {
// console.log(e.latlng);
// });
let setOverlay = function(filename) {
if(setOverlay._lastLayer) {
if(setOverlay._lastLayer._url == filename) return;
map.removeLayer(setOverlay._lastLayer);
}
setOverlay._lastLayer = L.imageOverlay(filename, [
[30.512, 120.719],
[30.525, 120.735]
], {
'opacity': 0.5,
'className': 'image-overlay'
}).addTo(map);
};
let selectionUpdate = function() {
let progressImages = 11;
// let metric = document.getElementById("metric").value;
let metric = document.dataType
if(metric != selectionUpdate.metric) {
// User selected new metric
// Regenerate preloaders
document.getElementById('preload').innerHTML = '';
for(let i = 0; i < progressImages; i++) {
let element = document.createElement('img');
element.src = 'results/' + metric + '_' + i + '.png';
document.getElementById('preload').appendChild(element);
}
// Reload colorbar
$.getJSON('results/' + metric + '.json', function(data) {
console.log(data);
document.getElementById("colorbar-0").innerHTML = '<span>' + data[0].toFixed(4) + '<br/>~<br/>' + data[1].toFixed(4) + '</span>';
document.getElementById("colorbar-1").innerHTML = '<span>' + data[1].toFixed(4) + '<br/>~<br/>' + data[2].toFixed(4) + '</span>';
document.getElementById("colorbar-2").innerHTML = '<span>' + data[2].toFixed(4) + '<br/>~<br/>' + data[3].toFixed(4) + '</span>';
document.getElementById("colorbar-3").innerHTML = '<span>' + data[3].toFixed(4) + '<br/>~<br/>' + data[4].toFixed(4) + '</span>';
document.getElementById("colorbar-4").innerHTML = '<span>' + data[4].toFixed(4) + '<br/>~<br/>' + data[5].toFixed(4) + '</span>';
document.getElementById("colorbar-5").innerHTML = '<span>' + data[5].toFixed(4) + '<br/>~<br/>' + data[6].toFixed(4) + '</span>';
document.getElementById("colorbar-6").innerHTML = '<span>' + data[6].toFixed(4) + '<br/>~<br/>' + data[7].toFixed(4) + '</span>';
document.getElementById("colorbar-7").innerHTML = '<span>' + data[7].toFixed(4) + '<br/>~<br/>' + data[8].toFixed(4) + '</span>';
document.getElementById("colorbar-8").innerHTML = '<span>' + data[8].toFixed(4) + '<br/>~<br/>' + data[9].toFixed(4) + '</span>';
document.getElementById("colorbar-9").innerHTML = '<span>' + data[9].toFixed(4) + '<br/>~<br/>' + data[10].toFixed(4) + '</span>';
});
}
let slider = document.getElementById("forecast-slider");
let progress = (slider.value - slider.min) / (slider.max - slider.min) * progressImages;
if(progress > progressImages - 1) progress = progressImages - 1;
setOverlay('results/' + metric + '_' + Math.floor(progress) + '.png')
};
let metrics = {
// 'bme680_co2' : 'CO2 (BME680)',
'bme680_hum' : 'Humidity (BME680)',
// 'bme680_iaq' : 'Indoor Air Quality (BME680)',
'bme680_prs' : 'Pressure (BME680)',
'bme680_tmp' : 'Temperature (BME680)',
// 'bme680_tvoc' : 'TVOC (BME680)',
'mics_co' : 'CO (MICS6814)',
'mics_no2' : 'NO2 (MICS6814)',
'pms5003_pm1' : 'PM1 (PMS5003)',
'pms5003_pm2_5': 'PM2.5 (PMS5003)',
'pms5003_pm10' : 'PM10 (PMS5003)',
// 'stm32_vbat' : 'STM32 Battery Voltage',
// 'stm32_tmp' : 'STM32 Chip Temperature',
// 'stm32_vref' : 'STM32 3.3V Rail Voltage',
};
let units = {
// 'bme680_co2' : 'ppm',
'bme680_hum' : '%',
// 'bme680_iaq' : '',
'bme680_prs' : 'Pa',
'bme680_tmp' : '°C',
// 'bme680_tvoc' : 'ppm',
'mics_co' : 'mg/m3',
'mics_no2' : 'ug/m3',
'pms5003_pm1' : 'ug/m3',
'pms5003_pm2_5': 'ug/m3',
'pms5003_pm10' : 'ug/m3',
// 'stm32_vbat' : 'V',
// 'stm32_tmp' : '°C',
// 'stm32_vref' : 'V',
};
$.getJSON('results/all.json', function(data) {
L.geoJson(data, {
pointToLayer: function(feature, latlng) {
// console.log(feature);
let popup = "<div><table>";
for(let key in metrics) {
popup += "<tr><td>" + metrics[key]
+ "</td><td class='text-right'>" + parseFloat(feature.properties[key]).toFixed(4)
+ "</td><td>" + units[key] + "</td></tr>";
}
popup += "</table></div>";
L.circleMarker(latlng, {
radius: 1,
color: '#fff',
opacity: 1,
fill: true,
weight: 2,
fillColor: '#fff',
fillOpacity: 1
}).addTo(map);
L.marker(latlng, {
icon: L.divIcon({
iconSize: null,
html: '<div class="name"><a href="#" onclick="return false">'
+ feature.properties.id
+ '</a></div>'
})
}).bindPopup(popup).addTo(map);
}
});
});
let updateMetricsDropdown = function() {
let ele = document.getElementById("metric");
ele.innerHTML = '';
for(let key in metrics) {
let link = document.createElement('a');
link.href = '#';
link.text = metrics[key];
link.classList.add('dropdown-item');
if(key == document.dataType) link.classList.add('active');
link.onclick = function() { document.dataType = key; updateMetricsDropdown(); selectionUpdate(); };
ele.appendChild(link);
}
};
document.dataType = Object.keys(metrics)[0];
updateMetricsDropdown();
document.getElementById("forecast-slider").oninput = selectionUpdate;
// document.getElementById("metric").onchange = selectionUpdate;
selectionUpdate();
document.getElementById('windrose-link').onclick = function() {
$('#windrose').toggleClass('d-none');
};
}
</script>
</body>
</html>