REFACTOR: Refactors buildCheckBox util function to take an object
[wrfxweb.git] / fdds / js / components / layerController.js
blob20b1046a0f16ae37e836f760433bc2110758c46d
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';
9 /**
10 * Component that handles adding and removing layers to the map. Provides user with a window
11 * to choose different layers available to add.
13 * Contents
14 * 1. Initialization block
15 * 2. Reset block
16 * 3. DomainSwitch block
17 * 4. AddAndRemoveLayers block
18 * 5. Util block
21 export class LayerController extends HTMLElement {
22 /** ===== Initialization block ===== */
23 constructor() {
24 super();
25 this.innerHTML = `
26 <div id='layer-controller-mobile-wrapper'>
27 <div id='layers-button' class='mobile-button feature-controller'>
28 layers
29 </div>
30 <div id='layer-controller-container' class='feature-controller hidden'>
31 <div id='base-maps'>
32 <h4>Base Maps</h4>
33 <div id='map-checkboxes' class='layer-list'>
34 </div>
35 </div>
36 <div id='raster-layers' class='hidden'>
37 <h4>Rasters</h4>
38 <div id='raster-checkboxes' class='layer-list'>
39 </div>
40 </div>
41 <div id='overlay-layers' class='hidden'>
42 <h4>Overlays</h4>
43 <div id='overlay-checkboxes' class='layer-list'>
44 </div>
45 </div>
46 <div id='opacity-slider-container'>
47 <h4>Top Layer Opacity</h4>
48 </div>
49 </div>
50 </div>
52 this.currentMapType = 'OSM';
53 this.overlayDict = {};
54 this.rasterDict = {};
55 this.activeLayers = {};
56 this.threadManager;
59 connectedCallback() {
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')) {
84 if (IS_MOBILE) {
85 document.querySelector('.catalog-menu').classList.add('hidden');
86 document.querySelector('#domain-selector').classList.add('hidden');
88 layersSelector.classList.remove('hidden');
89 } else {
90 layersSelector.classList.add('hidden');
95 subscribeToCurrentDomain() {
96 const domainSubscription = () => {
97 this.resetLayers();
98 this.switchDomain();
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 = () => {
105 this.resetLayers();
106 this.resetLayerController();
107 this.switchDomain();
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) {
147 layer.addTo(map);
148 this.currentMapType = layerName;
149 layer.bringToFront();
150 } else {
151 layer.remove(map);
154 for (const [layerName, layer] of Object.entries(simVars.baseLayerDict)) {
155 let checked = layerName == this.currentMapType;
156 let checkBoxParams = {
157 id: layerName,
158 text: layerName,
159 type: 'radio',
160 name: 'base',
161 checked: checked,
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;
181 if (blob.size > 0) {
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 ====== */
194 resetLayers() {
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];
227 layer.clearCache();
230 return {};
233 /** ===== DomainSwitch block ===== */
234 switchDomain() {
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);
265 if (layer == null) {
266 layer = new SimulationLayer(layerName, domain, rasterInfo);
267 if(simVars.overlayList.indexOf(layerName) >= 0) {
268 this.overlayDict[domain][layerName] = layer;
269 } else {
270 this.rasterDict[domain][layerName] = layer;
276 setMapView(coords) {
277 if (simVars.presets.pan || simVars.presets.zoom) {
278 this.setPresetMapView();
279 } else {
280 map.fitBounds([ [coords[0][1], coords[0][0]], [coords[2][1], coords[2][0]] ]);
284 setPresetMapView() {
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);
339 } else {
340 this.removeLayerFromMap(layerName);
343 for (let layerName in layerDict) {
344 let isChecked = simVars.overlayOrder.includes(layerName);
345 let layerBoxParams = {
346 id: layerName,
347 text: layerName,
348 type: 'checkbox',
349 name: 'layers',
350 checked: isChecked,
351 callback: layerClick,
352 args: layerName,
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);
381 setURL();
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);
403 setURL();
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;
417 break;
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);
432 let loadNow = [];
433 let loadLater = [];
434 let preloaded = 0;
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);
442 } else {
443 loadLater = loadLater.concat(layerDataArrayToLoad);
445 } else {
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();
458 let nFrames = 0;
459 let layers = [];
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;
466 layers.push(layer);
468 controllers.loadingProgress.setFrames(nFrames);
470 return layers;
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) {
495 return;
497 return this.overlayDict[domain][name];
499 if (this.rasterDict[domain] == null) {
500 return;
502 return this.rasterDict[domain][name];
506 window.customElements.define('layer-controller', LayerController);