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.
19 * 1. Initialization block
20 * 2. DomainSwitch block
21 * 3. AddAndRemoveLayers block
22 * 4. TimeSeriesGeneration block
26 export class TimeSeriesController
extends LayerController
{
27 /** ===== Initialization block ===== */
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
= {
52 text
: 'Default Show Marker Labels',
54 callback
: showMarkersCallback
,
57 const checkbox
= buildCheckBox(checkBoxParams
);
58 timeSeriesDiv
.appendChild(h4
);
59 timeSeriesDiv
.appendChild(checkbox
);
60 timeSeriesDiv
.appendChild(this.timeSeriesButton
);
61 container
.appendChild(timeSeriesDiv
);
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 ===== */
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();
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) {
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
;
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
)
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');
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
);
225 document
.body
.classList
.remove('waiting');
227 const timeSeriesChart
= document
.querySelector('timeseries-chart');
228 timeSeriesChart
.populateChart(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
);
244 timeSeriesData
.push(dataEntry
);
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') {
264 dataEntry
.dataset
[timestamp
] = colorbarValue
;
265 this.framesLoaded
+= 1;
266 let progress
= this.framesLoaded
/ this.totalFramesToLoad
;
267 controllers
.timeSeriesProgress
.setValue(progress
);
273 /** ===== Util block ===== */
274 updateToCurrentTimestamp() {
275 super.updateToCurrentTimestamp();
276 this.updateMarkers();
280 let timeSeriesMarkers
= controllers
.timeSeriesMarkers
.getValue();
281 for (let marker
of timeSeriesMarkers
) {
282 this.updateMarker(marker
);
286 async
updateMarker(marker
) {
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
);