1 import { dragElement
, setURL
, buildCheckBox
, IS_MOBILE
} from '../util.js';
2 import { controllerEvents
, controllers
} from './Controller.js';
3 import { OpacitySlider
} from './opacitySlider.js';
4 import { simVars
} from '../simVars.js';
5 import { map
} from '../map.js';
6 import { SimulationLayer
} from './simulationLayer.js';
7 import { ThreadManager
} from '../../threadManager.js';
10 * Component that handles adding and removing layers to the map. Provides user with a window
11 * to choose different layers available to add.
14 * 1. Initialization block
16 * 3. DomainSwitch block
17 * 4. AddAndRemoveLayers block
21 export class LayerController
extends HTMLElement
{
22 /** ===== Initialization block ===== */
26 <div id='layer-controller-mobile-wrapper'>
27 <div id='layers-button' class='mobile-button feature-controller'>
30 <div id='layer-controller-container' class='feature-controller hidden'>
33 <div id='map-checkboxes' class='layer-list'>
36 <div id='raster-layers' class='hidden'>
38 <div id='raster-checkboxes' class='layer-list'>
41 <div id='overlay-layers' class='hidden'>
43 <div id='overlay-checkboxes' class='layer-list'>
46 <div id='opacity-slider-container'>
47 <h4>Top Layer Opacity</h4>
52 this.currentMapType
= 'OSM';
53 this.overlayDict
= {};
55 this.activeLayers
= {};
60 const layerController
= this.querySelector('#layer-controller-container');
61 dragElement(layerController
, '');
62 L
.DomEvent
.disableClickPropagation(layerController
);
63 L
.DomEvent
.disableScrollPropagation(layerController
);
65 this.initializeLayerButton();
67 this.subscribeToCurrentDomain();
68 controllers
.currentTimestamp
.subscribe(() => this.updateToCurrentTimestamp());
69 this.subscribeToTopLayerOpacity();
70 this.subscribeToSimulationStartAndEndDates();
72 this.createOpacitySlider();
73 this.createMapBaseCheckBoxes();
74 this.createThreadManager();
77 initializeLayerButton() {
78 const layersButton
= this.querySelector('#layers-button');
80 L
.DomEvent
.disableClickPropagation(layersButton
);
81 layersButton
.onpointerdown
= (e
) => {
82 const layersSelector
= document
.querySelector('#layer-controller-container');
83 if (layersSelector
.classList
.contains('hidden')) {
85 document
.querySelector('.catalog-menu').classList
.add('hidden');
86 document
.querySelector('#domain-selector').classList
.add('hidden');
88 layersSelector
.classList
.remove('hidden');
90 layersSelector
.classList
.add('hidden');
95 subscribeToCurrentDomain() {
96 const domainSubscription
= () => {
99 let startDate
= controllers
.startDate
.value
;
100 let endDate
= controllers
.endDate
.value
;
101 this.loadWithPriority(startDate
, endDate
, simVars
.overlayOrder
);
102 this.updateToCurrentTimestamp();
104 const domainResetSubscription
= () => {
106 this.resetLayerController();
108 this.updateToCurrentTimestamp();
110 controllers
.currentDomain
.subscribe(domainSubscription
);
111 controllers
.currentDomain
.subscribe(domainResetSubscription
, controllerEvents
.SIM_RESET
);
114 subscribeToSimulationStartAndEndDates() {
115 const reload
= () => {
116 let startDate
= controllers
.startDate
.getValue();
117 let endDate
= controllers
.endDate
.getValue();
118 this.loadWithPriority(startDate
, endDate
, simVars
.overlayOrder
);
120 controllers
.startDate
.subscribe(reload
);
121 controllers
.endDate
.subscribe(reload
);
124 subscribeToTopLayerOpacity() {
125 controllers
.opacity
.subscribe(() => {
126 let newOpacity
= controllers
.opacity
.getValue();
127 if (simVars
.overlayOrder
.length
> 0) {
128 let currentDomain
= controllers
.currentDomain
.getValue();
129 let topLayerName
= simVars
.overlayOrder
[simVars
.overlayOrder
.length
- 1];
130 let topLayer
= this.getLayer(currentDomain
, topLayerName
);
131 topLayer
.setOpacity(newOpacity
);
136 createOpacitySlider() {
137 const opacitySliderContainer
= this.querySelector('#opacity-slider-container');
139 const opacitySlider
= new OpacitySlider(null, controllers
.opacity
);
140 opacitySliderContainer
.appendChild(opacitySlider
);
143 createMapBaseCheckBoxes() {
144 const baseMapDiv
= this.querySelector('#map-checkboxes');
145 const mapCheckCallback
= ([layerName
, layer
]) => {
146 if (layerName
!= this.currentMapType
) {
148 this.currentMapType
= layerName
;
149 layer
.bringToFront();
154 for (const [layerName
, layer
] of Object
.entries(simVars
.baseLayerDict
)) {
155 let checked
= layerName
== this.currentMapType
;
156 let checkBoxParams
= {
162 callback
: mapCheckCallback
,
163 args
: [layerName
, layer
],
165 // let mapCheckBox = buildCheckBox(layerName, 'radio', 'base',
166 // checked, mapCheckCallback, [layerName, layer]);
167 let mapCheckBox
= buildCheckBox(checkBoxParams
);
168 baseMapDiv
.appendChild(mapCheckBox
);
172 createThreadManager() {
173 const imageLoadedCallback
= (loadInfo
) => {
174 const blob
= loadInfo
.blob
;
175 const layerDomain
= loadInfo
.layerDomain
;
176 const layerName
= loadInfo
.layerName
;
177 const timestamp
= loadInfo
.timeStamp
;
178 const colorbar
= loadInfo
.colorbar
;
180 let objectURL
= null;
182 objectURL
= URL
.createObjectURL(blob
);
184 let layer
= this.getLayer(layerDomain
, layerName
);
185 layer
.setImageLoadedAtTimestamp(timestamp
, objectURL
, colorbar
);
187 controllers
.loadingProgress
.frameLoaded();
190 this.threadManager
= new ThreadManager(imageLoadedCallback
);
193 /** ====== Reset block ====== */
195 this.threadManager
.cancelCurrentLoad();
196 for (let currentlyAddedLayerName
of simVars
.overlayOrder
) {
197 let currentlyAddedLayer
= this.activeLayers
[currentlyAddedLayerName
];
198 if (currentlyAddedLayer
!= null) {
199 currentlyAddedLayer
.imageOverlay
.remove(map
);
201 delete this.activeLayers
[currentlyAddedLayerName
];
203 simVars
.displayedColorbar
= null;
204 controllers
.colorbarURL
.setValue(null);
207 resetLayerController() {
208 simVars
.overlayOrder
= [];
209 let presetRasters
= simVars
.presets
.rasters
;
210 if (presetRasters
!= null) {
211 simVars
.overlayOrder
= presetRasters
;
212 simVars
.presets
.rasters
= null;
215 this.rasterDict
= this.clearCache(this.rasterDict
);
216 this.overlayDict
= this.clearCache(this.overlayDict
);
218 this.querySelector('#layer-controller-container').classList
.remove('hidden');
219 document
.querySelector('#copyLink').classList
.remove('hidden');
222 clearCache(domainsToLayersDict
) {
223 for (let domain
in domainsToLayersDict
) {
224 let layersDict
= domainsToLayersDict
[domain
];
225 for (let timestamp
in layersDict
) {
226 let layer
= layersDict
[timestamp
];
233 /** ===== DomainSwitch block ===== */
235 let currentDomain
= controllers
.currentDomain
.getValue();
236 let timestamp
= simVars
.sortedTimestamps
[0];
238 this.initDomainToLayerDictionary(currentDomain
, this.rasterDict
);
239 this.initDomainToLayerDictionary(currentDomain
, this.overlayDict
);
240 let firstRasters
= simVars
.rasters
[currentDomain
][timestamp
];
241 this.makeLayersForDomainAndRasters(currentDomain
, firstRasters
);
243 let previouslyAddedLayerNames
= simVars
.overlayOrder
.filter(overlay
=> {
244 return (overlay
in this.overlayDict
[currentDomain
]) || (overlay
in this.rasterDict
[currentDomain
]);
246 simVars
.overlayOrder
= previouslyAddedLayerNames
;
248 let layerNames
= Object
.keys(firstRasters
);
249 let coords
= firstRasters
[layerNames
[0]].coords
;
250 this.setMapView(coords
);
252 this.createLayerCheckboxes();
255 initDomainToLayerDictionary(domain
, domainToLayerDict
) {
256 if (domainToLayerDict
[domain
] == null) {
257 domainToLayerDict
[domain
] = {};
261 makeLayersForDomainAndRasters(domain
, rasters
) {
262 for (let layerName
in rasters
) {
263 let rasterInfo
= rasters
[layerName
];
264 let layer
= this.getLayer(domain
, layerName
);
266 layer
= new SimulationLayer(layerName
, domain
, rasterInfo
);
267 if(simVars
.overlayList
.indexOf(layerName
) >= 0) {
268 this.overlayDict
[domain
][layerName
] = layer
;
270 this.rasterDict
[domain
][layerName
] = layer
;
277 if (simVars
.presets
.pan
|| simVars
.presets
.zoom
) {
278 this.setPresetMapView();
280 map
.fitBounds([ [coords
[0][1], coords
[0][0]], [coords
[2][1], coords
[2][0]] ]);
285 let pan
= simVars
.presets
.pan
;
286 if (!pan
|| pan
.length
!= 2 || isNaN(pan
[0]) || isNaN(pan
[1])) {
287 let mapCenter
= map
.getCenter();
288 pan
= [mapCenter
.lat
.toFixed(2), mapCenter
.lng
.toFixed(2)];
290 simVars
.presets
.pan
= null;
292 let zoom
= simVars
.presets
.zoom
;
293 if (!zoom
|| isNaN(zoom
)) {
294 zoom
= map
.getZoom();
296 simVars
.presets
.zoom
= null;
298 map
.setView(pan
, zoom
);
301 createLayerCheckboxes() {
302 let currentDomain
= controllers
.currentDomain
.getValue();
304 const rasterRegion
= this.querySelector('#raster-layers');
305 const overlayRegion
= this.querySelector('#overlay-layers');
306 const rasterDiv
= this.querySelector('#raster-checkboxes');
307 const overlayDiv
= this.querySelector('#overlay-checkboxes');
309 this.setVisibilityOfLayerRegion(rasterRegion
, this.rasterDict
);
310 this.setVisibilityOfLayerRegion(overlayRegion
, this.overlayDict
);
312 rasterDiv
.innerHTML
= '';
313 overlayDiv
.innerHTML
= '';
314 let rasterDict
= this.rasterDict
[currentDomain
];
315 let overlayDict
= this.overlayDict
[currentDomain
];
317 this.createCheckBoxesForLayerRegion(rasterDiv
, rasterDict
);
318 this.createCheckBoxesForLayerRegion(overlayDiv
, overlayDict
);
320 for (let layerName
of simVars
.overlayOrder
) {
321 this.addLayerToMap(layerName
);
325 setVisibilityOfLayerRegion(layerRegion
, layerDict
) {
326 layerRegion
.classList
.remove('hidden');
327 if (Object
.keys(layerDict
).length
== 0) {
328 layerRegion
.classList
.add('hidden');
332 createCheckBoxesForLayerRegion(layerDiv
, layerDict
) {
333 const layerClick
= (layerName
) => {
334 if (!simVars
.overlayOrder
.includes(layerName
)) {
335 let startDate
= controllers
.currentTimestamp
.getValue();
336 let endDate
= controllers
.endDate
.getValue();
337 this.addLayerToMap(layerName
);
338 this.loadWithPriority(startDate
, endDate
, simVars
.overlayOrder
);
340 this.removeLayerFromMap(layerName
);
343 for (let layerName
in layerDict
) {
344 let isChecked
= simVars
.overlayOrder
.includes(layerName
);
345 let layerBoxParams
= {
351 callback
: layerClick
,
354 // let layerBox = buildCheckBox(layerName, 'checkbox', 'layers',
355 // isChecked, layerClick, layerName);
356 let layerBox
= buildCheckBox(layerBoxParams
);
357 layerDiv
.appendChild(layerBox
);
361 /** ===== AddAndRemoveLayers block ===== */
362 addLayerToMap(layerName
) {
363 // register in currently displayed layers and bring to front if it's an overlay
364 let currentDomain
= controllers
.currentDomain
.getValue();
365 let layer
= this.getLayer(currentDomain
, layerName
);
366 console
.log('name ' + layerName
+ ' layer ' + layer
.imageOverlay
);
367 layer
.addLayerToMap();
368 this.activeLayers
[layerName
] = layer
;
369 // Make sure overlays are still on top
370 for (let overlay
of simVars
.overlayOrder
) {
371 if (simVars
.overlayList
.includes(overlay
)) {
372 let overlayLayer
= this.getLayer(currentDomain
, overlay
);
373 overlayLayer
.bringToFront();
376 if (simVars
.overlayOrder
.length
> 1) {
377 let lastLayerName
= simVars
.overlayOrder
[simVars
.overlayOrder
.length
- 2];
378 let lastLayer
= this.getLayer(currentDomain
, lastLayerName
);
379 lastLayer
.setOpacity(.5);
384 removeLayerFromMap(layerName
) {
385 let currentDomain
= controllers
.currentDomain
.getValue();
386 let removedLayer
= this.getLayer(currentDomain
, layerName
);
387 removedLayer
.removeLayer();
388 delete this.activeLayers
[layerName
];
390 if (simVars
.overlayOrder
.length
> 0) {
391 let topOpacity
= controllers
.opacity
.value
;
392 let lastLayerName
= simVars
.overlayOrder
[simVars
.overlayOrder
.length
- 1];
393 let lastLayer
= this.getLayer(currentDomain
, lastLayerName
);
394 lastLayer
.setOpacity(topOpacity
);
397 this.bringMostRecentColorbarLayerToFront();
399 let startDate
= controllers
.currentTimestamp
.getValue();
400 let endDate
= simVars
.sortedTimestamps
[simVars
.sortedTimestamps
.length
- 1];
401 this.loadWithPriority(startDate
, endDate
, simVars
.overlayOrder
);
406 bringMostRecentColorbarLayerToFront() {
407 let currentTimestamp
= controllers
.currentTimestamp
.getValue();
408 let currentDomain
= controllers
.currentDomain
.getValue();
409 let rasters_now
= simVars
.rasters
[currentDomain
][currentTimestamp
];
410 let mostRecentColorbar
= null;
411 let colorbarSrc
= '';
413 for (let i
= simVars
.overlayOrder
.length
- 1; i
>= 0; i
--) { // iterate over overlayOrder in reverse
414 if ('colorbar' in rasters_now
[simVars
.overlayOrder
[i
]]) {
415 mostRecentColorbar
= simVars
.overlayOrder
[i
];
416 colorbarSrc
= simVars
.rasterBase
+ rasters_now
[simVars
.overlayOrder
[i
]].colorbar
;
421 simVars
.displayedColorbar
= mostRecentColorbar
;
422 controllers
.colorbarURL
.setValue(colorbarSrc
);
425 async
loadWithPriority(startTime
, endTime
, layerNames
) {
426 let startDate
= controllers
.startDate
.getValue();
427 let endDate
= controllers
.endDate
.getValue();
428 let timestampsToLoad
= simVars
.sortedTimestamps
.filter((timestamp
) => {
429 return (timestamp
>= startDate
&& timestamp
<= endDate
);
431 let layersToLoad
= this.getLayersAndSetNumberOfFramesToLoad(layerNames
, timestampsToLoad
.length
);
436 for (let timestamp
of timestampsToLoad
) {
437 for (let layer
of layersToLoad
) {
438 let layerDataArrayToLoad
= layer
.dataArrayToLoadForTimestamp(timestamp
);
439 if (layerDataArrayToLoad
!= null) {
440 if (timestamp
>= startTime
&& timestamp
<= endTime
) {
441 loadNow
= loadNow
.concat(layerDataArrayToLoad
);
443 loadLater
= loadLater
.concat(layerDataArrayToLoad
);
446 let layerFrames
= layer
.hasColorbar
? 2 : 1;
447 preloaded
+= layerFrames
;
452 controllers
.loadingProgress
.frameLoaded(preloaded
);
453 this.threadManager
.loadImages(loadNow
, loadLater
);
456 getLayersAndSetNumberOfFramesToLoad(layerNames
, timestampsToLoad
) {
457 let currentDomain
= controllers
.currentDomain
.getValue();
461 controllers
.loadingProgress
.setValue(0);
462 for (let layerName
of layerNames
) {
463 let layer
= this.getLayer(currentDomain
, layerName
);
464 let layerFrames
= layer
.hasColorbar
? 2 : 1;
465 nFrames
+= layerFrames
* timestampsToLoad
;
468 controllers
.loadingProgress
.setFrames(nFrames
);
473 /** ===== Util block ===== */
474 updateToCurrentTimestamp() {
475 let currentDomain
= controllers
.currentDomain
.getValue();
476 let currentTimestamp
= controllers
.currentTimestamp
.getValue();
477 let shouldLoadAtEnd
= false;
478 for (let addedLayerName
of simVars
.overlayOrder
) {
479 let addedLayer
= this.getLayer(currentDomain
, addedLayerName
);
480 if (!shouldLoadAtEnd
&& !addedLayer
.timestampIsPreloaded(currentTimestamp
)) {
481 shouldLoadAtEnd
= true;
482 this.threadManager
.cancelCurrentLoad();
484 addedLayer
.setLayerImagesToTimestamp(currentTimestamp
);
486 if (shouldLoadAtEnd
) {
487 let endTime
= simVars
.sortedTimestamps
[simVars
.sortedTimestamps
.length
- 1];
488 this.loadWithPriority(currentTimestamp
, endTime
, simVars
.overlayOrder
);
492 getLayer(domain
, name
) {
493 if (simVars
.overlayList
.includes(name
)) {
494 if (this.overlayDict
[domain
] == null) {
497 return this.overlayDict
[domain
][name
];
499 if (this.rasterDict
[domain
] == null) {
502 return this.rasterDict
[domain
][name
];
506 window
.customElements
.define('layer-controller', LayerController
);