Sync from GitHub ce8d2eb
Browse files
aef.js
CHANGED
|
@@ -32,15 +32,17 @@ export function aefViews() {
|
|
| 32 |
...Array.from({ length: 64 }, (_, b) => ({ id: `band:${b}`, label: `Band A${String(b).padStart(2, '0')}, 10 m` }))]
|
| 33 |
}
|
| 34 |
|
| 35 |
-
/** A geotiff.js client that counts what it reads into `stats`. */
|
| 36 |
function countingClient(url, stats) {
|
| 37 |
-
|
|
|
|
| 38 |
async request({ headers, signal } = {}) {
|
| 39 |
const r = await fetch(url, { headers, signal }), data = await r.arrayBuffer()
|
| 40 |
-
stats.bytes += data.byteLength; stats.requests += 1
|
| 41 |
return { ok: r.ok, status: r.status, getHeader: name => r.headers.get(name), getData: async () => data }
|
| 42 |
},
|
| 43 |
}
|
|
|
|
| 44 |
}
|
| 45 |
|
| 46 |
/** One tile's rows (one per year): {year, epsg, west, north, path}. */
|
|
@@ -69,18 +71,25 @@ export async function aefCell(info, sample, stats = newStats()) {
|
|
| 69 |
return { epsg, cx, cy, corners, urls: tiles.map(x => `${info.files}${x.path}`) }
|
| 70 |
}
|
| 71 |
|
| 72 |
-
const opened = new Map() // COG url -> {tiff, left, top, dy}
|
|
|
|
| 73 |
|
| 74 |
-
/** A COG, opened once (its headers in one 1 MB block), with its origin and row direction
|
| 75 |
-
|
| 76 |
-
|
|
|
|
|
|
|
| 77 |
opened.set(url, (async () => {
|
| 78 |
-
const
|
|
|
|
| 79 |
const [, , , left, , dy, , top] = (await tiff.getImage(0)).fileDirectory.ModelTransformation // dy > 0: south-up
|
| 80 |
-
return { url, tiff, left, top, dy }
|
| 81 |
})().catch(e => { opened.delete(url); throw e }))
|
|
|
|
| 82 |
}
|
| 83 |
-
|
|
|
|
|
|
|
| 84 |
}
|
| 85 |
|
| 86 |
/** Every tile's part of the window, joined: one Int8Array per band (NODATA where no tile covers). */
|
|
|
|
| 32 |
...Array.from({ length: 64 }, (_, b) => ({ id: `band:${b}`, label: `Band A${String(b).padStart(2, '0')}, 10 m` }))]
|
| 33 |
}
|
| 34 |
|
| 35 |
+
/** A geotiff.js client that counts what it reads into `client.stats`: the read under way, set again by openCog. */
|
| 36 |
function countingClient(url, stats) {
|
| 37 |
+
const client = {
|
| 38 |
+
stats,
|
| 39 |
async request({ headers, signal } = {}) {
|
| 40 |
const r = await fetch(url, { headers, signal }), data = await r.arrayBuffer()
|
| 41 |
+
client.stats.bytes += data.byteLength; client.stats.requests += 1
|
| 42 |
return { ok: r.ok, status: r.status, getHeader: name => r.headers.get(name), getData: async () => data }
|
| 43 |
},
|
| 44 |
}
|
| 45 |
+
return client
|
| 46 |
}
|
| 47 |
|
| 48 |
/** One tile's rows (one per year): {year, epsg, west, north, path}. */
|
|
|
|
| 71 |
return { epsg, cx, cy, corners, urls: tiles.map(x => `${info.files}${x.path}`) }
|
| 72 |
}
|
| 73 |
|
| 74 |
+
const opened = new Map() // COG url -> {tiff, client, left, top, dy}, most recently used last
|
| 75 |
+
const KEEP = 4 // COGs kept open; older ones are dropped with their block cache
|
| 76 |
|
| 77 |
+
/** A COG, opened once (its headers in one 1 MB block), with its origin and row direction; its reads are counted
|
| 78 |
+
* into `stats` from now on. At most KEEP stay open, each caching at most 16 blocks (16 MB). */
|
| 79 |
+
async function openCog(url, stats) {
|
| 80 |
+
if (opened.has(url)) { const c = opened.get(url); opened.delete(url); opened.set(url, c) }
|
| 81 |
+
else {
|
| 82 |
opened.set(url, (async () => {
|
| 83 |
+
const client = countingClient(url, stats)
|
| 84 |
+
const tiff = await fromCustomClient(client, { blockSize: 2 ** 20, cacheSize: 16 })
|
| 85 |
const [, , , left, , dy, , top] = (await tiff.getImage(0)).fileDirectory.ModelTransformation // dy > 0: south-up
|
| 86 |
+
return { url, tiff, client, left, top, dy }
|
| 87 |
})().catch(e => { opened.delete(url); throw e }))
|
| 88 |
+
while (opened.size > KEEP) opened.delete(opened.keys().next().value)
|
| 89 |
}
|
| 90 |
+
const cog = await opened.get(url)
|
| 91 |
+
cog.client.stats = stats
|
| 92 |
+
return cog
|
| 93 |
}
|
| 94 |
|
| 95 |
/** Every tile's part of the window, joined: one Int8Array per band (NODATA where no tile covers). */
|
app.js
CHANGED
|
@@ -26,6 +26,7 @@ import { aefCell, aefViews, aefWindow, renderAef } from './aef.js'
|
|
| 26 |
import { aefSnippet, downloadAefTiff, downloadAll, downloadBand, downloadImage, downloadViirs, pythonSnippet, viirsSnippet }
|
| 27 |
from './download.js'
|
| 28 |
import { cellAt, cellIndex, renderTile } from './tiles.js'
|
|
|
|
| 29 |
|
| 30 |
const $ = id => document.getElementById(id)
|
| 31 |
const COLOURS = { S2L2A: [57, 181, 74], S2L1C: [0, 113, 188], S1RTC: [241, 90, 36], DEM: [180, 140, 90], VIIRS: [126, 87, 194], AEF: [32, 150, 150] }
|
|
|
|
| 26 |
import { aefSnippet, downloadAefTiff, downloadAll, downloadBand, downloadImage, downloadViirs, pythonSnippet, viirsSnippet }
|
| 27 |
from './download.js'
|
| 28 |
import { cellAt, cellIndex, renderTile } from './tiles.js'
|
| 29 |
+
import './zstd.js' // also here: on the private Space a module is served only if its referer carries the token, as app.js's does
|
| 30 |
|
| 31 |
const $ = id => document.getElementById(id)
|
| 32 |
const COLOURS = { S2L2A: [57, 181, 74], S2L1C: [0, 113, 188], S1RTC: [241, 90, 36], DEM: [180, 140, 90], VIIRS: [126, 87, 194], AEF: [32, 150, 150] }
|