From a5a498895d43ed43825124acf2f60e70b202c961 Mon Sep 17 00:00:00 2001 From: JOE XMG <thunyathep.s@outlook.com> Date: Sun, 18 Feb 2024 20:23:33 +0100 Subject: [PATCH] update --- public/udigit4icity/index.html | 1164 ++++++++++++++++++++++++++++++++ 1 file changed, 1164 insertions(+) create mode 100644 public/udigit4icity/index.html diff --git a/public/udigit4icity/index.html b/public/udigit4icity/index.html new file mode 100644 index 0000000..a17e0b8 --- /dev/null +++ b/public/udigit4icity/index.html @@ -0,0 +1,1164 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="UTF-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Integrated Web Map and ECharts</title> + + <link rel="stylesheet" href="https://js.arcgis.com/4.28/esri/css/main.css"> + + <script src="https://cdn.plot.ly/plotly-latest.min.js"></script> + <script src="https://api.mapbox.com/mapbox.js/v3.3.1/mapbox.js"></script> + <link href="https://api.mapbox.com/mapbox.js/v3.3.1/mapbox.css" rel="stylesheet" /> + <!-- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/echarts@5.0.0/dist/echarts.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/echarts-gl@5.0.0/dist/echarts-gl.min.css"> --> + <link rel="stylesheet" href="https://js.arcgis.com/4.28/esri/themes/dark/main.css" /> + + <style> + body { + font-size: 16px + } + + #chartControls label, + #chartControls select, + #chartControls button { + font-size: 16px; + margin-bottom: 15px + } + + html { + margin: 0; + padding: 0; + height: 100%; + overflow: hidden; + } + + #usage-pie-chart-container { + z-index: 1; + position: absolute; + top:10px; + right: 0px; + width: 20%; + height: 0.2px; + bottom:10%; + } + + #mainMenu { + position: absolute; + top: 10px; + left: 60px; + z-index: 4; + background-color: rgba(53, 51, 51, 0.8); + padding: 8px; + border-radius: 5px; + display: flex; + flex-direction: column; + } + + #mainMenu label { + margin-bottom: 5px; + } + + #mainMenu button { + margin-top: 10px; + padding: 5px 10px; + cursor: pointer; + } + + #main-menu { + width: 20%; + height: 100%; + background-color: #2c3e50; + color: rgb(91, 89, 89); + display: flex; + flex-direction: column; + align-items: center; + padding-top: 20px; + } + + #container { + display: flex; + width: 100%; + height: 100vh; + flex-direction: row; + } + + #main { + + width: 36%; + height: 65%; + position:relative; + top:35%; + right:0; + z-index: 1; + + } + + #viewDiv { + + width: 100%; + height: 100%; + position: absolute; + top:0; + right:0; + + } + + #spaceTimeCubeContainer { + display: none; + } + + .legend-building { + position: absolute; + bottom: 10px; + right: 10px; + padding: 5px; + background-color: rgba(255, 255, 255, 0.8); + border-radius: 5px; + z-index: 1; + display: flex; + flex-direction: column; + } + + .legend { + position: absolute; + top: 90%; + left: 36%; + padding: 5px; + background-color: rgba(255, 255, 255, 0.8); + border-radius: 5px; + z-index: 1; + display: flex; + flex-direction: column; + } + + .legend-item { + display: flex; + align-items: center; + margin-bottom: 5px; + cursor: pointer; + } + + .legend-item div { + align-items: relative; + width: 20px; + height: 20px; + margin-right: 10px; + } + + #chartControls { + position: absolute; + top: 34px; + left: 20px; + z-index: 3; + } +#plotlyContainer { + height: 100vh; + position: absolute; + bottom: 0; + left: 0; + top: 2%; + right:100; + width: 100%; + z-index: 3; +} + +.dark-popup-content { + color: #232020; + background-color: #171515; /* to dark gray */ +} + + </style> +</head> + +<body> + <div id="container"> + <div id="usage-pie-chart-container" style="width: 300px; height: 300px; "> + <canvas id="usage-pie-chart" style="width: 100%; height: 100%;"></canvas> + </div> + + <div id="main"></div> + <div id="viewDiv"></div> + <div id="timeSlider"></div> + + <div class="legend"></div> + <div class="legend-building"></div> + <div id="spaceTimeCubeContainer"> + <div id="plotlyContainer"></div> + + </div> + </div> + + </div> + </div> + </div> + + + </div> + <div id="mainMenu"> + <label for="measurementType">Select Measurement Type:</label> + <select id="measurementType"> + <option value="temperature">Temperature °C</option> + <option value="humidity">Humidity %</option> + <option value="illuminance">Illuminance in Lux</option> + </select> + + <label for="chartType">Select Chart Type:</label> + <select id="chartType"> + <option value="line">Line Chart</option> + <option value="bar">Bar Chart</option> + </select> + + <button id="loadDatastream">Load Datastream</button> + <button id="toggleSpaceTime">Bus Space-Time Visualization</button> + + <button id="toggle3DButton">3D City Building</button> + + </div> + + <script src="https://cdn.plot.ly/plotly-latest.min.js"></script> + <script src="https://api.mapbox.com/mapbox.js/v3.3.1/mapbox.js"></script> + <!-- <script src="https://cdn.jsdelivr.net/npm/echarts@5.0.0/dist/echarts.min.js"></script> + <script src="https://cdn.jsdelivr.net/npm/echarts-gl@5.0.0/dist/echarts-gl.min.js"></script> --> + <script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.5.0/echarts.min.js" integrity="sha512-k37wQcV4v2h6jgYf5IUz1MoSKPpDs630XGSmCaCCOXxy2awgAWKHGZWr9nMyGgk3IOxA1NxdkN8r1JHgkUtMoQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> + <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> + <script src="https://code.highcharts.com/highcharts.js"></script> + + <script src="https://js.arcgis.com/4.28/"></script> +<script> + + const data = [ + + ]; + + //to ensure if plotly and mapbox are loaded + if (window.Plotly && window.L) { + + document.getElementById("loadDatastream").addEventListener("click", function () { + var selectedMeasurementType = document.getElementById("measurementType").value; + var selectedChartType = document.getElementById("chartType").value; + + // Mapping from sensor location to their corresponding Datastream id + var datastreamIds = { + "Co-working": { + illuminance: 7, + temperature: 8, + humidity: 9 + }, + "Eaves": { + illuminance: 1, + temperature: 2, + humidity: 3 + }, + "Cafeteria": { + illuminance: 4, + temperature: 5, + humidity: 6 + } + }; + + //adding color for each + var locationColors = { + "Co-working": 'orange', + "Eaves": '#229954', + "Cafeteria": '#355EC2' + }; + + var resultData = []; + + Object.entries(datastreamIds).forEach(([location, types]) => { + var datastreamId = types[selectedMeasurementType]; + var datastreamUrl = "https://ogcapi.hft-stuttgart.de/sta/udigit4icity/v1.1/Datastreams(" + datastreamId + ")/Observations?$select=result,phenomenonTime&$top=10000"; + + fetch(datastreamUrl) + .then(response => response.json()) + .then(data => { + var locationData = data.value + .filter(item => item.result !== null && item.result >= 1) + .map(item => ({ + phenomenonTime: new Date(item.phenomenonTime), + result: item.result + })); + + resultData.push({ + location: location, + data: locationData + }); + + if (resultData.length === Object.keys(datastreamIds).length) { + createChart(resultData, selectedMeasurementType, selectedChartType, locationColors); + } + }) + .catch(error => { + console.error("Error loading data:", error); + }); + }); + }); + + function createChart(resultData, selectedMeasurementType, selectedChartType, locationColors) { + var chartDom = document.getElementById('main'); + var myChart = echarts.init(chartDom); + + myChart.setOption({ + backgroundColor: 'dark-gray', + title: { + text: selectedMeasurementType.charAt(0).toUpperCase() + selectedMeasurementType.slice(1) + + ' Results at Different Sensor Locations', + left: 'center', + textStyle: { + color: 'white', + fontSize: 20, + }, + + }, + tooltip: { + trigger: 'axis', + backgroundColor: 'rgba(0, 0, 0, 0.7)', + formatter: function (params) { + var timestamp = new Date(params[0].value[0]); + var formattedTime = timestamp.toLocaleString(); + var tooltip = 'Timestamp: ' + formattedTime + '<br>'; + params.forEach(function (item) { + tooltip += item.seriesName + ': ' + item + .value[1] + '<br>'; + }); + return tooltip; + }, + }, + grid: { + left: '5%', + right: '15%', + top: '10%', + bottom: '10%', + + }, + xAxis: { + name: 'Phenomenon Time', + type: 'time', + boundaryGap: false, + axisLabel: { + color: 'green', + fontSize: 18, + }, + }, + yAxis: { + name: selectedMeasurementType.charAt(0).toUpperCase() + selectedMeasurementType.slice(1), + axisLabel: { + color: 'red', + fontSize: 18, + }, + }, + series: resultData.map(locationData => ({ + name: locationData.location, + type: selectedChartType, + data: locationData.data.map(item => [item.phenomenonTime, item.result]), + itemStyle: { + color: locationColors[locationData.location], + emphasis: { + color: 'white', + fontWeight: 'bold', + }, + }, + })), + legend: { + data: resultData.map(locationData => locationData.location), + right: 10, + bottom: 10, + textStyle: { + fontSize: 14, + color: 'white', + }, + emphasis: { + textStyle: { + fontSize: 14, + color: 'black', + fontWeight: 'bold', + }, + backgroundColor: 'white', + }, + }, + dataZoom: [ + { + type: 'slider', + xAxisIndex: [0], + startValue: resultData[0].data[0].phenomenonTime, + endValue: resultData[0].data[resultData[0].data.length - 1].phenomenonTime, + zoomLock: false, + showDetail: false, + top:'92%' + }, + { + type: 'inside', + xAxisIndex: [0], + }, + ], + toolbox: { + feature: { + dataZoom: { + show: true, + }, + dataView: { + show: true, + }, + saveAsImage: { + show: true, + }, + }, + }, + }); + + // adding legend items dynamically + var legend = document.querySelector('.legend'); + legend.innerHTML = ''; + Object.keys(locationColors).forEach(location => { + var color = locationColors[location]; + var legendItem = createLegendItem(color, location, location); + legend.appendChild(legendItem); + }); + } + + function createLegendItem(color, label, id) { + const legendItem = document.createElement("div"); + legendItem.className = "legend-item"; + legendItem.id = id; + legendItem.innerHTML = ` + <div style="background-color: ${color};"></div> + <span>${label}</span> + `; + return legendItem; + } + + function createBuildingLegendItem(color, label, id) { + const legendItem = document.createElement("div"); + legendItem.className = "legend-item"; + legendItem.id = id; + legendItem.innerHTML = ` + <div style="background-color: ${color};"></div> + <span>${label}</span> + `; + return legendItem; + } + + // To animating space-time routes directly + animateSpaceTimeRoutes('https://ogcapi.hft-stuttgart.de/ogc_api_moving_features/collections/bus_1/items'); + function animateSpaceTimeRoutes(url) { + fetch(url) + .then(response => response.json()) + .then(data => { + if (!data || !data.features) { + console.error('Invalid data format:', data); + return; + } + + const frames = data.features.map((feature, index) => { + const coordinates = feature.geometry.coordinates; + const timestamps = feature.properties.datetimes; + + if (!Array.isArray(coordinates) || !Array.isArray(timestamps)) { + console.error('Invalid feature format:', feature); + return; + } + + return { + data: [ + { + type: 'scattermapbox', + lat: coordinates.map(coord => coord[1]), + lon: coordinates.map(coord => coord[0]), + mode: 'lines+markers', + marker: { size: 6, color: 'black' }, + line: { color: 'black' }, + name: 'Bus route', + }, + { + type: 'scatter3d', + x: coordinates.map(coord => coord[0]), + y: coordinates.map(coord => coord[1]), + z: timestamps.map((time, i) => new Date(time).toISOString()), + + mode: 'lines+markers', + line: { color: 'dark' }, + name: 'Bus trajectory line', + }, + ], + name: `frame${index + 1}`, + }; + }); + + // to set the layout with mapbox _ access token + +const layout = { + mapbox: { + style: 'light', + center: { lon: frames[0].data[0].lon[0], lat: frames[0].data[0].lat[0] }, + zoom: 10, + accesstoken: 'pk.eyJ1IjoicmVkaWV0OTk2MyIsImEiOiJjbHJ1cmk5aGUwaDRvMmpuYWM4Z2NqcmZuIn0.c11HYT-5ucd6g6mL03bp3Q', // Mapbox access token + }, + margin: { t: 0, b: 0, l: 0, r: 0 }, + scene: { + xaxis: { + title: 'X Axis', + titlefont: { + family: 'Arial, sans-serif', + size: 15, + color: 'black' + }, + tickfont: { + family: 'Arial, sans-serif', + size: 15, + color: 'black', + bold: true + } + }, + yaxis: { + title: 'Y Axis', + titlefont: { + family: 'Arial, sans-serif', + size: 15, + color: 'black' + }, + tickfont: { + family: 'Arial, sans-serif', + size: 15, + color: 'black', + bold: true + } + }, + zaxis: { + title: 'Time', + titlefont: { + family: 'Arial, sans-serif', + size: 16, + color: 'black' + }, + tickfont: { + family: 'Arial, sans-serif', + size: 15, + color: 'black', + bold: true + } + }, + // Common font settings for both + font: { + family: 'Arial, sans-serif', + size: 12, + color: 'black', + bold: true + } + } +}; + + // Initialize Plotly container + Plotly.newPlot('plotlyContainer', frames[0].data, layout); + Plotly.addFrames('plotlyContainer', frames); + }) + .catch(error => { + console.error('Error fetching data:', error); + }); + } + + // Function to toggle space-time visualization + document.getElementById("toggleSpaceTime").addEventListener("click", function () { + const plotlyContainer = document.getElementById("plotlyContainer"); + plotlyContainer.style.display = plotlyContainer.style.display === "none" ? "block" : "none"; + }); + } else { + console.error("Plotly or Mapbox not loaded. Check if the libraries are loaded correctly."); + } + +function loadHistoricalRoutes() { + // to fetch historical data and update the scene layer + animateHistoricalMovingFeatures('https://ogcapi.hft-stuttgart.de/ogc_api_moving_features/collections/bus_1/items', [226, 119, 40], 'Bus', 'Bus', 'busLegend'); +} + +</script> + + <script> + require([ + "esri/Map", + "esri/views/SceneView", + "esri/layers/SceneLayer", + "esri/layers/FeatureLayer", + "esri/Graphic", + "esri/geometry/Polyline", + "esri/geometry/Point", + "esri/symbols/WebStyleSymbol", + "esri/symbols/SimpleLineSymbol", + "esri/layers/GraphicsLayer", + "esri/request", + "esri/renderers/UniqueValueRenderer", + "esri/renderers/SimpleRenderer", + "esri/symbols/SimpleMarkerSymbol", + "esri/PopupTemplate" +], function (Map, SceneView, SceneLayer, FeatureLayer, Graphic, Polyline, Point, WebStyleSymbol, SimpleLineSymbol, GraphicsLayer, esriRequest, UniqueValueRenderer, SimpleRenderer, SimpleMarkerSymbol, PopupTemplate) { + + const map = new Map({ + basemap: "dark-gray-vector" + }); + + const view = new SceneView({ + container: "viewDiv", + map: map, + camera: { + position: { + x: 130.5180055250, + y: 33.7766570370, + z: 25000, + spatialReference: { + wkid: 4326 + } + }, + heading: 0, + tilt: 0 + }, + environment: { + atmosphereEnabled: false, // clearer view + lighting: { + directShadowsEnabled: true, + + } + } + }); + +const webStyleSymbol = new WebStyleSymbol({ + name: "Telecom", + styleName: "EsriIconsStyle" +}); + +const popupTemplate = new PopupTemplate({ + title: "{title}", // dynamic titles + content: ` + <table > + <tr> + <th>Datastream Description</th> + <td>{Datastream_Description}</td> + </tr> + <tr> + <th>Datastream ID</th> + <td>{Datastream_ID}</td> + </tr> + <tr> + <th>Date</th> + <td>{Date}</td> + </tr> + <tr> + <th>Latitude</th> + <td>{Latitude}</td> + </tr> + <tr> + <th>Longitude</th> + <td>{Longitude}</td> + </tr> + <tr> + <th>observedArea</th> + <td>{observedArea}</td> + </tr> + <tr> + <th>Result</th> + <td>{Result}</td> + </tr> + + </table> + ` +}); + +const featureLayer1 = new FeatureLayer({ + url: "https://services.arcgis.com/1lplwYilIlo008hQ/arcgis/rest/services/Datastreams_observation_xy_splited/FeatureServer", + renderer: new SimpleRenderer({ + symbol: webStyleSymbol // specified symbol + }), + popupTemplate: popupTemplate, + outFields: ["*"] // …include all fields +}); + + +map.add(featureLayer1); + +const featureLayer2 = new FeatureLayer({ + url: "https://services.arcgis.com/1lplwYilIlo008hQ/arcgis/rest/services/Munakata_City_Road/FeatureServer", + renderer: new SimpleRenderer({ + symbol: new SimpleLineSymbol({ + color: '#7DF9FF', + width: 0.1 + }) + }), + outFields: ["*"], +}); + +map.add(featureLayer2); + + // Adding hosted 3D building layer + const hostedLayer = new SceneLayer({ + url: "https://tiles.arcgis.com/tiles/1lplwYilIlo008hQ/arcgis/rest/services/Munakata_City_Building_0h/SceneServer", + renderer: new UniqueValueRenderer({ + field: "usage", // actual usage name + defaultSymbol: { + type: "mesh-3d", + symbolLayers: [ + { + type: "fill", + material: { + color: [253, 127, 111, 1] // #fd7f6fff + } + } + ] + }, + uniqueValueInfos: [ + { + value: "文教厚生施è¨", + symbol: { + type: "mesh-3d", + symbolLayers: [ + { + type: "fill", + material: { + color:'#FF6384', + } + } + ] + } + }, + { + value: "å•†æ¥æ–½è¨", + symbol: { + type: "mesh-3d", + symbolLayers: [ + { + type: "fill", + material: { + color: "#347fb3" + } + } + ] + } + }, + { + value: "å…±åŒä½å®…", + symbol: { + type: "mesh-3d", + symbolLayers: [ + { + type: "fill", + material: { + color: "#FFCE56", + } + } + ] + } + }, + { + value: "å·¥å ´", + symbol: { + type: "mesh-3d", + symbolLayers: [ + { + type: "fill", + material: { + color: "#4BC0C0", + } + } + ] + } + }, + { + value: "Other", + symbol: { + type: "mesh-3d", + symbolLayers: [ + { + type: "fill", + material: { + color: "#9966FF", + } + } + ] + } + }, + { + value: "ä½å®…", + symbol: { + type: "mesh-3d", + symbolLayers: [ + { + type: "fill", + material: { + color: "#b57433", + } + } + ] + } + }, + { + value: "æ¥å‹™æ–½è¨", + symbol: { + type: "mesh-3d", + symbolLayers: [ + { + type: "fill", + material: { + color:"#3ca33c", + } + } + ] + } + }, + { + value: "è¾²æž—æ¼æ¥ç”¨æ–½è¨", + symbol: { + type: "mesh-3d", + symbolLayers: [ + { + type: "fill", + material: { + color: "#FF99CC", + } + } + ] + } + }, + { + value:"é‹è¼¸å€‰åº«æ–½è¨", + symbol: { + type: "mesh-3d", + symbolLayers: [ + { + type: "fill", + material: { + color: "#BDBDBD", + } + } + ] + } + }, + ] + }), + elevationInfo: { + "mode": "on-the-ground", + + "offset": 20, + "featureExpressionInfo": { + "expression": "$feature.ELEVATION_Meters" + }, + "unit": "meters" + }, + visible: false // 3D building initially hidden + }); + + map.add(hostedLayer); + +// usage data with 'usage' and 'count' properties +const usageData = [ + { usage: "educational facilities", count: 100 }, + { usage: "Commercial Facilities", count: 200 }, +]; + +// Additional usage +const additionalUsageData = [ + { usage: "apartment house", count: 150 }, + { usage: "factory", count: 75 }, + { usage: "Other", count: 50 }, + { usage: "Residential", count: 300 }, + { usage: "business facility", count: 180 }, + { usage: "Agriculture, Forestry and Fishing Facilities", count: 90 }, + { usage: "Transportation warehouse facilities", count: 120 } +]; + +// Combines all usage_data +const allUsageData = usageData.concat(additionalUsageData); + +// to extract labels and counts +const labels = allUsageData.map(item => item.usage); +const counts = allUsageData.map(item => item.count); + +// colors for each category +const colors = [ + "#FF6384", + "#347fb3", + "#FFCE56", + "#4BC0C0", + "#9966FF", + "#b57433", + "#3ca33c", + "#FF99CC", + "#BDBDBD" +]; + +// Creating pie chart +const pieChartCanvas = document.getElementById("usage-pie-chart").getContext("2d"); +const usagePieChart = new Chart(pieChartCanvas, { + type: "pie", + data: { + labels: labels, + datasets: [{ + data: counts, + backgroundColor: colors + }] + }, + options: { + title: { + display: true, + text: "Building Usage" + }, + legend: { + labels: { + fontColor: 'white' + } + } + } +}); + + // To add an event listener for toggling 3D plot visibility + document.getElementById("toggle3DButton").addEventListener("click", function () { + hostedLayer.visible = !hostedLayer.visible; + }); + + const graphicsLayer = new GraphicsLayer(); + map.add(graphicsLayer); + +function updatePopupContent(thingId, newData) { + const graphic = IoTGraphics.find(item => item.thingId === thingId); + + if (graphic) { + // updating the popup content + graphic.graphic.popupTemplate.content = `New Content: ${newData}`; + } +} + +function createSymbol(styleName) { + return new WebStyleSymbol({ + name: styleName, + styleName: "EsriIconsStyle" + }); +} + +function createCallout(point, symbol, height, time) { + const iconGraphic = new Graphic({ + geometry: point, + symbol: symbol, + attributes: { + time: time + }, + popupTemplate: { + title: "Time", + content: "{time}" + } + }); + + graphicsLayer.add(iconGraphic); // adds graphic to layer +} + + +function animateMovingFeatures(url, styleName, legendLabel, id, height) { + esriRequest(url, { responseType: "json" }) + .then(response => { + const features = response.data.features; + const coordinates = features.reduce((acc, curr) => acc.concat(curr.geometry.coordinates), []); + const datetimes = features.reduce((acc, curr) => acc.concat(curr.properties.datetimes), []); + + const symbol = createSymbol(styleName); // Create symbol once + let currentPoint = 0; + let movingFeatureGraphic; + + // To animate moving features + const animationInterval = setInterval(() => { + if (currentPoint < coordinates.length) { + const [longitude, latitude, featureHeight] = coordinates[currentPoint]; + const time = new Date(datetimes[currentPoint]).toLocaleString(); + + const movingFeaturePoint = new Point({ + x: longitude, + y: latitude, + z: 0, + spatialReference: { + wkid: 4326 + } + }); + + if (!movingFeatureGraphic) { + // Create the moving feature graphic for the first time + movingFeatureGraphic = new Graphic({ + geometry: movingFeaturePoint, + symbol: symbol, + attributes: { + time: time + }, + popupTemplate: { + title: "Time", + content: "{time}" + } + }); + graphicsLayer.add(movingFeatureGraphic); + } else { + // Update the position of the existing moving feature graphic + movingFeatureGraphic.geometry = movingFeaturePoint; + movingFeatureGraphic.attributes.time = time; + } + + currentPoint++; + } + }, 1000); + }) + .catch(error => { + console.error("Error fetching data:", error); + }); +} + +// Function to animate moving features for east and west bound trains +function animateTrainRoutes(eastboundUrl, westboundUrl, styleName, legendLabel, id, height) { + Promise.all([esriRequest(eastboundUrl, { responseType: "json" }), esriRequest(westboundUrl, { responseType: "json" })]) + .then(responses => { + const eastboundFeatures = responses[0].data.features; + const westboundFeatures = responses[1].data.features; + + //Combine east and westbound features into one array + const allFeatures = [...eastboundFeatures, ...westboundFeatures]; + + const symbol = createSymbol(styleName); // Create symbol once + let currentFeatureIndex = 0; + let movingFeatureGraphic; + + // animate features + const animationInterval = setInterval(() => { + if (currentFeatureIndex < allFeatures.length) { + const feature = allFeatures[currentFeatureIndex]; + const [longitude, latitude, featureHeight] = feature.geometry.coordinates; + const time = new Date(feature.properties.datetimes[0]).toLocaleString(); + + const movingFeaturePoint = new Point({ + x: longitude, + y: latitude, + z: featureHeight, + spatialReference: { + wkid: 4326 + } + }); + + if (!movingFeatureGraphic) { + // Create the moving feature graphic for the first time + movingFeatureGraphic = new Graphic({ + geometry: movingFeaturePoint, + symbol: symbol, + attributes: { + time: time + }, + popupTemplate: { + title: "Time", + content: "{time}" + } + }); + graphicsLayer.add(movingFeatureGraphic); + } else { + // update the position of the existing M_feature graphic + movingFeatureGraphic.geometry = movingFeaturePoint; + movingFeatureGraphic.attributes.time = time; + } + + currentFeatureIndex++; + } else { + // Reset the animation when all features have been displayed + currentFeatureIndex = 0; + } + }, 1000); + }) + .catch(error => { + console.error("Error fetching data:", error); + }); +} + +animateMovingFeatures("https://ogcapi.hft-stuttgart.de/ogc_api_moving_features/collections/bus_1/items", "Bus", "Bus", "busLegend", 100); +animateMovingFeatures("https://ogcapi.hft-stuttgart.de/ogc_api_moving_features/collections/train_EastBound_WD/items", "Train", "Eastbound Train", "trainEastLegend", 500); +animateMovingFeatures("https://ogcapi.hft-stuttgart.de/ogc_api_moving_features/collections/train_WestBound_WD/items", "Train", "Westbound Train", "trainWestLegend", 500); + + document.getElementById("toggleSpaceTime").addEventListener("click", function () { + const spaceTimeContainer = document.getElementById("spaceTimeCubeContainer"); + console.log("Toggle button clicked"); + console.log("Current display style:", spaceTimeContainer.style.display); + spaceTimeContainer.style.display = spaceTimeContainer.style.display === "none" ? "block" : "none"; + console.log("New display style:", spaceTimeContainer.style.display); + + // If the space-time container is set to block initialize/reload the space-time visualization + if (spaceTimeContainer.style.display === "block") { + console.log("Initializing space-time visualization..."); + animateSpaceTimeRoutes('https://ogcapi.hft-stuttgart.de/ogc_api_moving_features/collections/bus_1/items'); + } + }); + +}); + + function animateSpaceTimeRoute(url, containerId, trainColor, trainName) { + fetch(url) + .then(response => response.json()) + .then(data => { + if (!data || !data.features) { + console.error('Invalid data format:', data); + return; + } + + const features = Array.isArray(data.features) ? data.features : [data.features]; + + const frames = features.map((feature, index) => { + const coordinates = feature.geometry.coordinates; + const timestamps = feature.properties.datetimes; + + if (!Array.isArray(coordinates) || !Array.isArray(timestamps)) { + console.error('Invalid feature format:', feature); + return; + } + + return { + data: [ + { + type: 'scattermapbox', + lat: coordinates.map(coord => coord[1]), + lon: coordinates.map(coord => coord[0]), + mode: 'lines+markers', + marker: { size: 20, color: trainColor }, + line: { color: trainColor }, + name: `Bus route`, + }, + { + type: 'scatter3d', + x: coordinates.map(coord => coord[0]), + y: coordinates.map(coord => coord[1]), + z: timestamps.map((time, i) => new Date(time).toISOString()), // convert to ISO format + mode: 'lines+markers', + line: { color: trainColor }, + text: timestamps.map((time) => new Date(time).toLocaleTimeString()), // labels on the points + name: `Bus Space-Time - ${index + 1}`, + }, + ], + name: `Frame ${index + 1}`, + }; + }); + + // Set the layout with mapbox style and access token + const layout = { + mapbox: { + style: 'light', + center: { lon: features[0].geometry.coordinates[0][0], lat: features[0].geometry.coordinates[0][1] }, + zoom: 10, + accesstoken: 'pk.eyJ1IjoicmVkaWV0OTk2MyIsImEiOiJjbHJ1cmk5aGUwaDRvMmpuYWM4Z2NqcmZuIn0.c11HYT-5ucd6g6mL03bp3Q', + }, + margin: { t: 0, b: 0, l: 0, r: 0 }, + + }; + + // initialize Plotly container + Plotly.newPlot(containerId, frames[0].data, layout); + + // adding frames + Plotly.addFrames(containerId, frames); + }) + .catch(error => { + console.error('Error fetching data:', error); + }); + } + +</script> +<script> +const thingsLocations = [ { thingId: 1, locationURL: "https://ogcapi.hft-stuttgart.de/sta/udigit4icity/v1.1/Things(1)/Locations", datastreamsURL: "https://ogcapi.hft-stuttgart.de/sta/udigit4icity/v1.1/Things(1)/Datastreams" }, { thingId: 2, locationURL: "https://ogcapi.hft-stuttgart.de/sta/udigit4icity/v1.1/Things(2)/Locations", datastreamsURL: "https://ogcapi.hft-stuttgart.de/sta/udigit4icity/v1.1/Things(2)/Datastreams" }, { thingId: 3, locationURL: "https://ogcapi.hft-stuttgart.de/sta/udigit4icity/v1.1/Things(3)/Locations", datastreamsURL: "https://ogcapi.hft-stuttgart.de/sta/udigit4icity/v1.1/Things(3)/Datastreams" }, { thingId: 4, locationURL: "https://ogcapi.hft-stuttgart.de/sta/udigit4icity/v1.1/Things(4)/Locations", datastreamsURL: "https://ogcapi.hft-stuttgart.de/sta/udigit4icity/v1.1/Things(4)/Datastreams" }, { thingId: 5, locationURL: "https://ogcapi.hft-stuttgart.de/sta/udigit4icity/v1.1/Things(5)/Locations", datastreamsURL : "https://ogcapi.hft-stuttgart.de/sta/udigit4icity/v1.1/Things(5)/Datastreams" }, ]; +</script> +</body> + -- GitLab