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.
8 * 1. Initialization block
9 * 2. AddToAndRemovFromMap block
10 * 3. LoadTimestamp block
11 * 4. ColorbarMap block
14 export class SimulationLayer
{
15 /** ===== Initialization block ===== */
16 constructor(layerName
, domain
, rasterInfo
) {
17 let cs
= rasterInfo
.coords
;
18 let layerURL
= simVars
.rasterBase
+ rasterInfo
.raster
;
19 let hasColorbar
= ('colorbar' in rasterInfo
);
21 this.layerName
= layerName
;
23 this.hasColorbar
= hasColorbar
;
24 this.imageOverlay
= L
.imageOverlay(layerURL
,
25 [[cs
[0][1], cs
[0][0]], [cs
[2][1], cs
[2][0]]],
27 attribution
: simVars
.organization
,
31 this.preloadedRasters
= {};
32 this.preloadedColorbars
= {};
33 this.timestampsToColorbarMaps
= {};
34 this.coordAndTimestampToColorbarValueCache
= {};
36 this.imgCanvas
= document
.createElement('canvas');
37 this.imgCanvas
.width
= 1;
38 this.imgCanvas
.height
= 1;
40 this.clrbarCanvas
= document
.createElement('canvas');
43 /** ===== AddToAndRemoveFromMap block ===== */
45 let currentTimestamp
= controllers
.currentTimestamp
.getValue();
46 let rastersNow
= simVars
.rasters
[this.domain
][currentTimestamp
];
47 let rasterInfo
= rastersNow
[this.layerName
];
48 let opacity
= controllers
.opacity
.getValue();
50 this.imageOverlay
.addTo(map
);
51 if (!(simVars
.overlayOrder
.includes(this.layerName
))) {
52 simVars
.overlayOrder
.push(this.layerName
);
54 this.imageOverlay
.bringToFront();
55 this.imageOverlay
.setUrl(simVars
.rasterBase
+ rasterInfo
.raster
);
56 this.imageOverlay
.setOpacity(opacity
);
57 if (this.hasColorbar
) {
58 let cbURL
= simVars
.rasterBase
+ rasterInfo
.colorbar
;
59 controllers
.colorbarURL
.setValue(cbURL
);
60 // simVars.setColorbarURL(cbURL);
61 // simVars.showColorbar();
62 simVars
.displayedColorbar
= this.layerName
;
67 if (this.imageOverlay
!= null) {
68 this.imageOverlay
.bringToFront();
73 this.imageOverlay
.remove(map
);
74 simVars
.overlayOrder
.splice(simVars
.overlayOrder
.indexOf(this.layerName
), 1);
78 if (this.imageOverlay
!= null) {
79 this.imageOverlay
.setOpacity(opacity
);
83 /** ===== LoadingTimestamp block ===== */
84 dataArrayToLoadForTimestamp(timestamp
) {
85 if (this.timestampIsPreloaded(timestamp
)) {
89 let raster
= simVars
.rasters
[this.domain
][timestamp
];
90 let rasterInfo
= raster
[this.layerName
];
91 let imgURL
= simVars
.rasterBase
+ rasterInfo
.raster
;
95 layerName
: this.layerName
,
96 layerDomain
: this.domain
,
99 if (this.hasColorbar
) {
100 let colorbarURL
= simVars
.rasterBase
+ rasterInfo
.colorbar
;
102 imageURL
: colorbarURL
,
103 timeStamp
: timestamp
,
104 layerName
: this.layerName
,
105 layerDomain
: this.domain
,
112 timestampIsPreloaded(timestamp
) {
113 let rasterCheck
= this.preloadedRasters
[timestamp
] != null;
114 let colorbarCheck
= true;
115 if (this.hasColorbar
) {
116 colorbarCheck
= this.preloadedColorbars
[timestamp
] != null;
118 return rasterCheck
&& colorbarCheck
;
121 setLayerImagesToTimestamp(timestamp
) {
122 let imageURL
= this.getURLAtTimestamp(timestamp
);
123 this.imageOverlay
.setUrl(imageURL
);
124 if (this.layerName
== simVars
.displayedColorbar
) {
125 let colorbarURL
= this.getColorbarURLAtTimestamp(timestamp
);
126 if (colorbarURL
!= null) {
127 controllers
.colorbarURL
.setValue(colorbarURL
);
128 // simVars.setColorbarURL(colorbarURL);
133 getURLAtTimestamp(timestamp
) {
134 let rastersNow
= simVars
.rasters
[this.domain
][timestamp
];
135 let rasterInfo
= rastersNow
[this.layerName
];
136 let imageURL
= simVars
.rasterBase
+ rasterInfo
.raster
;
137 if (timestamp
in this.preloadedRasters
) {
138 imageURL
= this.preloadedRasters
[timestamp
];
143 getColorbarURLAtTimestamp(timestamp
) {
144 if (!this.hasColorbar
) {
148 let rastersNow
= simVars
.rasters
[this.domain
][timestamp
];
149 let rasterInfo
= rastersNow
[this.layerName
];
150 let colorbarURL
= simVars
.rasterBase
+ rasterInfo
.colorbar
;
151 if (timestamp
in this.preloadedColorbars
) {
152 colorbarURL
= this.preloadedColorbars
[timestamp
];
158 for (let timestamp
in this.preloadedRasters
) {
159 URL
.revokeObjectURL(this.preloadedRasters
[timestamp
]);
161 this.preloadedRasters
= {};
162 for (let timestamp
in this.preloadedColorbars
) {
163 URL
.revokeObjectURL(this.preloadedRasters
[timestamp
]);
165 this.preloadedColorbars
= {};
168 async
setImageLoadedAtTimestamp(timestamp
, imgURL
, colorbar
) {
170 this.imagePreloadedAtTimestamp(timestamp
, '', colorbar
);
174 const img
= new Image();
176 this.imagePreloadedAtTimestamp(timestamp
, imgURL
, colorbar
);
178 img
.onerror
= () => {
179 console
.warn('Problem loading image at url: ' + imgURL
);
184 imagePreloadedAtTimestamp(timestamp
, imgURL
, colorbar
) {
186 this.preloadedColorbars
[timestamp
] = imgURL
;
188 this.preloadedRasters
[timestamp
] = imgURL
;
192 /** ===== ColorbarMap block ===== */
193 async
colorValueAtLocation(timestamp
, coords
) {
194 let key
= timestamp
+ coords
.join(',');
195 if (key
in this.coordAndTimestampToColorbarValueCache
) {
196 return this.coordAndTimestampToColorbarValueCache
[key
];
198 let [r
, g
, b
] = await
this.rgbValueAtLocation(timestamp
, coords
);
199 let colorValue
= null;
200 if ((r
+ g
+ b
) != 0) {
201 colorValue
= await
this.rgbValueToColorbarValue(timestamp
, [r
, g
, b
]);
203 this.coordAndTimestampToColorbarValueCache
[key
] = colorValue
;
207 async
rgbValueAtLocation(timestamp
, coords
) {
208 return new Promise(resolve
=> {
209 let imgURL
= this.getURLAtTimestamp(timestamp
);
210 let [xCoord
, yCoord
] = coords
;
211 let img
= new Image();
214 let imgX
= Math
.floor(xCoord
* img
.naturalWidth
);
215 let imgY
= Math
.floor(yCoord
* img
.naturalHeight
);
217 this.imgCanvas
.getContext('2d').clearRect(0, 0, 1, 1);
219 this.imgCanvas
.getContext('2d').drawImage(img
, imgX
, imgY
, 1, 1, 0, 0, 1, 1);
220 let pixelData
= this.imgCanvas
.getContext('2d').getImageData(0, 0, 1, 1).data
;
222 resolve([pixelData
[0], pixelData
[1], pixelData
[2]])
224 img
.onerror
= () => {
225 console
.warn('Problem loading image at url: ' + imgURL
);
233 async
rgbValueToColorbarValue(timestamp
, rgbValue
) {
234 let colorbarMap
= await
this.generateColorbarMap(timestamp
);
235 let colorbarValue
= this.findClosestKey(rgbValue
, colorbarMap
);
236 return colorbarValue
;
239 async
generateColorbarMap(timestamp
) {
240 return new Promise(resolve
=> {
241 let colorbarMap
= this.timestampsToColorbarMaps
[timestamp
];
242 if (colorbarMap
!= null) {
243 resolve(colorbarMap
);
246 let colorbarURL
= this.getColorbarURLAtTimestamp(timestamp
);
247 let colorbarImg
= new Image();
248 colorbarImg
.onload
= () => {
249 colorbarMap
= this.mapRGBsToColorbarValues(colorbarImg
, timestamp
);
250 this.timestampsToColorbarMaps
[timestamp
] = colorbarMap
;
251 resolve(colorbarMap
);
253 colorbarImg
.onerror
= () => {
254 console
.warn('Problem loading colorbar at url: ' + colorbarURL
);
255 resolve(colorbarMap
);
257 colorbarImg
.src
= colorbarURL
;
261 /** Iterates over all keys in clrbarMap and finds closest one to given rgb values. Returns relative
262 * location in clrbarMap. */
263 findClosestKey(rgb
, clrbarMap
) {
265 if (clrbarMap
== null) {
268 if (r
+ g
+ b
== 0) {
271 const createKey
= (r
, g
, b
) => r
+ ',' + g
+ ',' + b
;
272 const mapKey
= (key
) => key
.split(',').map(str
=> parseInt(str
));
273 let closestKey
= createKey(r
, g
, b
);
274 if (closestKey
in clrbarMap
){
275 return clrbarMap
[closestKey
];
277 let minDiff
= 255*3 + 1;
278 for (let key
in clrbarMap
) {
279 let [rk
, gk
, bk
] = mapKey(key
);
280 let newDiff
= Math
.abs(r
- rk
) + Math
.abs(g
- gk
) + Math
.abs(b
- bk
);
281 if (newDiff
< minDiff
) {
283 closestKey
= createKey(rk
, gk
, bk
);
286 return clrbarMap
[closestKey
];
289 mapRGBsToColorbarValues(colorbarImg
, timestamp
) {
290 this.drawColorbarCanvas(colorbarImg
);
292 if (!this.clrbarCanvas
) {
295 let [left
, right
] = this.getColorbarHorizontalBounds();
296 let horizontalCenterOfColorbar
= Math
.floor((right
+ left
)/2);
297 let [top
, bottom
] = this.getColorbarVerticalBoundsAndMapRGBsToHeights(horizontalCenterOfColorbar
, clrbarMap
);
298 this.convertHeightsToProportionalHeights(clrbarMap
, top
, bottom
);
300 clrbarMap
.start
= top
;
301 clrbarMap
.end
= bottom
;
302 clrbarMap
.right
= right
;
303 clrbarMap
.left
= left
;
305 this.convertProportionalHeightsToColorbarValues(clrbarMap
, timestamp
);
309 getColorbarHorizontalBounds() {
312 let y
= Math
.round(this.clrbarCanvas
.height
/ 2);
313 for (let x
= this.clrbarCanvas
.width
- 1; x
> 0; x
--) {
314 let colorbarData
= this.clrbarCanvas
.getContext('2d').getImageData(x
, y
, 1, 1).data
;
316 if (colorbarData
[0] + colorbarData
[1] + colorbarData
[2] != 0) {
320 if (colorbarData
[0] + colorbarData
[1] + colorbarData
[2] == 0) {
326 return [left
, right
];
329 getColorbarVerticalBoundsAndMapRGBsToHeights(horizontalCenterOfColorbar
, clrbarMap
) {
332 for (let j
= 0; j
< this.clrbarCanvas
.height
; j
++) {
333 let [r
, g
, b
, alpha
] = this.clrbarCanvas
.getContext('2d').getImageData(horizontalCenterOfColorbar
, j
, 1, 1).data
;
335 if (r
+ g
+ b
!= 0) {
339 if (r
+ g
+ b
== 0) {
344 clrbarMap
[r
+ ',' + g
+ ',' + b
] = j
;
347 return [top
, bottom
];
350 convertHeightsToProportionalHeights(clrbarMap
, top
, bottom
) {
351 const computeLocation
= (key
) => 1 - (clrbarMap
[key
] - top
) / (bottom
- top
);
352 for (let rgbKey
in clrbarMap
) {
353 clrbarMap
[rgbKey
] = computeLocation(rgbKey
);
357 convertProportionalHeightsToColorbarValues(clrbarMap
, timestamp
) {
358 if (simVars
.displayedColorbar
== null) {
362 let levels
= this.getColorbarLevelsAtTimestamp(timestamp
);
367 let levelsAreStratified
= this.areColorbarLevelsStratified(clrbarMap
, levels
, levelMap
);
368 let [firstLevel
, lastLevel
] = this.mapColorbarTickLocationsToValues(clrbarMap
, levels
, levelMap
);
370 let slope
= (lastLevel
[1] - firstLevel
[1]) / (lastLevel
[0] - firstLevel
[0]);
371 const interpolate
= (location
) => {
372 if (!levelsAreStratified
) {
373 let val
= slope
*(location
- firstLevel
[0]) + firstLevel
[1];
374 return Math
.round(val
* 100) / 100;
376 // find closest key in levelMap
377 let closestKey
= location
;
379 for (let key
in levelMap
) {
380 let distance
= Math
.abs(key
- location
);
381 if (distance
< minDistance
) {
383 minDistance
= distance
;
386 return levelMap
[closestKey
];
388 for (let color
in clrbarMap
) {
389 clrbarMap
[color
] = interpolate(clrbarMap
[color
]);
393 getColorbarLevelsAtTimestamp(timestamp
) {
394 let rastersAtTimestamp
= simVars
.rasters
[this.domain
][timestamp
];
395 let rasterInfo
= rastersAtTimestamp
[this.layerName
];
396 let levels
= rasterInfo
.levels
;
399 simVars
.noLevels
.add(this.layerName
, this.domain
, utcToLocal(timestamp
));
400 let index
= simVars
.sortedTimestamps
.indexOf(timestamp
);
401 let nearIndex
= index
== 0 ? 1 : index
- 1;
402 let nearTimestamp
= simVars
.sortedTimestamps
[nearIndex
];
403 let nearRastersAtTime
= simVars
.rasters
[this.domain
][nearTimestamp
];
404 let nearRasterInfo
= nearRastersAtTime
[this.layerName
];
405 levels
= nearRasterInfo
.levels
;
411 areColorbarLevelsStratified(clrbarMap
, levels
, levelMap
) {
412 let levelsAreStratified
= false;
413 if (Object
.keys(clrbarMap
).length
- 10 < levels
.length
) {
414 levelsAreStratified
= true;
416 if (levelsAreStratified
) {
419 return levelsAreStratified
;
422 mapColorbarTickLocationsToValues(clrbarMap
, levels
, levelMap
) {
423 let x
= clrbarMap
.left
- 5;
424 let levelIndex
= levels
.length
- 1;
427 const computeLocation
= (y
) => 1 - (y
- clrbarMap
.start
) / (clrbarMap
.end
- clrbarMap
.start
);
428 for (let y
= 0; y
< this.clrbarCanvas
.height
; y
++) {
429 if (levelIndex
< 0) {
432 let colorbarData
= this.clrbarCanvas
.getContext('2d').getImageData(x
, y
, 1, 1).data
;
433 if (colorbarData
[3] != 0) {
435 while (colorbarData
[3] != 0 && y
< this.clrbarCanvas
.height
) {
438 colorbarData
= this.clrbarCanvas
.getContext('2d').getImageData(x
, y
, 1, 1).data
;
440 if (levelIndex
== 0) {
441 y
= y
- (markHeight
- 1);
442 } else if (levelIndex
< (levels
.length
- 1)) {
443 y
= y
- Math
.floor((markHeight
-1)/2);
446 location
= computeLocation(y
);
447 levelMap
[location
] = levels
[levelIndex
];
448 if (lastLevel
.length
== 0) {
449 lastLevel
= [location
, levels
[levelIndex
]];
451 levelIndex
= levelIndex
- 1;
455 let firstLevel
= [location
, levels
[0]];
456 return [firstLevel
, lastLevel
];
459 drawColorbarCanvas(colorbarImg
) {
460 this.clrbarCanvas
.getContext('2d').clearRect(0, 0, this.clrbarCanvas
.width
, this.clrbarCanvas
.height
);
461 if (colorbarImg
== null) {
464 this.clrbarCanvas
.width
= colorbarImg
.width
;
465 this.clrbarCanvas
.height
= colorbarImg
.height
;
466 this.clrbarCanvas
.getContext('2d').drawImage(colorbarImg
, 0, 0, this.clrbarCanvas
.width
, this.clrbarCanvas
.height
);