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
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
;
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
,
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 ===== */
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
;
70 if (this.imageOverlay
!= null) {
71 this.imageOverlay
.bringToFront();
76 this.imageOverlay
.remove(map
);
77 simVars
.overlayOrder
.splice(simVars
.overlayOrder
.indexOf(this.layerName
), 1);
81 if (this.imageOverlay
!= null) {
82 this.imageOverlay
.setOpacity(opacity
);
86 /** ===== LoadingTimestamp block ===== */
87 dataArrayToLoadForTimestamp(timestamp
) {
88 if (this.timestampIsPreloaded(timestamp
)) {
92 var raster
= simVars
.rasters
[this.domain
][timestamp
];
93 var rasterInfo
= raster
[this.layerName
];
94 var imgURL
= simVars
.rasterBase
+ rasterInfo
.raster
;
98 layerName
: this.layerName
,
99 layerDomain
: this.domain
,
102 if (this.hasColorbar
) {
103 var colorbarURL
= simVars
.rasterBase
+ rasterInfo
.colorbar
;
105 imageURL
: colorbarURL
,
106 timeStamp
: timestamp
,
107 layerName
: this.layerName
,
108 layerDomain
: this.domain
,
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
];
145 getColorbarURLAtTimestamp(timestamp
) {
146 if (!this.hasColorbar
) {
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
];
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
) {
172 this.imagePreloadedAtTimestamp(timestamp
, '', colorbar
);
176 const img
= new Image();
178 this.imagePreloadedAtTimestamp(timestamp
, imgURL
, colorbar
);
180 img
.onerror
= () => {
181 console
.warn('Problem loading image at url: ' + imgURL
);
186 imagePreloadedAtTimestamp(timestamp
, imgURL
, colorbar
) {
188 this.preloadedColorbars
[timestamp
] = imgURL
;
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
;
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();
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
);
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
);
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
) {
267 if (clrbarMap
== null) {
270 if (r
+ g
+ b
== 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
) {
285 closestKey
= createKey(rk
, gk
, bk
);
288 return clrbarMap
[closestKey
];
291 createMapOfRGBToColorbarValues(colorbarImg
, timeStamp
) {
292 this.drawColorbarCanvas(colorbarImg
);
294 if (!this.clrbarCanvas
) {
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
);
310 getHorizontalBoundsOfColorbar() {
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
;
317 if (colorbarData
[0] + colorbarData
[1] + colorbarData
[2] != 0) {
321 if (colorbarData
[0] + colorbarData
[1] + colorbarData
[2] == 0) {
327 return [left
, right
];
330 getVerticalBoundsOfColorbarAndPopulateMapWithRGBValuesToHeight(horizontalCenterOfColorbar
, clrbarMap
) {
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
;
336 if (r
+ g
+ b
!= 0) {
340 if (r
+ g
+ b
== 0) {
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();
362 if (simVars
.displayedColorbar
== null) {
365 var rastersAtTime
= simVars
.rasters
[currentDomain
][timeStamp
];
366 var rasterInfo
= rastersAtTime
[this.layerName
];
367 var levels
= rasterInfo
.levels
;
368 var x
= clrbarMap
.left
- 5;
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
;
381 var stratified
= false;
382 if (Object
.keys(clrbarMap
).length
- 10 < levels
.length
) {
385 var levelIndex
= levels
.length
- 1;
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) {
396 var colorbarData
= clrbarCanvas
.getContext('2d').getImageData(x
, y
, 1, 1).data
;
397 if (colorbarData
[3] != 0) {
399 while (colorbarData
[3] != 0 && y
< clrbarCanvas
.height
) {
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
]];
416 coord1
= [location
, levels
[levelIndex
]];
418 levelIndex
= levelIndex
- 1;
422 var slope
= (coord2
[1] - coord1
[1]) / (coord2
[0] - coord1
[0]);
423 const interpolate
= (location
) => {
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
;
431 for (var key
in levelMap
) {
432 var distance
= Math
.abs(key
- location
);
433 if (distance
< minDistance
) {
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) {
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
);