diff --git a/index.html b/index.html new file mode 100644 index 0000000000000000000000000000000000000000..40c7a24322c66018c67b006f91a2deeefa13efec --- /dev/null +++ b/index.html @@ -0,0 +1,646 @@ +<html lang="en"> + +<head> + <meta charset="utf-8" /> + <script src="https://cesium.com/downloads/cesiumjs/releases/1.80/Build/Cesium/Cesium.js"></script> + <link href="https://cesium.com/downloads/cesiumjs/releases/1.80/Build/Cesium/Widgets/widgets.css" + rel="stylesheet" /> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" + integrity="sha384-4LISF5TTJX/fLmGSxO53rV4miRxdg84mZsxmO8Rx5jGtp/LbrixFETvWa5a6sESd" crossorigin="anonymous"> + <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> + <link rel="stylesheet" href="style.css" /> +</head> + +<body> + <div class="webmap"> + <div id="cesiumContainer"></div> + <div class="menu"> + <div class="menubuttons"> + <div class="menuDiv"> + <div> + <b>Select the Attribute to Visualize Statistics</b> + </div> + <div> + <div class="options"> + <input type="radio" id="yearOfCo" name="attribute" value="Year_of_co" checked /> + <label for="yearOfCo">Year of Construction</label> + </div> + <div class="options"> + <input type="radio" id="primaryUsa" name="attribute" value="PrimaryUsa" /> + <label for="primaryUsa">Primary Usage</label> + </div> + <div class="options"> + <input type="radio" id="specificS" name="attribute" value="Specific_s" /> + <label for="specificS">Heating demand in [kWh/m².year]</label> + </div> + </div> + </div> + </div> + <div class="chartContainer"> + <div class="chartCanvas"> + <canvas id="quant-chart"></canvas> + </div> + </div> + <div id="individualStatsChart" class="individualStatsChart"> + <p id="stats-tag">Select a building to show statistics...</p> + <canvas id="buildingStats"></canvas> + </div> + </div> + </div> + + <div class="backdrop" id="menu"> + <h3>Stoeckach Energy Dashboard</h3> + <div id="legend""> + <div class=" my-legend" id="legendcontainer"> + <div class="legend-title">Sp. Heating Demand (kWh/m<sup>2</sup>.a)</div> + <div class="legend-scale"> + <ul class="legend-labels"> + <li><span style="background: #771f02"></span>Demand >= 250 </li> + <li><span style="background: #be4400"></span>Demand >= 200 </li> + <li><span style="background: #ee5c00"></span>Demand >= 150 </li> + <li><span style="background: #ef8d00"></span>Demand >= 125 </li> + <li><span style="background: #f0ba00"></span>Demand >= 100 </li> + <li><span style="background: #f1d700"></span>Demand >= 75 </li> + <li><span style="background: #e4f200"></span>Demand >= 50 </li> + <li><span style="background: #b8fe00"></span>Demand >= 25 </li> + <li><span style="background: #4cbb00"></span>Demand < 25 </li> + <li><span style="background: #000000"></span>None Heated / No Data</li> + </ul> + </div> + </div> + </div> + </div> + <i class="bi bi-info-circle-fill icon" id="toggleIcon"></i> + + <script> + Cesium.Ion.defaultAccessToken = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIwNTg4NmUwZS0wZTk5LTQ0MTMtYTcwMS05OTU4NzcxZGIyZTMiLCJpZCI6MjE3MzI3LCJpYXQiOjE3MTY0NzAyMjB9.DxT60vd_gLj7WGPyNbsNP8mKOy40UKMe-av2z5mvE1E"; + var viewer = new Cesium.Viewer("cesiumContainer", { + baseLayerPicker: true, + vrButton: true, + geocoder: true, + navigationHelpButton: false, + selectionIndicator: true, + shadows: true, + timeline: true, + sceneModePicker: true, + }); + + var tileset = viewer.scene.primitives.add( + new Cesium.Cesium3DTileset({ + url: "./tileset.json", + }) + ); + + tileset.style = new Cesium.Cesium3DTileStyle({ + color: { + conditions: [ + ["Number(${Specific_s}) >= 250", "color('#771f02')"], + ["Number(${Specific_s}) >= 200", "color('#be4400')"], + ["Number(${Specific_s}) >= 150", "color('#ee5c00')"], + ["Number(${Specific_s}) >= 125", "color('#ef8d00')"], + ["Number(${Specific_s}) >= 100", "color('#4cbb00')"], + ["Number(${Specific_s}) >= 75", "color('#f0ba00')"], + ["Number(${Specific_s}) >= 50", "color('#f1d700')"], + ["Number(${Specific_s}) >= 25", "color('#e4f200')"], + ["true", "color('#000000')"], + ], + }, + }); + + let lineChartInstance; + //Selecting a Building + var Pickers_3DTile_Activated = true; + + function active3DTilePicker() { + + var element = document.getElementById('individualStatsChart'); + if (element) { + element.style.display = 'block'; + } + + var highlighted = { + feature: undefined, + originalColor: new Cesium.Color() + }; + // Information about the currently selected feature + var selected = { + feature: undefined, + originalColor: new Cesium.Color() + }; + + // An entity object which will hold info about the currently selected feature for infobox display + var selectedEntity = new Cesium.Entity(); + + // Get default left click handler for when a feature is not picked on left click + var clickHandler = viewer.screenSpaceEventHandler.getInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK); + + // Color a feature GREY on hover. + viewer.screenSpaceEventHandler.setInputAction(function onMouseMove(movement) { + if (Pickers_3DTile_Activated) { + // If a feature was previously highlighted, undo the highlight + if (Cesium.defined(highlighted.feature)) { + highlighted.feature.color = highlighted.originalColor; + highlighted.feature = undefined; + } + // Pick a new feature + var picked3DtileFeature = viewer.scene.pick(movement.endPosition); + if (!Cesium.defined(picked3DtileFeature)) { + + return; + } + // Highlight the feature if it's not already selected. + if (picked3DtileFeature !== selected.feature) { + highlighted.feature = picked3DtileFeature; + Cesium.Color.clone(picked3DtileFeature.color, highlighted.originalColor); + picked3DtileFeature.color = Cesium.Color.GREY; + } + } + }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); + + // Color a feature AQUA on selection and show info in the InfoBox. + viewer.screenSpaceEventHandler.setInputAction(function onLeftClick(movement) { + if (Pickers_3DTile_Activated) { + // If a feature was previously selected, undo the highlight + if (Cesium.defined(selected.feature)) { + selected.feature.color = selected.originalColor; + selected.feature = undefined; + var options = null; + lineChartInstance.destroy(); + lineChartInstance = null; + document.getElementById('stats-tag').style.display = 'block'; + } + // Pick a new feature + var picked3DtileFeature = viewer.scene.pick(movement.position); + if (!Cesium.defined(picked3DtileFeature)) { + clickHandler(movement); + return; + } + // Select the feature if it's not already selected + if (selected.feature === picked3DtileFeature) { + return; + } + selected.feature = picked3DtileFeature; + // Save the selected feature's original color + if (picked3DtileFeature === highlighted.feature) { + Cesium.Color.clone(highlighted.originalColor, selected.originalColor); + highlighted.feature = undefined; + } else { + Cesium.Color.clone(picked3DtileFeature.color, selected.originalColor); + } + // Highlight newly selected feature + picked3DtileFeature.color = Cesium.Color.AQUA; + // Set feature infobox description + var featureName = "Building Attributes"; + selectedEntity.name = featureName; + selectedEntity.description = 'Loading <div class="cesium-infoBox-loading"></div>'; + viewer.selectedEntity = selectedEntity; + selectedEntity.description = + '<table class="cesium-infoBox-defaultTable"><tbody>' + + '<tr><th>GML ID</th><td>' + picked3DtileFeature.getProperty('gml_id') + '</td></tr>' + + '<tr><th>GML Parent ID</th><td>' + picked3DtileFeature.getProperty('gml_parent_id') + '</td></tr>' + + '<tr><th>Sp. Heating Demand</th><td>' + picked3DtileFeature.getProperty('Specific_s') + ' ' + 'kWh/m²' + '</td></tr>' + + '</tbody></table>'; + + document.getElementById('stats-tag').style.display = 'none'; + + } else { + document.getElementById('stats-tag').style.display = 'block'; + } + + // Fetch GeoJSON data - please install CORS plugin otherwise this will not work + fetch('https://ugl.hft-stuttgart.de/leaflet-stoeckach-heatdemand/building_data.json') + .then(response => response.json()) + .then(data => { + + // var picked3DtileFeature = viewer.scene.pick(movement.position); + var gml_id = picked3DtileFeature.getProperty('gml_id'); + + var buildingData = data.features.find(feature => feature.properties.gml_id == gml_id) + if (buildingData) { + var properties = buildingData.properties; + var months = [ + 'January_He', 'February_H', 'March_Heat', 'April_Heat', 'May_Heatin', + 'June_Heati', 'July_Heati', 'August_Hea', 'September_', 'October_He', + 'November_H', 'December_H' + ]; + var monthlyValues = months.map(month => { + // Handle potential formatting issues + var value = properties[month]; + if (typeof value === 'string') { + value = parseFloat(value.replace(',', '.')); + } + return value; + }); + + + // Create the building year of construction distribution chart using Chart.js + var ctx1 = document.getElementById('buildingStats').getContext('2d'); + + // Destroy the previous chart instance if it exists + if (lineChartInstance) { + lineChartInstance.destroy(); + } + + lineChartInstance = new Chart(ctx1, { + type: 'line', + data: { + labels: [ + 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' + ], + datasets: [{ + label: 'Heat Demand Per Month', + data: monthlyValues, + backgroundColor: 'rgba(75, 192, 192, 0.5)', + borderColor: 'rgba(75, 192, 192, 1)', + borderWidth: 1 + }] + }, + options: { + responsive: true, + scales: { + x: { + title: { + display: true, + text: "Month", + font: { + size: 8, + family: "Inter", + weight: 600, + }, + }, + grid: { + color: "#edf2f4", + lineWidth: 0.5, // Change grid line color to light shade + }, + ticks: { + font: { + size: 7, + family: "Inter", + weight: 500, + }, + color: "#2C2C2C", + }, + }, + y: { + title: { + display: true, + text: "Heating demand in [kWh/m².month]", + font: { + size: 8, + family: "Inter", + weight: 600, + }, + }, + grid: { + color: "#edf2f4", + lineWidth: 0.5, // Change grid line color to light shade + }, + beginAtZero: true, + ticks: { + font: { + size: 8, + family: "Inter", + weight: 500, + }, + color: "#2C2C2C", + }, + }, + }, + plugins: { + legend: { + labels: { + font: { + size: 9, // Font size for legend labels + family: "Inter", + weight: 500, + }, + color: "#2C2C2C", + }, + display: false, + }, + title: { + font: { + size: 13, + family: "Inter", + weight: 600, + }, + display: true, + text: 'Heating Demand for Building (GML_ID: ' + gml_id + ')' + }, + }, + }, + }); + } + + }) + + }, + + Cesium.ScreenSpaceEventType.LEFT_CLICK); + } + + + active3DTilePicker(); + + document.addEventListener('DOMContentLoaded', function () { + // Get the element and close button by their IDs + var element = document.getElementById('individualStatsChart'); + var closeButton = document.getElementById('closeStats'); + + // Check if the elements exist + if (element && closeButton) { + // Add click event listener to the close button + closeButton.addEventListener('click', function () { + // Hide the element + element.style.display = 'none'; + }); + } + }); + + // Charts and Visualization + + let chart; + + Cesium.when(tileset.readyPromise).then(function (tileset) { + viewer.flyTo(tileset); + + var processedTiles = new Set(); + var yearFrequency = {}; + var primaryUsaFrequency = {}; + var specificSFrequency = { + "0-25": 0, + "26-50": 0, + "51-75": 0, + "76-100": 0, + "101-125": 0, + "126-150": 0, + "151-200": 0, + "201-250": 0, + "251+": 0, + }; + + tileset.tileVisible.addEventListener(function (tile) { + if (!processedTiles.has(tile)) { + processedTiles.add(tile); + + var content = tile.content; + var featuresLength = content.featuresLength; + + for (var i = 0; i < featuresLength; i++) { + var feature = content.getFeature(i); + + var year = feature.getProperty("Year_of_co"); + if (year) { + if (yearFrequency[year]) { + yearFrequency[year]++; + } else { + yearFrequency[year] = 1; + } + } + + var primaryUsa = feature.getProperty("PrimaryUsa"); + if (primaryUsa) { + if (primaryUsaFrequency[primaryUsa]) { + primaryUsaFrequency[primaryUsa]++; + } else { + primaryUsaFrequency[primaryUsa] = 1; + } + } + + var specific_s = feature.getProperty("Specific_s"); + if (specific_s) { + specific_s = Number(specific_s); + if (specific_s >= 0 && specific_s <= 25) { + specificSFrequency["0-25"]++; + } else if (specific_s > 25 && specific_s <= 50) { + specificSFrequency["26-50"]++; + } else if (specific_s > 50 && specific_s <= 75) { + specificSFrequency["51-75"]++; + } else if (specific_s > 75 && specific_s <= 100) { + specificSFrequency["76-100"]++; + } else if (specific_s > 100 && specific_s <= 125) { + specificSFrequency["101-125"]++; + } else if (specific_s > 125 && specific_s <= 150) { + specificSFrequency["126-150"]++; + } else if (specific_s > 150 && specific_s <= 200) { + specificSFrequency["151-200"]++; + } else if (specific_s > 200 && specific_s <= 250) { + specificSFrequency["201-250"]++; + } else if (specific_s > 250) { + specificSFrequency["251+"]++; + } + } + } + + // Draw initial chart based on the default selection + updateChart(); + } + }); + + const drawChart = (labels, data, type, label) => { + var ctx = document.getElementById("quant-chart").getContext("2d"); + + // Destroy existing chart instance if it exists + if (chart) { + chart.destroy(); + } + + var options = { + responsive: true, + plugins: { + legend: { + labels: { + font: { + family: "Inter", // Set font family + size: 11, // Set font size + weight: "bold", // Set font weight + }, + }, + }, + }, + scales: { + x: { + ticks: { + font: { + family: "Inter", // Set font family + size: 9, // Set font size + weight: "normal", // Set font weight + }, + }, + }, + y: { + ticks: { + font: { + family: "Inter", // Set font family + size: 9, // Set font size + weight: "normal", // Set font weight + }, + }, + }, + }, + }; + + var backgroundColors = "rgba(75, 192, 192, 0.2)"; + var borderColors = "rgba(75, 192, 192, 1)"; + + if (type === "doughnut") { + // Custom colors for doughnut chart + backgroundColors = [ + "#FF6384", + "#36A2EB", + "#FFCE56", + "#FF9F40", + "#4BC0C0", + "#9966FF", + "#FF6384", + "#36A2EB", + ]; + + // Convert labels to Init Case + labels = labels.map((label) => + label.replace(/\b\w/g, (char) => char.toUpperCase()) + ); + + options = { + ...options, + plugins: { + tooltip: { + callbacks: { + label: function (tooltipItem) { + let value = data[tooltipItem.dataIndex]; + let total = data.reduce((acc, val) => acc + val, 0); + let percentage = ((value / total) * 100).toFixed(2); + return `${labels[tooltipItem.dataIndex] + }: ${percentage}% (${value})`; + }, + }, + }, + legend: { + labels: { + generateLabels: function (chart) { + var data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map((label, i) => { + var value = data.datasets[0].data[i]; + var total = data.datasets[0].data.reduce( + (acc, val) => acc + val, + 0 + ); + var percentage = ((value / total) * 100).toFixed(2); + return { + text: `${label} (${percentage}%)`, + fillStyle: data.datasets[0].backgroundColor[i], + hidden: false, + lineCap: "butt", + lineDash: [], + lineDashOffset: 0, + lineJoin: "miter", + strokeStyle: "rgba(0,0,0,0.1)", + pointStyle: "circle", + rotation: 0, + }; + }); + } + return []; + }, + }, + }, + }, + }; + } else if ( + type === "bar" && + label === "Heating demand in [kWh/m².year]" + ) { + // Custom colors for specific_s bar chart + backgroundColors = [ + "#4cbb00", + "#b8fe00", + "#e4f200", + "#f1d700", + "#f0ba00", + "#ef8d00", + "#ee5c00", + "#be4400", + ]; + borderColors = backgroundColors; + options.plugins = { + legend: { + display: true, + }, + }; + } + + chart = new Chart(ctx, { + type: type, + data: { + labels: labels, + datasets: [ + { + label: label, + data: data, + backgroundColor: Array.isArray(backgroundColors) + ? backgroundColors + : [backgroundColors], + borderColor: + type === "doughnut" + ? "transparent" + : Array.isArray(borderColors) + ? borderColors + : [borderColors], + borderWidth: 1, + }, + ], + }, + options: options, + }); + }; + + const updateChart = () => { + var selectedAttribute = document.querySelector( + 'input[name="attribute"]:checked' + ).value; + if (selectedAttribute === "Year_of_co") { + var labels = Object.keys(yearFrequency); + var data = Object.values(yearFrequency); + drawChart(labels, data, "bar", "Building Count"); + } else if (selectedAttribute === "PrimaryUsa") { + var labels = Object.keys(primaryUsaFrequency); + var data = Object.values(primaryUsaFrequency); + drawChart(labels, data, "doughnut", "Primary Use Percentage"); + } else if (selectedAttribute === "Specific_s") { + var labels = Object.keys(specificSFrequency); + var data = Object.values(specificSFrequency); + drawChart(labels, data, "bar", "Heating demand in [kWh/m².year]"); + } + }; + + // Add event listeners to radio buttons + document + .querySelectorAll('input[name="attribute"]') + .forEach((radio) => { + radio.addEventListener("change", updateChart); + }); + }); + + document.getElementById('toggleIcon').addEventListener('click', function() { + var menu = document.getElementById('menu'); + var icon = document.getElementById('toggleIcon'); + + // Toggle the menu display + if (menu.style.display === 'none' || menu.style.display === '') { + menu.style.display = 'block'; + } else { + menu.style.display = 'none'; + } + + // Toggle the icon position + icon.classList.toggle('moved'); + }); + + + </script> +</body> + +</html> \ No newline at end of file