Fixes bug that added multiple markers to layers
[wrfxweb.git] / fdds / js / components / timeSeriesController.js
blob59941cd8996a6c1a3062eda0aa589215114745a9
1 import { LayerController } from './layerController.js';
2 import { controllers } from './Controller.js';
3 import { simVars } from '../simVars.js';
4 import { map } from '../map.js';
5 import { Marker } from './timeSeriesMarker.js';
6 import { TimeSeriesButton } from './timeSeriesButton.js';
7 import { buildCheckBox, doubleClick } from '../util.js';
9 const TIMESERIES_BATCHSIZE = 10;
10 const TIMEOUT_MS = 80;
12 /** TimeSeriesController extends LayerController and adds functionality for generating a timeseries
13 * mapping a specific pixel value to its corresponing location on the colorbar over a certain time
14 * range. Uses the layer that is on top. To use, double click on image to bring up
15 * a popup showing the value of the pixel at that particular time stamp and enable button to
16 * generate a timeseries.
18 * Contents
19 * 1. Initialization block
20 * 2. DomainSwitch block
21 * 3. AddAndRemoveLayers block
22 * 4. TimeSeriesGeneration block
23 * 5. Util block
26 export class TimeSeriesController extends LayerController {
27 /** ===== Initialization block ===== */
28 constructor() {
29 super();
30 this.createTimeSeriesLayerGroup();
32 this.totalFramesToLoad = 0;
33 this.framesLoaded = 0;
36 createTimeSeriesLayerGroup() {
37 this.timeSeriesButton = new TimeSeriesButton();
38 this.timeSeriesButton.getButton().disabled = true;
39 this.loadingTimeSeries = false;
41 const container = this.querySelector('#layer-controller-container');
42 const timeSeriesDiv = document.createElement('div');
43 timeSeriesDiv.className = 'layer-group';
44 timeSeriesDiv.id = 'timeseries-layer-group';
45 const h4 = document.createElement('h4');
46 h4.innerText = 'Timeseries over all Markers';
47 const showMarkersCallback = () => {
48 simVars.showMarkers = !simVars.showMarkers;
50 const checkBoxParams = {
51 id: 'show-markers',
52 text: 'Default Show Marker Labels',
53 type: 'checkbox',
54 callback: showMarkersCallback,
55 checked: true,
57 const checkbox = buildCheckBox(checkBoxParams);
58 timeSeriesDiv.appendChild(h4);
59 timeSeriesDiv.appendChild(checkbox);
60 timeSeriesDiv.appendChild(this.timeSeriesButton);
61 container.appendChild(timeSeriesDiv);
64 connectedCallback() {
65 super.connectedCallback();
67 this.initializeTimeSeriesButton();
68 this.subscribeToMarkerController();
71 initializeTimeSeriesButton() {
72 const generateTimeSeriesCallback = async () => {
73 document.body.classList.add('waiting');
74 this.loadingTimeSeries = true;
75 let startDate = this.timeSeriesButton.getStartDate();
76 let endDate = this.timeSeriesButton.getEndDate();
77 let timeSeriesData = await this.generateTimeSeriesData(startDate, endDate);
78 document.body.classList.remove('waiting');
80 simVars.generateTimeSeriesCallback = generateTimeSeriesCallback;
82 const cancelTimeSeriesCallback = () => {
83 this.loadingTimeSeries = false;
85 simVars.cancelTimeSeriesCallback = cancelTimeSeriesCallback;
88 subscribeToMarkerController() {
89 let markerController = controllers.timeSeriesMarkers;
90 markerController.subscribe(() => {
91 if (markerController.getValue().length == 0) {
92 this.timeSeriesButton.getButton().disabled = true;
94 }, markerController.removeEvent);
97 /** ===== DomainSwitch block ===== */
98 switchDomain() {
99 this.timeSeriesButton.updateTimestamps();
100 super.switchDomain();
101 this.removeAllTimeSeriesMarkers();
102 this.resetTimeSeriesButtonDates();
105 removeAllTimeSeriesMarkers() {
106 let timeSeriesMarkers = controllers.timeSeriesMarkers.getValue();
107 for (let marker of timeSeriesMarkers) {
108 marker.hideMarkerInfo();
109 marker.marker.removeFrom(map);
111 controllers.timeSeriesMarkers.value = [];
112 this.timeSeriesButton.getButton().disabled = true;
115 resetTimeSeriesButtonDates() {
116 let startDate = controllers.startDate.getValue();
117 this.timeSeriesButton.setStartDate(startDate);
119 let endDate = controllers.endDate.getValue();
120 this.timeSeriesButton.setEndDate(endDate);
123 /** ===== AddAndRemoveLayers block ===== */
124 addLayerToMap(layerName) {
125 super.addLayerToMap(layerName);
127 this.setUpLayerForTimeSeries(layerName);
130 setUpLayerForTimeSeries(layerName) {
131 let currentDomain = controllers.currentDomain.value;
132 let layer = this.getLayer(currentDomain, layerName);
133 let img = layer.imageOverlay._image;
134 if (layer.hasColorbar) {
135 let doubleClickCallback = (e) => {
136 let latLon = map.mouseEventToLatLng(e);
137 e.stopPropagation(); // needed because otherwise immediately closes the popup
138 let xCoord = e.offsetX / img.width;
139 let yCoord = e.offsetY / img.height;
140 this.createNewMarker(latLon, xCoord, yCoord);
141 this.timeSeriesButton.getButton().disabled = false;
143 doubleClick(img, doubleClickCallback);
144 let timeSeriesMarkers = controllers.timeSeriesMarkers.getValue();
145 if (timeSeriesMarkers.length > 0) {
146 this.timeSeriesButton.getButton().disabled = false;
147 this.updateMarkers();
149 } else {
150 img.style.pointerEvents = 'none';
154 removeLayerFromMap(layerName) {
155 super.removeLayerFromMap(layerName);
157 if (!simVars.displayedColorbar) {
158 this.timeSeriesButton.getButton().disabled = true;
160 this.updateMarkers();
163 /** ===== TimeSeriesGeneration block ===== */
164 createNewMarker(latLon, xCoord, yCoord) {
165 let marker = new Marker(latLon, [xCoord, yCoord]);
166 controllers.timeSeriesMarkers.add(marker);
167 this.updateMarker(marker);
170 generateTimeSeriesData(startDate, endDate) {
171 if (simVars.displayedColorbar == null) {
172 return;
175 document.body.classList.add('waiting');
176 let timeSeriesMarkers = controllers.timeSeriesMarkers.getValue();
177 controllers.timeSeriesProgress.setValue(0);
178 this.framesLoaded = 0;
180 let timestampsToLoad = simVars.sortedTimestamps.filter(timestamp => timestamp >= startDate && timestamp <= endDate);
181 let layersForTimeSeries = this.getLayersToGenerateTimeSeriesOver();
182 this.totalFramesToLoad = timestampsToLoad.length * timeSeriesMarkers.length * layersForTimeSeries.length;
184 let layerData = {};
185 let timeSeriesGenerationInfo = {
186 layersForTimeSeries: layersForTimeSeries,
187 timeSeriesMarkers: timeSeriesMarkers,
188 timestampsToLoad: timestampsToLoad
190 this.batchLoadTimeSeries(layerData, timeSeriesGenerationInfo);
193 getLayersToGenerateTimeSeriesOver() {
194 let layerSpecification = this.timeSeriesButton.getLayerSpecification();
195 let currentDomain = controllers.currentDomain.getValue();
197 let colorbarLayers = simVars.overlayOrder.map(layerName => {
198 return this.getLayer(currentDomain, layerName)
199 }).filter(layer => {
200 if (layerSpecification == 'top-layer') {
201 return layer.layerName == simVars.displayedColorbar;
203 return layer.hasColorbar;
206 return colorbarLayers;
209 async batchLoadTimeSeries(layerData, timeSeriesGenerationInfo, index = 0, batchSize = TIMESERIES_BATCHSIZE) {
210 if (!this.loadingTimeSeries) {
211 document.body.classList.remove('waiting');
212 return;
214 let {timestampsToLoad} = timeSeriesGenerationInfo;
215 let batchEnd = Math.min(index + batchSize, timestampsToLoad.length);
216 await this.loadTimeSeriesBatch(layerData, timeSeriesGenerationInfo, index, batchEnd);
218 if (batchEnd < timestampsToLoad.length) {
219 const batchLoadAfterTimeout = () => {
220 this.batchLoadTimeSeries(layerData, timeSeriesGenerationInfo, batchEnd);
222 setTimeout(batchLoadAfterTimeout, TIMEOUT_MS);
224 } else {
225 document.body.classList.remove('waiting');
227 const timeSeriesChart = document.querySelector('timeseries-chart');
228 timeSeriesChart.populateChart(layerData);
229 return layerData;
233 async loadTimeSeriesBatch(layerData, timeSeriesGenerationInfo, index, batchEnd) {
234 let {layersForTimeSeries, timeSeriesMarkers, timestampsToLoad } = timeSeriesGenerationInfo;
235 let timestampBatch = timestampsToLoad.slice(index, batchEnd);
237 for (let colorbarLayer of layersForTimeSeries) {
238 let layerName = colorbarLayer.layerName;
239 let timeSeriesData = (index == 0) ? [] : layerData[layerName];
240 for (let markerIndex = 0; markerIndex < timeSeriesMarkers.length; markerIndex++) {
241 let marker = timeSeriesMarkers[markerIndex];
242 let dataEntry = await this.generateTimeSeriesDataForLayerAndMarker(colorbarLayer, marker, timestampBatch);
243 if (index == 0) {
244 timeSeriesData.push(dataEntry);
245 } else {
246 timeSeriesData[markerIndex].dataset = {...timeSeriesData[markerIndex].dataset, ...dataEntry.dataset };
249 layerData[layerName] = timeSeriesData;
253 async generateTimeSeriesDataForLayerAndMarker(colorbarLayer, marker, filteredTimestamps) {
254 let timeSeriesMarker = marker.getContent();
255 let dataType = this.timeSeriesButton.getDataType();
256 let dataEntry = ({label: timeSeriesMarker.getName(), latLon: marker._latlng, color: timeSeriesMarker.getChartColor(),
257 dataset: {}, hidden: timeSeriesMarker.hideOnChart});
258 for (let timestamp of filteredTimestamps) {
259 let coords = marker.imageCoords;
260 let colorbarValue = await colorbarLayer.colorValueAtLocation(timestamp, coords);
261 if (colorbarValue == null && dataType == 'continuous') {
262 colorbarValue = 0;
264 dataEntry.dataset[timestamp] = colorbarValue;
265 this.framesLoaded += 1;
266 let progress = this.framesLoaded / this.totalFramesToLoad;
267 controllers.timeSeriesProgress.setValue(progress);
270 return dataEntry;
273 /** ===== Util block ===== */
274 updateToCurrentTimestamp() {
275 super.updateToCurrentTimestamp();
276 this.updateMarkers();
279 updateMarkers() {
280 let timeSeriesMarkers = controllers.timeSeriesMarkers.getValue();
281 for (let marker of timeSeriesMarkers) {
282 this.updateMarker(marker);
286 async updateMarker(marker) {
287 let rgb = [0, 0, 0];
288 let clrbarLocation = null;
289 if (simVars.displayedColorbar != null) {
290 let currentDomain = controllers.currentDomain.value;
291 let currentTimestamp = controllers.currentTimestamp.value;
292 let layer = this.getLayer(currentDomain, simVars.displayedColorbar);
294 rgb = await layer.rgbValueAtLocation(currentTimestamp, marker.imageCoords);
295 clrbarLocation = await layer.rgbValueToColorbarValue(currentTimestamp, rgb);
297 marker.getContent().setRGBValues(rgb, clrbarLocation);
301 window.customElements.define('timeseries-controller', TimeSeriesController);