REFACTOR Renames functions in simulationLayer for better clarity
[wrfxweb.git] / fdds / js / components / simulationLayer.js
blobf448dd32bead2d12bd32a9a55bda28278bd2ff4c
1 import { simVars } from '../simVars.js';
2 import { controllers } from './Controller.js';
3 import { map } from '../map.js';
4 import { utcToLocal } from '../util.js';
6 /** Layer for a specific domain.
7 * Contents
8 * 1. Initialization block
9 * 2. AddToAndRemovFromMap block
10 * 3. LoadTimestamp block
11 * 4. ColorbarMap block
15 export class SimulationLayer {
16 /** ===== Initialization block ===== */
17 constructor(layerName, domain, rasterInfo) {
18 var cs = rasterInfo.coords;
19 var layerURL = simVars.rasterBase + rasterInfo.raster;
20 var hasColorbar = ('colorbar' in rasterInfo);
21 const rasterColorbar = document.querySelector('#raster-colorbar');
23 this.layerName = layerName;
24 this.domain = domain;
25 this.hasColorbar = hasColorbar;
26 this.rasterColorbar = rasterColorbar;
27 this.imageOverlay = L.imageOverlay(layerURL,
28 [[cs[0][1], cs[0][0]], [cs[2][1], cs[2][0]]],
30 attribution: simVars.organization,
31 opacity: 0.5,
32 interactive: true
33 });
34 this.preloadedRasters = {};
35 this.preloadedColorbars = {};
36 this.timestampsToColorbarMaps = {};
37 this.coordAndTimestampToColorbarValueCache = {};
39 this.imgCanvas = document.createElement('canvas');
40 this.imgCanvas.width = 1;
41 this.imgCanvas.height = 1;
43 this.clrbarCanvas = document.createElement('canvas');
46 /** ===== AddToAndRemoveFromMap block ===== */
47 addLayerToMap() {
48 var currentTimestamp = controllers.currentTimestamp.getValue();
49 var rastersNow = simVars.rasters[this.domain][currentTimestamp];
50 var rasterInfo = rastersNow[this.layerName];
51 var opacity = controllers.opacity.getValue();
53 this.imageOverlay.addTo(map);
54 if (!(simVars.overlayOrder.includes(this.layerName))) {
55 simVars.overlayOrder.push(this.layerName);
57 this.imageOverlay.bringToFront();
58 this.imageOverlay.setUrl(simVars.rasterBase + rasterInfo.raster);
59 this.imageOverlay.setOpacity(opacity);
60 if (this.hasColorbar) {
61 var cbURL = simVars.rasterBase + rasterInfo.colorbar;
62 // const rasterColorbar = document.querySelector('#raster-colorbar');
63 this.rasterColorbar.src = cbURL;
64 this.rasterColorbar.style.display = 'block';
65 simVars.displayedColorbar = this.layerName;
69 bringToFront() {
70 if (this.imageOverlay != null) {
71 this.imageOverlay.bringToFront();
75 removeLayer() {
76 this.imageOverlay.remove(map);
77 simVars.overlayOrder.splice(simVars.overlayOrder.indexOf(this.layerName), 1);
80 setOpacity(opacity) {
81 if (this.imageOverlay != null) {
82 this.imageOverlay.setOpacity(opacity);
86 /** ===== LoadingTimestamp block ===== */
87 dataArrayToLoadForTimestamp(timestamp) {
88 if (this.timestampIsPreloaded(timestamp)) {
89 return null;
91 var toLoad = [];
92 var raster = simVars.rasters[this.domain][timestamp];
93 var rasterInfo = raster[this.layerName];
94 var imgURL = simVars.rasterBase + rasterInfo.raster;
95 toLoad.push({
96 imageURL: imgURL,
97 timeStamp: timestamp,
98 layerName: this.layerName,
99 layerDomain: this.domain,
100 colorbar: false
102 if (this.hasColorbar) {
103 var colorbarURL = simVars.rasterBase + rasterInfo.colorbar;
104 toLoad.push({
105 imageURL: colorbarURL,
106 timeStamp: timestamp,
107 layerName: this.layerName,
108 layerDomain: this.domain,
109 colorbar: true
112 return toLoad;
115 timestampIsPreloaded(timestamp) {
116 var rasterCheck = this.preloadedRasters[timestamp] != null;
117 var colorbarCheck = true;
118 if (this.hasColorbar) {
119 colorbarCheck = this.preloadedColorbars[timestamp] != null;
121 return rasterCheck && colorbarCheck;
124 setLayerImagesToTimestamp(timestamp) {
125 var imageURL = this.getURLAtTimestamp(timestamp);
126 this.imageOverlay.setUrl(imageURL);
127 if (this.layerName == simVars.displayedColorbar) {
128 var colorbarURL = this.getColorbarURLAtTimestamp(timestamp);
129 if (colorbarURL != null) {
130 this.rasterColorbar.src = colorbarURL;
135 getURLAtTimestamp(timestamp) {
136 var rastersNow = simVars.rasters[this.domain][timestamp];
137 var rasterInfo = rastersNow[this.layerName];
138 var imageURL = simVars.rasterBase + rasterInfo.raster;
139 if (timestamp in this.preloadedRasters) {
140 imageURL = this.preloadedRasters[timestamp];
142 return imageURL;
145 getColorbarURLAtTimestamp(timestamp) {
146 if (!this.hasColorbar) {
147 return;
150 var rastersNow = simVars.rasters[this.domain][timestamp];
151 var rasterInfo = rastersNow[this.layerName];
152 var colorbarURL = simVars.rasterBase + rasterInfo.colorbar;
153 if (timestamp in this.preloadedColorbars) {
154 colorbarURL = this.preloadedColorbars[timestamp];
156 return colorbarURL;
159 clearCache() {
160 for (var timestamp in this.preloadedRasters) {
161 URL.revokeObjectURL(this.preloadedRasters[timestamp]);
163 this.preloadedRasters = {};
164 for (var timestamp in this.preloadedColorbars) {
165 URL.revokeObjectURL(this.preloadedRasters[timestamp]);
167 this.preloadedColorbars = {};
170 async setImageLoadedAtTimestamp(timestamp, imgURL, colorbar) {
171 if (!imgURL) {
172 this.imagePreloadedAtTimestamp(timestamp, '', colorbar);
173 return;
176 const img = new Image();
177 img.onload = () => {
178 this.imagePreloadedAtTimestamp(timestamp, imgURL, colorbar);
180 img.onerror = () => {
181 console.warn('Problem loading image at url: ' + imgURL);
183 img.src = imgURL;
186 imagePreloadedAtTimestamp(timestamp, imgURL, colorbar) {
187 if (colorbar) {
188 this.preloadedColorbars[timestamp] = imgURL;
189 } else {
190 this.preloadedRasters[timestamp] = imgURL;
194 /** ===== ColorbarMap block ===== */
195 async colorValueAtLocation(timestamp, coords) {
196 var key = timestamp + coords.join(',');
197 if (key in this.coordAndTimestampToColorbarValueCache) {
198 return this.coordAndTimestampToColorbarValueCache[key];
200 var [r, g, b] = await this.rgbValueAtLocation(timestamp, coords);
201 var colorValue = null;
202 if ((r + g + b) != 0) {
203 colorValue = await this.rgbValueToColorbarValue(timestamp, [r, g, b]);
205 this.coordAndTimestampToColorbarValueCache[key] = colorValue;
206 return colorValue;
209 async rgbValueAtLocation(timestamp, coords) {
210 return new Promise(resolve => {
211 var imgURL = this.getURLAtTimestamp(timestamp);
212 var [xCoord, yCoord] = coords;
213 var img = new Image();
215 img.onload = () => {
216 var imgX = Math.floor(xCoord * img.naturalWidth);
217 var imgY = Math.floor(yCoord * img.naturalHeight);
219 this.imgCanvas.getContext('2d').clearRect(0, 0, 1, 1);
221 this.imgCanvas.getContext('2d').drawImage(img, imgX, imgY, 1, 1, 0, 0, 1, 1);
222 var pixelData = this.imgCanvas.getContext('2d').getImageData(0, 0, 1, 1).data;
224 resolve([pixelData[0], pixelData[1], pixelData[2]])
226 img.onerror = () => {
227 console.warn('Problem loading image at url: ' + imgURL);
228 resolve([0, 0, 0]);
231 img.src = imgURL;
235 async rgbValueToColorbarValue(timestamp, rgbValue) {
236 var colorbarMap = await this.generateColorbarMap(timestamp);
237 var colorbarValue = this.findClosestKey(rgbValue, colorbarMap);
238 return colorbarValue;
241 async generateColorbarMap(timestamp) {
242 return new Promise(resolve => {
243 var colorbarMap = this.timestampsToColorbarMaps[timestamp];
244 if (colorbarMap != null) {
245 resolve(colorbarMap);
246 return;
248 var colorbarURL = this.getColorbarURLAtTimestamp(timestamp);
249 var colorbarImg = new Image();
250 colorbarImg.onload = () => {
251 colorbarMap = this.createMapOfRGBToColorbarValues(colorbarImg, timestamp);
252 this.timestampsToColorbarMaps[timestamp] = colorbarMap;
253 resolve(colorbarMap);
255 colorbarImg.onerror = () => {
256 console.warn('Problem loading colorbar at url: ' + colorbarURL);
257 resolve(colorbarMap);
259 colorbarImg.src = colorbarURL;
263 /** Iterates over all keys in clrbarMap and finds closest one to given rgb values. Returns relative
264 * location in clrbarMap. */
265 findClosestKey(rgb, clrbarMap) {
266 var [r, g, b] = rgb;
267 if (clrbarMap == null) {
268 return null;
270 if (r + g + b == 0) {
271 return 0;
273 const createKey = (r, g, b) => r + ',' + g + ',' + b;
274 const mapKey = (key) => key.split(',').map(str => parseInt(str));
275 var closestKey = createKey(r, g, b);
276 if (closestKey in clrbarMap){
277 return clrbarMap[closestKey];
279 var minDiff = 255*3 + 1;
280 for (var key in clrbarMap) {
281 var [rk, gk, bk] = mapKey(key);
282 var newDiff = Math.abs(r - rk) + Math.abs(g - gk) + Math.abs(b - bk);
283 if (newDiff < minDiff) {
284 minDiff = newDiff;
285 closestKey = createKey(rk, gk, bk);
288 return clrbarMap[closestKey];
291 createMapOfRGBToColorbarValues(colorbarImg, timeStamp) {
292 this.drawColorbarCanvas(colorbarImg);
293 let clrbarMap = {};
294 if (!this.clrbarCanvas) {
295 return clrbarMap;
297 let [left, right] = this.getHorizontalBoundsOfColorbar();
298 let horizontalCenterOfColorbar = Math.floor((right + left)/2);
299 let [top, bottom] = this.getVerticalBoundsOfColorbarAndPopulateMapWithRGBValuesToHeight(horizontalCenterOfColorbar, clrbarMap);
300 this.convertHeightValuesInMapToProportionalHeights(clrbarMap, top, bottom);
302 clrbarMap.start = top;
303 clrbarMap.end = bottom;
304 clrbarMap.right = right;
305 clrbarMap.left = left;
306 this.mapLevels(clrbarMap, timeStamp);
307 return clrbarMap;
310 getHorizontalBoundsOfColorbar() {
311 let right = 0;
312 let left = 0;
313 let y = Math.round(this.clrbarCanvas.height / 2);
314 for (let x = this.clrbarCanvas.width - 1; x > 0; x--) {
315 let colorbarData = this.clrbarCanvas.getContext('2d').getImageData(x, y, 1, 1).data;
316 if (right == 0) {
317 if (colorbarData[0] + colorbarData[1] + colorbarData[2] != 0) {
318 right = x;
320 } else {
321 if (colorbarData[0] + colorbarData[1] + colorbarData[2] == 0) {
322 left = x;
323 break;
327 return [left, right];
330 getVerticalBoundsOfColorbarAndPopulateMapWithRGBValuesToHeight(horizontalCenterOfColorbar, clrbarMap) {
331 let top = 0;
332 let bottom = 0;
333 for (var j = 0; j < this.clrbarCanvas.height; j++) {
334 let [r, g, b, alpha] = this.clrbarCanvas.getContext('2d').getImageData(horizontalCenterOfColorbar, j, 1, 1).data;
335 if (top == 0) {
336 if (r + g + b != 0) {
337 top = j + 1;
339 } else {
340 if (r + g + b == 0) {
341 bottom = j - 1;
342 break;
345 clrbarMap[r + ',' + g + ',' + b] = j;
348 return [top, bottom];
351 convertHeightValuesInMapToProportionalHeights(clrbarMap, top, bottom) {
352 const computeLocation = (key) => 1 - (clrbarMap[key] - top) / (bottom - top);
353 for (var rgbKey in clrbarMap) {
354 clrbarMap[rgbKey] = computeLocation(rgbKey);
358 mapLevels(clrbarMap, timeStamp) {
359 let clrbarCanvas = this.clrbarCanvas;
360 var currentDomain = controllers.currentDomain.getValue();
361 var levelMap = {};
362 if (simVars.displayedColorbar == null) {
363 return;
365 var rastersAtTime = simVars.rasters[currentDomain][timeStamp];
366 var rasterInfo = rastersAtTime[this.layerName];
367 var levels = rasterInfo.levels;
368 var x = clrbarMap.left - 5;
369 if (!levels) {
370 simVars.noLevels.add(simVars.displayedColorbar, currentDomain, utcToLocal(timeStamp));
371 var index = simVars.sortedTimestamps.indexOf(timeStamp);
372 var nearIndex = index == 0 ? 1 : index - 1;
373 var nearTimestamp = simVars.sortedTimestamps[nearIndex];
374 var nearRastersAtTime = simVars.rasters[currentDomain][nearTimestamp];
375 var nearRasterInfo = nearRastersAtTime[simVars.displayedColorbar];
376 levels = nearRasterInfo.levels;
377 if (!levels) {
378 return;
381 var stratified = false;
382 if (Object.keys(clrbarMap).length - 10 < levels.length) {
383 stratified = true;
385 var levelIndex = levels.length - 1;
386 if (stratified) {
387 levelMap[0] = 0;
389 var coord1 = [];
390 var coord2 = [];
391 const computeLocation = (y) => 1 - (y - clrbarMap.start) / (clrbarMap.end - clrbarMap.start);
392 for (var y = 0; y < clrbarCanvas.height; y++) {
393 if (levelIndex < 0) {
394 break;
396 var colorbarData = clrbarCanvas.getContext('2d').getImageData(x, y, 1, 1).data;
397 if (colorbarData[3] != 0) {
398 var markHeight = 0;
399 while (colorbarData[3] != 0 && y < clrbarCanvas.height) {
400 y += 1;
401 markHeight += 1;
402 colorbarData = clrbarCanvas.getContext('2d').getImageData(x, y, 1, 1).data;
404 if (levelIndex == 0) {
405 y = y - (markHeight - 1);
406 } else if (levelIndex < (levels.length - 1)) {
407 y = y - Math.floor((markHeight-1)/2);
410 var location = computeLocation(y);
411 levelMap[location] = levels[levelIndex];
412 if (coord2.length == 0) {
413 coord2 = [location, levels[levelIndex]];
415 else {
416 coord1 = [location, levels[levelIndex]];
418 levelIndex = levelIndex - 1;
419 y += 5;
422 var slope = (coord2[1] - coord1[1]) / (coord2[0] - coord1[0]);
423 const interpolate = (location) => {
424 if (!stratified) {
425 var val = slope*(location - coord1[0]) + coord1[1];
426 return Math.round(val * 100) / 100;
428 // find closest key in levelMap
429 var closestKey = location;
430 var minDistance = 1;
431 for (var key in levelMap) {
432 var distance = Math.abs(key - location);
433 if (distance < minDistance) {
434 closestKey = key;
435 minDistance = distance;
438 return levelMap[closestKey];
440 for (var color in clrbarMap) {
441 clrbarMap[color] = interpolate(clrbarMap[color]);
445 drawColorbarCanvas(colorbarImg) {
446 this.clrbarCanvas.getContext('2d').clearRect(0, 0, this.clrbarCanvas.width, this.clrbarCanvas.height);
447 if (colorbarImg == null) {
448 return;
450 this.clrbarCanvas.width = colorbarImg.width;
451 this.clrbarCanvas.height = colorbarImg.height;
452 this.clrbarCanvas.getContext('2d').drawImage(colorbarImg, 0, 0, this.clrbarCanvas.width, this.clrbarCanvas.height);