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(checkBoxParams
);
166 baseMapDiv
.appendChild(mapCheckBox
);
170 createThreadManager() {
171 const imageLoadedCallback
= (loadInfo
) => {
172 const blob
= loadInfo
.blob
;
173 const layerDomain
= loadInfo
.layerDomain
;
174 const layerName
= loadInfo
.layerName
;
175 const timestamp
= loadInfo
.timeStamp
;
176 const colorbar
= loadInfo
.colorbar
;
178 let objectURL
= null;
180 objectURL
= URL
.createObjectURL(blob
);
182 let layer
= this.getLayer(layerDomain
, layerName
);
183 layer
.setImageLoadedAtTimestamp(timestamp
, objectURL
, colorbar
);
185 controllers
.loadingProgress
.frameLoaded();
188 this.threadManager
= new ThreadManager(imageLoadedCallback
);
191 /** ====== Reset block ====== */
193 this.threadManager
.cancelCurrentLoad();
194 for (let currentlyAddedLayerName
of simVars
.overlayOrder
) {
195 let currentlyAddedLayer
= this.activeLayers
[currentlyAddedLayerName
];
196 if (currentlyAddedLayer
!= null) {
197 currentlyAddedLayer
.imageOverlay
.remove(map
);
199 delete this.activeLayers
[currentlyAddedLayerName
];
201 simVars
.displayedColorbar
= null;
202 controllers
.colorbarURL
.setValue(null);
205 resetLayerController() {
206 simVars
.overlayOrder
= [];
207 let presetRasters
= simVars
.presets
.rasters
;
208 if (presetRasters
!= null) {
209 simVars
.overlayOrder
= presetRasters
;
210 simVars
.presets
.rasters
= null;
213 this.rasterDict
= this.clearCache(this.rasterDict
);
214 this.overlayDict
= this.clearCache(this.overlayDict
);
216 this.querySelector('#layer-controller-container').classList
.remove('hidden');
217 document
.querySelector('#copyLink').classList
.remove('hidden');
220 clearCache(domainsToLayersDict
) {
221 for (let domain
in domainsToLayersDict
) {
222 let layersDict
= domainsToLayersDict
[domain
];
223 for (let timestamp
in layersDict
) {
224 let layer
= layersDict
[timestamp
];
231 /** ===== DomainSwitch block ===== */
233 let currentDomain
= controllers
.currentDomain
.getValue();
234 let timestamp
= simVars
.sortedTimestamps
[0];
236 this.initDomainToLayerDictionary(currentDomain
, this.rasterDict
);
237 this.initDomainToLayerDictionary(currentDomain
, this.overlayDict
);
238 let firstRasters
= simVars
.rasters
[currentDomain
][timestamp
];
239 this.makeLayersForDomainAndRasters(currentDomain
, firstRasters
);
241 let previouslyAddedLayerNames
= simVars
.overlayOrder
.filter(overlay
=> {
242 return (overlay
in this.overlayDict
[currentDomain
]) || (overlay
in this.rasterDict
[currentDomain
]);
244 simVars
.overlayOrder
= previouslyAddedLayerNames
;
246 let layerNames
= Object
.keys(firstRasters
);
247 let coords
= firstRasters
[layerNames
[0]].coords
;
248 this.setMapView(coords
);
250 this.createLayerCheckboxes();
253 initDomainToLayerDictionary(domain
, domainToLayerDict
) {
254 if (domainToLayerDict
[domain
] == null) {
255 domainToLayerDict
[domain
] = {};
259 makeLayersForDomainAndRasters(domain
, rasters
) {
260 for (let layerName
in rasters
) {
261 let rasterInfo
= rasters
[layerName
];
262 let layer
= this.getLayer(domain
, layerName
);
264 layer
= new SimulationLayer(layerName
, domain
, rasterInfo
);
265 if(simVars
.overlayList
.indexOf(layerName
) >= 0) {
266 this.overlayDict
[domain
][layerName
] = layer
;
268 this.rasterDict
[domain
][layerName
] = layer
;
275 if (simVars
.presets
.pan
|| simVars
.presets
.zoom
) {
276 this.setPresetMapView();
278 map
.fitBounds([ [coords
[0][1], coords
[0][0]], [coords
[2][1], coords
[2][0]] ]);
283 let pan
= simVars
.presets
.pan
;
284 if (!pan
|| pan
.length
!= 2 || isNaN(pan
[0]) || isNaN(pan
[1])) {
285 let mapCenter
= map
.getCenter();
286 pan
= [mapCenter
.lat
.toFixed(2), mapCenter
.lng
.toFixed(2)];
288 simVars
.presets
.pan
= null;
290 let zoom
= simVars
.presets
.zoom
;
291 if (!zoom
|| isNaN(zoom
)) {
292 zoom
= map
.getZoom();
294 simVars
.presets
.zoom
= null;
296 map
.setView(pan
, zoom
);
299 createLayerCheckboxes() {
300 let currentDomain
= controllers
.currentDomain
.getValue();
302 const rasterRegion
= this.querySelector('#raster-layers');
303 const overlayRegion
= this.querySelector('#overlay-layers');
304 const rasterDiv
= this.querySelector('#raster-checkboxes');
305 const overlayDiv
= this.querySelector('#overlay-checkboxes');
307 this.setVisibilityOfLayerRegion(rasterRegion
, this.rasterDict
);
308 this.setVisibilityOfLayerRegion(overlayRegion
, this.overlayDict
);
310 rasterDiv
.innerHTML
= '';
311 overlayDiv
.innerHTML
= '';
312 let rasterDict
= this.rasterDict
[currentDomain
];
313 let overlayDict
= this.overlayDict
[currentDomain
];
315 this.createCheckBoxesForLayerRegion(rasterDiv
, rasterDict
);
316 this.createCheckBoxesForLayerRegion(overlayDiv
, overlayDict
);
318 for (let layerName
of simVars
.overlayOrder
) {
319 this.addLayerToMap(layerName
);
323 setVisibilityOfLayerRegion(layerRegion
, layerDict
) {
324 layerRegion
.classList
.remove('hidden');
325 if (Object
.keys(layerDict
).length
== 0) {
326 layerRegion
.classList
.add('hidden');
330 createCheckBoxesForLayerRegion(layerDiv
, layerDict
) {
331 const layerClick
= (layerName
) => {
332 if (!simVars
.overlayOrder
.includes(layerName
)) {
333 let startDate
= controllers
.currentTimestamp
.getValue();
334 let endDate
= controllers
.endDate
.getValue();
335 this.addLayerToMap(layerName
);
336 this.loadWithPriority(startDate
, endDate
, simVars
.overlayOrder
);
338 this.removeLayerFromMap(layerName
);
341 for (let layerName
in layerDict
) {
342 let isChecked
= simVars
.overlayOrder
.includes(layerName
);
343 let layerBoxParams
= {
349 callback
: layerClick
,
352 let layerBox
= buildCheckBox(layerBoxParams
);
353 layerDiv
.appendChild(layerBox
);
357 /** ===== AddAndRemoveLayers block ===== */
358 addLayerToMap(layerName
) {
359 // register in currently displayed layers and bring to front if it's an overlay
360 let currentDomain
= controllers
.currentDomain
.getValue();
361 let layer
= this.getLayer(currentDomain
, layerName
);
362 console
.log('name ' + layerName
+ ' layer ' + layer
.imageOverlay
);
363 layer
.addLayerToMap();
364 this.activeLayers
[layerName
] = layer
;
365 // Make sure overlays are still on top
366 for (let overlay
of simVars
.overlayOrder
) {
367 if (simVars
.overlayList
.includes(overlay
)) {
368 let overlayLayer
= this.getLayer(currentDomain
, overlay
);
369 overlayLayer
.bringToFront();
372 if (simVars
.overlayOrder
.length
> 1) {
373 let lastLayerName
= simVars
.overlayOrder
[simVars
.overlayOrder
.length
- 2];
374 let lastLayer
= this.getLayer(currentDomain
, lastLayerName
);
375 lastLayer
.setOpacity(.5);
380 removeLayerFromMap(layerName
) {
381 let currentDomain
= controllers
.currentDomain
.getValue();
382 let removedLayer
= this.getLayer(currentDomain
, layerName
);
383 removedLayer
.removeLayer();
384 delete this.activeLayers
[layerName
];
386 if (simVars
.overlayOrder
.length
> 0) {
387 let topOpacity
= controllers
.opacity
.value
;
388 let lastLayerName
= simVars
.overlayOrder
[simVars
.overlayOrder
.length
- 1];
389 let lastLayer
= this.getLayer(currentDomain
, lastLayerName
);
390 lastLayer
.setOpacity(topOpacity
);
393 this.bringMostRecentColorbarLayerToFront();
395 let startDate
= controllers
.currentTimestamp
.getValue();
396 let endDate
= simVars
.sortedTimestamps
[simVars
.sortedTimestamps
.length
- 1];
397 this.loadWithPriority(startDate
, endDate
, simVars
.overlayOrder
);
402 bringMostRecentColorbarLayerToFront() {
403 let currentTimestamp
= controllers
.currentTimestamp
.getValue();
404 let currentDomain
= controllers
.currentDomain
.getValue();
405 let rasters_now
= simVars
.rasters
[currentDomain
][currentTimestamp
];
406 let mostRecentColorbar
= null;
407 let colorbarSrc
= '';
409 for (let i
= simVars
.overlayOrder
.length
- 1; i
>= 0; i
--) { // iterate over overlayOrder in reverse
410 if ('colorbar' in rasters_now
[simVars
.overlayOrder
[i
]]) {
411 mostRecentColorbar
= simVars
.overlayOrder
[i
];
412 colorbarSrc
= simVars
.rasterBase
+ rasters_now
[simVars
.overlayOrder
[i
]].colorbar
;
417 simVars
.displayedColorbar
= mostRecentColorbar
;
418 controllers
.colorbarURL
.setValue(colorbarSrc
);
421 async
loadWithPriority(startTime
, endTime
, layerNames
) {
422 let startDate
= controllers
.startDate
.getValue();
423 let endDate
= controllers
.endDate
.getValue();
424 let timestampsToLoad
= simVars
.sortedTimestamps
.filter((timestamp
) => {
425 return (timestamp
>= startDate
&& timestamp
<= endDate
);
427 let layersToLoad
= this.getLayersAndSetNumberOfFramesToLoad(layerNames
, timestampsToLoad
.length
);
432 for (let timestamp
of timestampsToLoad
) {
433 for (let layer
of layersToLoad
) {
434 let layerDataArrayToLoad
= layer
.dataArrayToLoadForTimestamp(timestamp
);
435 if (layerDataArrayToLoad
!= null) {
436 if (timestamp
>= startTime
&& timestamp
<= endTime
) {
437 loadNow
= loadNow
.concat(layerDataArrayToLoad
);
439 loadLater
= loadLater
.concat(layerDataArrayToLoad
);
442 let layerFrames
= layer
.hasColorbar
? 2 : 1;
443 preloaded
+= layerFrames
;
448 controllers
.loadingProgress
.frameLoaded(preloaded
);
449 this.threadManager
.loadImages(loadNow
, loadLater
);
452 getLayersAndSetNumberOfFramesToLoad(layerNames
, timestampsToLoad
) {
453 let currentDomain
= controllers
.currentDomain
.getValue();
457 controllers
.loadingProgress
.setValue(0);
458 for (let layerName
of layerNames
) {
459 let layer
= this.getLayer(currentDomain
, layerName
);
460 let layerFrames
= layer
.hasColorbar
? 2 : 1;
461 nFrames
+= layerFrames
* timestampsToLoad
;
464 controllers
.loadingProgress
.setFrames(nFrames
);
469 /** ===== Util block ===== */
470 updateToCurrentTimestamp() {
471 let currentDomain
= controllers
.currentDomain
.getValue();
472 let currentTimestamp
= controllers
.currentTimestamp
.getValue();
473 let shouldLoadAtEnd
= false;
474 for (let addedLayerName
of simVars
.overlayOrder
) {
475 let addedLayer
= this.getLayer(currentDomain
, addedLayerName
);
476 if (!shouldLoadAtEnd
&& !addedLayer
.timestampIsPreloaded(currentTimestamp
)) {
477 shouldLoadAtEnd
= true;
478 this.threadManager
.cancelCurrentLoad();
480 addedLayer
.setLayerImagesToTimestamp(currentTimestamp
);
482 if (shouldLoadAtEnd
) {
483 let endTime
= simVars
.sortedTimestamps
[simVars
.sortedTimestamps
.length
- 1];
484 this.loadWithPriority(currentTimestamp
, endTime
, simVars
.overlayOrder
);
488 getLayer(domain
, name
) {
489 if (simVars
.overlayList
.includes(name
)) {
490 if (this.overlayDict
[domain
] == null) {
493 return this.overlayDict
[domain
][name
];
495 if (this.rasterDict
[domain
] == null) {
498 return this.rasterDict
[domain
][name
];
502 window
.customElements
.define('layer-controller', LayerController
);