Commit 86bff050 authored by Sven Schneider's avatar Sven Schneider
Browse files

added aggregation method into appCharts.js

No related merge requests found
Showing with 770 additions and 614 deletions
+770 -614
<!DOCTYPE html>
<html lang="en">
<head>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="" />
<meta name="author" content="" />
<title>Dashboard - iCity Bosch</title>
<link href="css/styles.css" rel="stylesheet" />
<link
href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css"
rel="stylesheet"
crossorigin="anonymous"
/>
<link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" rel="stylesheet" crossorigin="anonymous" />
<!-- Font Awesome icons -->
<script
src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/js/all.min.js"
crossorigin="anonymous"
></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/js/all.min.js" crossorigin="anonymous"></script>
<!-- Axios -->
<!-- <script src="./node_modules/axios/dist/axios.min.js"></script> -->
......@@ -46,37 +37,23 @@
<!-- Cesium lib -->
<script src="https://cesium.com/downloads/cesiumjs/releases/1.48/Build/Cesium/Cesium.js"></script>
<link
href="https://cesium.com/downloads/cesiumjs/releases/1.48/Build/Cesium/Widgets/widgets.css"
rel="stylesheet"
/>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.48/Build/Cesium/Widgets/widgets.css" rel="stylesheet" />
<!-- Bootstrap dashboard template -->
<script
defer
src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
crossorigin="anonymous"
></script>
<script
defer
src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js"
crossorigin="anonymous"
></script>
<script defer src="https://code.jquery.com/jquery-3.5.1.slim.min.js" crossorigin="anonymous"></script>
<script defer src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<script defer src="js/thirdparty/scripts.js"></script>
<!--
Custom JS -->
<script defer type="module" src="js/appCesium.js"></script>
<script defer type="module" src="js/appChart.js"></script>
</head>
<body class="sb-nav-fixed">
</head>
<body class="sb-nav-fixed">
<nav class="sb-topnav navbar navbar-expand navbar-dark bg-dark">
<a class="navbar-brand" href="index.html">iCity Bosch Dashboard</a>
<button
class="btn btn-link btn-sm order-1 order-lg-0"
id="sidebarToggle"
href="#"
>
<button class="btn btn-link btn-sm order-1 order-lg-0" id="sidebarToggle" href="#">
<i class="fas fa-bars"></i>
</button>
</nav>
......@@ -108,15 +85,11 @@
<div class="col-xl-12">
<div class="card mb-4">
<div class="card-header">
<i class="fas fa-globe mr-1"></i>
3D Visualization
<i class="fas fa-globe mr-1"></i> 3D Visualization
</div>
<div class="card-body">
<div
id="cesiumGlobeContainer"
width="100%"
height="40"
></div>
<div id="cesiumGlobeContainer" width="100%" height="40">
</div>
</div>
</div>
</div>
......@@ -125,8 +98,7 @@
<div class="col-xl-6">
<div class="card mb-4">
<div class="card-header">
<i class="fas fa-chart-line mr-1"></i>
Line Chart Example
<i class="fas fa-chart-line mr-1"></i> Line Chart Example
</div>
<div class="card-body">
<div id="chart-line" width="100%" height="40"></div>
......@@ -136,8 +108,7 @@
<div class="col-xl-6">
<div class="card mb-4">
<div class="card-header">
<i class="fas fa-chart-area mr-1"></i>
Area Chart Example
<i class="fas fa-chart-area mr-1"></i> Area Chart Example
</div>
<div class="card-body">
<div id="chart-heatmap" width="100%" height="40"></div>
......@@ -149,13 +120,10 @@
</main>
<footer class="py-4 bg-light mt-auto">
<div class="container-fluid">
<div
class="d-flex align-items-center justify-content-between small"
>
<div class="d-flex align-items-center justify-content-between small">
<div class="text-muted">Copyright &copy; HfT Stuttgart 2021</div>
<div>
<a href="#">Privacy Policy</a>
&middot;
<a href="#">Privacy Policy</a> &middot;
<a href="#">Terms &amp; Conditions</a>
</div>
</div>
......@@ -163,5 +131,6 @@
</footer>
</div>
</div>
</body>
</body>
</html>
\ No newline at end of file
......@@ -10,6 +10,7 @@ import {
formatSTAResponseForLineChart,
drawLineChartHC,
followNextLink,
aggregateResponse,
} from "./appChart.js";
// Constants
......@@ -40,23 +41,22 @@ const viewer = new Cesium.Viewer("cesiumGlobeContainer", {
* @param {String} urlTiles URL to the 3DTiles to be loaded
* @returns {undefined} undefined
*/
const loadTiles = function (urlTiles) {
const loadTiles = function(urlTiles) {
const tileset = new Cesium.Cesium3DTileset({
url: urlTiles,
});
viewer.scene.primitives.add(tileset);
tileset.readyPromise.then(function () {
tileset.readyPromise.then(function() {
viewer
.zoomTo(
tileset,
new Cesium.HeadingPitchRange(
0.0,
-0.5,
0.0, -0.5,
tileset.boundingSphere.radius / 0.5
)
)
.otherwise(function (err) {
.otherwise(function(err) {
throw err;
});
});
......@@ -67,7 +67,7 @@ const loadTiles = function (urlTiles) {
* @param{*}
* @returns {undefined} undefined
*/
const loadNonDetailed = function () {
const loadNonDetailed = function() {
// Paths to data sources
const URL_3DTILES = "data_3d/3dtiles/1_full/tileset.json";
......@@ -81,7 +81,7 @@ const loadNonDetailed = function () {
* @param {String} gltfId Name of the glTF model file without the extension i.e. exclude the `.gltf` suffix
* @returns {undefined} undefined
*/
const gltfLoad = function (gltfUrl, gltfId) {
const gltfLoad = function(gltfUrl, gltfId) {
const modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(
Cesium.Cartesian3.fromDegrees(9.083385, 48.881342, 0)
);
......@@ -101,7 +101,7 @@ const gltfLoad = function (gltfUrl, gltfId) {
* @param{*}
* @returns {undefined} undefined
*/
const loadDetailed = function () {
const loadDetailed = function() {
// Paths to data sources
const URL_3DTILES = "data_3d/3dtiles/2_partial/tileset.json";
const URL_GLTF = "data_3d/gltf";
......@@ -166,7 +166,7 @@ if (!LOAD_DETAILED_BLDG225) {
* @param {*}
* @returns {undefined}
*/
const activate3DTileFeaturePicking = function () {
const activate3DTileFeaturePicking = function() {
// HTML overlay for showing feature name on mouseover
const nameOverlay = document.createElement("div");
viewer.container.appendChild(nameOverlay);
......@@ -312,6 +312,7 @@ const activate3DTileFeaturePicking = function () {
},
});
// Get "ALL" the Observations that satisfy our query
followNextLink(axiosGetRequest)
.then((success) => {
......@@ -337,10 +338,14 @@ const activate3DTileFeaturePicking = function () {
console.log(err);
})
.then((observationArr) => {
var agg = aggregateResponse(observationArr, 0, 'mean');
console.log(agg);
drawHeatMapHC(formatSTAResponseForHeatMap(observationArr));
drawLineChartHC(formatSTAResponseForLineChart(observationArr));
});
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
};
activate3DTileFeaturePicking();
\ No newline at end of file
......@@ -13,7 +13,7 @@ export const BASE_URL = "http://193.196.39.91:8080/frost-icity-tp31/v1.1";
* @param {*} samplingRate String representing the sampling rate of the observations
* @returns {Number} Datastream corresponding to the input building
*/
export const getDatastreamIdFromBuildingNumber = function (
export const getDatastreamIdFromBuildingNumber = function(
buildingNumber,
phenomenon,
samplingRate
......@@ -86,7 +86,7 @@ export const getDatastreamIdFromBuildingNumber = function (
* @param {Number} datastreamID - Integer representing the Datastream ID
* @returns {String} URL string for fetching the Observations corresponding to a Datastream
*/
export const getObservationsUrl = function (baseUrl, datastreamID) {
export const getObservationsUrl = function(baseUrl, datastreamID) {
if (!datastreamID) return;
const fullDatastreamURL = `${baseUrl}/Datastreams(${datastreamID})/Observations`;
return fullDatastreamURL;
......@@ -98,7 +98,7 @@ export const getObservationsUrl = function (baseUrl, datastreamID) {
* @param {String} dateStop Stop date in YYYY-MM-DD format
* @returns {String} Temporal filter string
*/
export const createTemporalFilterString = function (dateStart, dateStop) {
export const createTemporalFilterString = function(dateStart, dateStop) {
if (!dateStart || !dateStop) return;
const filterString = `resultTime ge ${dateStart}T00:00:00.000Z and resultTime le ${dateStop}T00:00:00.000Z`;
return filterString;
......@@ -119,12 +119,199 @@ export const PARAM_SELECT = "result,phenomenonTime";
// },
// });
/**
*
* @param {JSON} obj JSON object on which to replace a specific key.
* @param {String} oldKey is the old key in the JSON to be renamed to newKey
* @param {String} newKey is the key that should replace the oldKey
* usage: myjson.forEach((obj) => renameKey(obj, "oldkey", "newkey"));
*/
function renameKey(obj, oldKey, newKey) {
obj[newKey] = obj[oldKey];
delete obj[oldKey];
}
/**
*
* @param {Array} arr is the Array to be converted into a JSON
* @returns {JSON} stringToJsonObject
*/
function convertArray2JSON(arr) {
var arrayToString = JSON.stringify(Object.assign({}, arr)); // convert array to string
var stringToJsonObject = JSON.parse(arrayToString); // convert string to json object
return stringToJsonObject;
}
function createDateFromDateTimeString(jsonData) {
let datx = [];
let daty = [];
const MONTH = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
for (var u = 0; u < jsonData.length; u++) {
daty.push(jsonData[u].temperature);
let date = new Date(jsonData[u].datetime);
let datum = date.getDate();
let month = MONTH[date.getMonth()];
let hour = date.getHours() + ":00";
let newDateStr = datum + "/" + month + "-" + hour;
datx.push(newDateStr);
}
return [datx, daty];
}
/**
* Format the response from SensorThings API to make it suitable for heatmap
* @param {Array} obsArray Response from SensorThings API as array
* @param {Int8} hours Number of hours to aggregate over. If hours=0,
* it will be aggregated by date (e.g. all values recorded on 1st of May etc...)
* if hours to any number (also hours=24) data will be aggregated over every 24hours,
* even if the date changes (e.g. data from 1st of May from 10pm up 9pm on 2nd of May etc.)
* @param {String} method Specify how to aggregate date. Use: 'mean' = default, 'sum', 'min' or 'max'
* @returns {Array} Aggregated Response
*/
export const aggregateResponse = function(obsArray, hours, method) {
if (!obsArray) return;
if (hours < 0) return;
// check if we have a defined method or the method specified is accepted, rest is handeled in switch/case below
if (method == undefined) method = 'mean';
// convert obsArray to json
let jsonFromArr = [];
for (var i = 0; i < obsArray.length; i++) {
jsonFromArr.push(convertArray2JSON(obsArray[i]));
}
// rename the keys in the jason
jsonFromArr.forEach((obj) => renameKey(obj, "0", "datetime"));
let jsonData = jsonFromArr;
jsonData.forEach((obj) => renameKey(obj, "1", "temperature"));
let newOutput = [];
var aggDates = [];
var aggregatedVals = [];
var vals = []; // store values temporarily to use for processing
if (hours == 0) { // i.e. aggregate over one Date / Day
var currentDate;
var oldDate;
for (var d = 0; d < jsonData.length; d++) {
let tmpDate = new Date(jsonData[d].datetime);
currentDate = tmpDate.getDate(); // gets the day of the month 1...31
if (d === 0) oldDate = currentDate;
if (currentDate == oldDate) {
vals.push(jsonData[d].temperature);
} else {
aggDates.push(new Date(tmpDate - 1));
if (vals.length == 0) {
aggregatedVals.push(-1);
oldDate = currentDate;
continue;
}
switch (method) {
case 'mean':
aggregatedVals.push(vals.reduce(function(a, b) { return a + b / vals.length; }, 0));
break;
case 'sum':
aggregatedVals.push(vals.reduce(function(a, b) { return a + b; }, 0));
break;
case 'min':
aggregatedVals.push(vals.reduce(function(a, b) { return Math.min(a, b); }));
break;
case 'max':
aggregatedVals.push(vals.reduce(function(a, b) { return Math.max(a, b); }));
break;
default:
aggregatedVals.push(vals.reduce(function(a, b) { return a + b / vals.length; }, 0));
}
vals = []; // clear the daily value vector
vals.push(jsonData[d].temperature); // now push first entry of new day into my temp value vector.
}
oldDate = currentDate;
} // end of for loop
// create output to be in the same List format as the original data from obsArray.
for (let i = 0; i < aggregatedVals.length; i++) {
newOutput.push([aggDates[i].toISOString(), aggregatedVals[i]]);
}
} else { // i.e. aggregate over X hours, irrespective of the day.
let cnt = 0;
let cumHours = 0;
for (var d = 0; d < jsonData.length; d++) {
if (cnt < hours) {
vals.push(jsonData[d].temperature);
cnt++;
} else {
cumHours += cnt;
cnt = 0;
aggDates.push(cumHours);
if (vals.length == 0) {
aggregatedVals.push(-1);
continue;
}
switch (method) {
case 'mean':
aggregatedVals.push(vals.reduce(function(a, b) { return a + b / vals.length; }, 0));
break;
case 'sum':
aggregatedVals.push(vals.reduce(function(a, b) { return a + b; }, 0));
break;
case 'min':
aggregatedVals.push(vals.reduce(function(a, b) { return Math.min(a, b); }));
break;
case 'max':
aggregatedVals.push(vals.reduce(function(a, b) { return Math.max(a, b); }));
break;
default:
aggregatedVals.push(vals.reduce(function(a, b) { return a + b / vals.length; }, 0));
}
vals = []; // clear the daily value vector
vals.push(jsonData[d].temperature); // now push first entry of new day into my temp value vector.
cnt++;
}
oldDate = currentDate;
} // end of for loop
// create output to be in the same List format as the original data from obsArray.
for (let i = 0; i < aggregatedVals.length; i++) {
newOutput.push([aggDates[i], aggregatedVals[i]]);
}
} // end else
return newOutput;
}
/**
* Format the response from SensorThings API to make it suitable for heatmap
* @param {Array} obsArray Response from SensorThings API as array
* @returns {Array} Array of formatted observations suitable for use in a heatmap
*/
export const formatSTAResponseForHeatMap = function (obsArray) {
export const formatSTAResponseForHeatMap = function(obsArray) {
if (!obsArray) return;
const dataSTAFormatted = [];
obsArray.forEach((obs) => {
......@@ -151,7 +338,7 @@ export const formatSTAResponseForHeatMap = function (obsArray) {
* @param {Array} formattedObsArrayForHeatmap Response from SensorThings API formatted for use in a heatmap
* @returns {Object} Highcharts library heatmap object
*/
export const drawHeatMapHC = function (formattedObsArrayForHeatmap) {
export const drawHeatMapHC = function(formattedObsArrayForHeatmap) {
Highcharts.chart("chart-heatmap", {
chart: {
type: "heatmap",
......@@ -223,8 +410,7 @@ export const drawHeatMapHC = function (formattedObsArrayForHeatmap) {
},
},
series: [
{
series: [{
data: formattedObsArrayForHeatmap,
boostThreshold: 100,
borderWidth: 0,
......@@ -232,12 +418,10 @@ export const drawHeatMapHC = function (formattedObsArrayForHeatmap) {
colsize: 24 * 36e5, // one day
tooltip: {
headerFormat: "Temperature<br/>",
pointFormat:
"{point.x:%e %b, %Y} {point.y}:00: <b>{point.value} ℃</b>",
pointFormat: "{point.x:%e %b, %Y} {point.y}:00: <b>{point.value} ℃</b>",
},
turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
},
],
}, ],
});
};
......@@ -246,7 +430,7 @@ export const drawHeatMapHC = function (formattedObsArrayForHeatmap) {
* @param {Array} obsArray Response from SensorThings API as array
* @returns {Array} Array of formatted observations suitable for use in a line chart
*/
export const formatSTAResponseForLineChart = function (obsArray) {
export const formatSTAResponseForLineChart = function(obsArray) {
if (!obsArray) return;
const dataSTAFormatted = [];
obsArray.forEach((result) => {
......@@ -262,7 +446,7 @@ export const formatSTAResponseForLineChart = function (obsArray) {
* @param {Array} formattedObsArrayForLineChart - Response from SensorThings API formatted for use in a line chart
* @returns {Object} Highcharts library line chart object
*/
export const drawLineChartHC = function (formattedObsArrayForLineChart) {
export const drawLineChartHC = function(formattedObsArrayForLineChart) {
// Create the chart
Highcharts.stockChart("chart-line", {
chart: {
......@@ -283,16 +467,14 @@ export const drawLineChartHC = function (formattedObsArrayForLineChart) {
align: "left",
},
series: [
{
series: [{
name: "AAPL",
data: formattedObsArrayForLineChart,
tooltip: {
valueDecimals: 2,
},
turboThreshold: Number.MAX_VALUE, // #3404, remove after 4.0.5 release
},
],
}, ],
});
};
......@@ -303,14 +485,14 @@ export const drawLineChartHC = function (formattedObsArrayForLineChart) {
* @param {Object} responsePromise Promise object
* @returns {Object} - Object containing results from all the "@iot.nextLink" links
*/
export const followNextLink = function (responsePromise) {
export const followNextLink = function(responsePromise) {
if (!responsePromise) return;
return responsePromise
.then(function (lastSuccess) {
.then(function(lastSuccess) {
if (lastSuccess.data["@iot.nextLink"]) {
return followNextLink(
axios.get(lastSuccess.data["@iot.nextLink"])
).then(function (nextLinkSuccess) {
).then(function(nextLinkSuccess) {
nextLinkSuccess.data.value = lastSuccess.data.value.concat(
nextLinkSuccess.data.value
);
......@@ -320,7 +502,7 @@ export const followNextLink = function (responsePromise) {
return lastSuccess;
}
})
.catch(function (err) {
.catch(function(err) {
console.log(err);
});
};
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment