diff --git a/src/TempoLite.vue b/src/TempoLite.vue index 688c05c..26cc943 100644 --- a/src/TempoLite.vue +++ b/src/TempoLite.vue @@ -146,6 +146,20 @@

TEMPO Field of Regard
+ +
+ + +
-
+
+
+ + +

+ The cloud mask shows where the satellite could not measure NO2 because of cloud cover. +

+
+
-
TEMPO opacity
+
Overlay opacity
@@ -514,12 +543,13 @@
-
- Smithsonian Logo - -
+ + +
+ Smithsonian Logo +
@@ -555,7 +585,7 @@ import { getTimestamps } from "./timestamps"; const erdTimestamps: number[] = []; const newTimestamps: number[] = []; - +const cloudTimestamps: number[] = []; const fosterTimestamps = [ 1698838920000, @@ -761,6 +791,13 @@ export default defineComponent({ loadedImagesProgress: 0, useHighRes: false, + + cloudOverlay: new L.ImageOverlay("", novDecBounds, { + opacity, + interactive: false, + }), + cloudTimestamps, + showClouds: true, }; }, @@ -804,6 +841,7 @@ export default defineComponent({ this.singleDateSelected = this.uniqueDays[this.uniqueDays.length-1].value; this.imageOverlay.setUrl(this.imageUrl).addTo(this.map as Map); + this.cloudOverlay.setUrl(this.cloudUrl).addTo(this.map as Map); this.updateFieldOfRegard(); if (this.showFieldOfRegard) { @@ -917,6 +955,21 @@ export default defineComponent({ return url + this.imageName; }, + cloudUrl(): string { + if (!this.showClouds) { + return ''; + } + + if (this.cloudTimestamps.includes(this.timestamp)) { + return this.getCloudFilename(this.date); + } + return ''; + }, + + cloudDataAvailable(): boolean { + return this.cloudTimestamps.includes(this.timestamp); + }, + whichDataSet(): string { if (this.fosterTimestamps.includes(this.timestamp)) { return 'TEMPO-lite'; @@ -1046,6 +1099,7 @@ export default defineComponent({ }, updateBounds() { this.imageOverlay.setBounds(this.imageBounds); + this.cloudOverlay.setBounds(this.imageBounds); }, // preloadImages(images: string[]) { @@ -1058,9 +1112,19 @@ export default defineComponent({ this.erdTimestamps = ts.early_release; this.newTimestamps = ts.released; this.timestamps = this.timestamps.concat(this.erdTimestamps, this.newTimestamps).sort(); + this.cloudTimestamps = ts.clouds; }); }, + getCloudFilename(date: Date): string { + const filename = this.getTempoFilename(date); + if (this.useHighRes) { + return 'https://raw.githubusercontent.com/johnarban/tempo-data-holdings/main/clouds/images/' + filename; + } else { + return 'https://raw.githubusercontent.com/johnarban/tempo-data-holdings/main/clouds/images/resized_images/' + filename; + } + }, + getTempoFilename(date: Date): string { return `tempo_${date.getUTCFullYear()}-${zpad(date.getUTCMonth()+1)}-${zpad(date.getUTCDate())}T${zpad(date.getUTCHours())}h${zpad(date.getUTCMinutes())}m.png`; }, @@ -1114,6 +1178,8 @@ export default defineComponent({ console.log('preloading images for ', this.thumbLabel); const times = this.timestamps.slice(this.minIndex, this.maxIndex + 1); const images = times.map(ts => this.getTempoDataUrl(ts) + this.getTempoFilename(new Date(ts))); + const cloudImages = times.filter(ts => this.cloudTimestamps.includes(ts)).map(ts => this.getCloudFilename(new Date(ts))); + images.push(...cloudImages); const promises = _preloadImages(images); let loaded = 0; this.loadedImagesProgress = 0; @@ -1170,6 +1236,10 @@ export default defineComponent({ this.updateFieldOfRegard(); }, + cloudUrl(url: string) { + this.cloudOverlay.setUrl(url); + }, + useHighRes() { this.imagePreload(); }, @@ -1221,6 +1291,7 @@ export default defineComponent({ opacity(value: number) { this.imageOverlay.setOpacity(value); + this.cloudOverlay.setOpacity(value); } } }); @@ -1461,12 +1532,12 @@ ul { grid-row: 4 / 6; } - #body-logos { - grid-column: 3 / 4; - grid-row: 5 / 6; - align-self: end; - justify-self: end; - } + // #body-logos { + // grid-column: 3 / 4; + // grid-row: 5 / 6; + // align-self: end; + // justify-self: end; + // } } // style the content @@ -1599,6 +1670,13 @@ a { width: 250px; border: 2px solid black; } + + #map-show-hide-clouds { + z-index: 1000; + position: absolute; + top: 1rem; + right: 80px; + } #map-legend { position: absolute; @@ -1718,7 +1796,7 @@ a { width: 100%; display: flex; flex-direction: column; - gap: 10px; + gap: 5px; } #opacity-slider-container { @@ -1742,8 +1820,10 @@ a { } #body-logos { + margin-bottom: -1rem; display: flex; flex-direction: row; + justify-content: flex-end; img { height: 35px !important; vertical-align: middle; @@ -1914,10 +1994,10 @@ i.mdi-menu-down { grid-row: 6 / 7; } - #body-logos { - grid-column: 1 / 2; - grid-row: 7 / 8; - } + // #body-logos { + // grid-column: 1 / 2; + // grid-row: 7 / 8; + // } } .content-with-sidebars { diff --git a/src/timestamps.ts b/src/timestamps.ts index 75be019..820f900 100644 --- a/src/timestamps.ts +++ b/src/timestamps.ts @@ -10,6 +10,11 @@ export interface Manifest { resized_image_directory: string; timestamps: number[]; }; + clouds: { + image_directory: string; + resized_image_directory: string; + timestamps: number[]; + }; } export async function fetchManifest(): Promise { @@ -20,11 +25,13 @@ export async function fetchManifest(): Promise { interface Timestamps { early_release: number[]; released: number[]; + clouds: number[]; } export async function getTimestamps(): Promise { const manifest = await fetchManifest(); const earlyRelease = manifest.early_release; const released = manifest.released; - return { early_release: earlyRelease.timestamps, released: released.timestamps }; + const clouds = manifest.clouds; + return { early_release: earlyRelease.timestamps, released: released.timestamps, clouds: clouds.timestamps }; }