For Lidar profiles, starts the simulation at the latest timestamp
[wrfxweb.git] / fdds / js / components / layerController.js
blob1a05dae707d410f25c0f5b13f1e9d2c7701fd26c
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(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;
179 if (blob.size > 0) {
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 ====== */
192 resetLayers() {
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];
225 layer.clearCache();
228 return {};
231 /** ===== DomainSwitch block ===== */
232 switchDomain() {
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);
263 if (layer == null) {
264 layer = new SimulationLayer(layerName, domain, rasterInfo);
265 if(simVars.overlayList.indexOf(layerName) >= 0) {
266 this.overlayDict[domain][layerName] = layer;
267 } else {
268 this.rasterDict[domain][layerName] = layer;
274 setMapView(coords) {
275 if (simVars.presets.pan || simVars.presets.zoom) {
276 this.setPresetMapView();
277 } else {
278 map.fitBounds([ [coords[0][1], coords[0][0]], [coords[2][1], coords[2][0]] ]);
282 setPresetMapView() {
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);
337 } else {
338 this.removeLayerFromMap(layerName);
341 for (let layerName in layerDict) {
342 let isChecked = simVars.overlayOrder.includes(layerName);
343 let layerBoxParams = {
344 id: layerName,
345 text: layerName,
346 type: 'checkbox',
347 name: 'layers',
348 checked: isChecked,
349 callback: layerClick,
350 args: layerName,
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);
377 setURL();
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);
399 setURL();
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;
413 break;
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);
428 let loadNow = [];
429 let loadLater = [];
430 let preloaded = 0;
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);
438 } else {
439 loadLater = loadLater.concat(layerDataArrayToLoad);
441 } else {
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();
454 let nFrames = 0;
455 let layers = [];
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;
462 layers.push(layer);
464 controllers.loadingProgress.setFrames(nFrames);
466 return layers;
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) {
491 return;
493 return this.overlayDict[domain][name];
495 if (this.rasterDict[domain] == null) {
496 return;
498 return this.rasterDict[domain][name];
502 window.customElements.define('layer-controller', LayerController);