mikonvergence commited on
Commit
6e2aeb5
·
verified ·
1 Parent(s): 695f088

Background loading of the other datasets

Browse files
Files changed (2) hide show
  1. README.md +3 -1
  2. app.js +45 -7
README.md CHANGED
@@ -10,7 +10,7 @@ short_description: Major TOM Core samples on a 3D globe, read in the browser
10
 
11
  > **CLAUDE-GENERATED DOCUMENT** — automated LLM-generated content. Verify before relying on it.
12
 
13
- > **Review status:** draft for review, 2026-11-02. Space not yet created.
14
 
15
  # Major TOM Raster Viewer
16
 
@@ -30,6 +30,8 @@ panel: dataset
30
  -> app.js: MapLibre globe; cursor shows the cell and whether it has a sample
31
  click a cell -> its footprint rows (page index) -> the image file on Hugging Face (one row group per sample)
32
  -> samples.js: that row group's thumbnail column -> panel, with acquisition time, cloud cover or orbit, product
 
 
33
  ```
34
 
35
  ## Files
 
10
 
11
  > **CLAUDE-GENERATED DOCUMENT** — automated LLM-generated content. Verify before relying on it.
12
 
13
+ > **Review status:** draft for review, 2026-11-02. Space created (private) and deployed 2026-11-02.
14
 
15
  # Major TOM Raster Viewer
16
 
 
30
  -> app.js: MapLibre globe; cursor shows the cell and whether it has a sample
31
  click a cell -> its footprint rows (page index) -> the image file on Hugging Face (one row group per sample)
32
  -> samples.js: that row group's thumbnail column -> panel, with acquisition time, cloud cover or orbit, product
33
+ in the background: the other datasets' footprints once the first is drawn; after a click, the same cell's samples
34
+ and first thumbnail in the other datasets (kept, so switching dataset shows them at once)
35
  ```
36
 
37
  ## Files
app.js CHANGED
@@ -16,7 +16,7 @@ import { cellAt, cellIndex, renderTile } from './tiles.js'
16
  const $ = id => document.getElementById(id)
17
  const COLOURS = { S2L2A: [57, 181, 74], S2L1C: [0, 113, 188], S1RTC: [241, 90, 36], DEM: [180, 140, 90] }
18
 
19
- const state = { name: null, info: null, coverage: null, version: 0, loading: 0 }
20
 
21
  // ---- map ---------------------------------------------------------------------------------------------------
22
 
@@ -67,6 +67,7 @@ map.on('load', async () => {
67
  map.getSource('coast').setData(coastLines(feature(topo, topo.objects.land)))
68
  map.getSource('land').setData(continuousRings(feature(topo, topo.objects.land)))
69
  const datasets = await DATASETS
 
70
  buildDatasetList(datasets)
71
  setDataset(Object.keys(datasets)[0], datasets)
72
  })
@@ -220,11 +221,47 @@ async function setDataset(name, datasets) {
220
  $('stat-draw').textContent = secs(performance.now() - t0)
221
  document.body.dataset.loaded = name // for automated checks
222
  stopLoader()
 
223
  })
224
  }
225
 
 
 
 
 
 
 
 
226
  // ---- selected cell -----------------------------------------------------------------------------------------
227
  // click -> cell -> its samples (footprint rows) -> buttons when there are several -> thumbnail of the chosen one
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
 
229
  const pick = { cell: null, samples: [], chosen: 0, thumbnail: null, ticket: 0 }
230
 
@@ -251,14 +288,14 @@ async function pickCell() {
251
  $('choices').replaceChildren(); $('pick-meta').replaceChildren(); $('pick-stats').textContent = ''
252
  let samples
253
  try {
254
- samples = await samplesAt(name, row, col)
255
  } catch (error) {
256
  if (ticket === pick.ticket) showThumbnail(null, `Could not load: ${error.message}`)
257
  return
258
  }
259
  if (ticket !== pick.ticket) return
260
  pick.samples = samples
261
- if (!samples.length) { showThumbnail(null, `No ${state.info.label} sample in this cell`); return }
262
  if (samples.length > 1) {
263
  $('choices').replaceChildren(...samples.map((s, i) => {
264
  const b = document.createElement('button')
@@ -277,13 +314,14 @@ async function choose(i) {
277
  meta(sample, null, info)
278
  showThumbnail(null, 'Loading the thumbnail…')
279
  try {
280
- const t = await loadThumbnail(info, sample)
281
- if (ticket !== pick.ticket) { URL.revokeObjectURL(t.src); return }
282
- if (pick.thumbnail) URL.revokeObjectURL(pick.thumbnail.src)
283
  pick.thumbnail = t
284
  showThumbnail(t.src)
285
  meta(sample, t.productId, info)
286
- $('pick-stats').textContent = `Thumbnail: ${kb(t.stats.bytes)} in ${t.stats.requests} requests · ${secs(t.stats.read)}`
 
 
287
  } catch (error) {
288
  if (ticket === pick.ticket) showThumbnail(null, `Could not load: ${error.message}`)
289
  }
 
16
  const $ = id => document.getElementById(id)
17
  const COLOURS = { S2L2A: [57, 181, 74], S2L1C: [0, 113, 188], S1RTC: [241, 90, 36], DEM: [180, 140, 90] }
18
 
19
+ const state = { name: null, info: null, datasets: null, coverage: null, version: 0, loading: 0 }
20
 
21
  // ---- map ---------------------------------------------------------------------------------------------------
22
 
 
67
  map.getSource('coast').setData(coastLines(feature(topo, topo.objects.land)))
68
  map.getSource('land').setData(continuousRings(feature(topo, topo.objects.land)))
69
  const datasets = await DATASETS
70
+ state.datasets = datasets
71
  buildDatasetList(datasets)
72
  setDataset(Object.keys(datasets)[0], datasets)
73
  })
 
221
  $('stat-draw').textContent = secs(performance.now() - t0)
222
  document.body.dataset.loaded = name // for automated checks
223
  stopLoader()
224
+ prefetchFootprints()
225
  })
226
  }
227
 
228
+ /** The other datasets' footprints, one after another, once the shown one is drawn: switching is then instant. */
229
+ async function prefetchFootprints() {
230
+ for (const name of Object.keys(state.datasets)) {
231
+ try { await loadCoverage(name) } catch { /* loaded again, with its error shown, when picked */ }
232
+ }
233
+ }
234
+
235
  // ---- selected cell -----------------------------------------------------------------------------------------
236
  // click -> cell -> its samples (footprint rows) -> buttons when there are several -> thumbnail of the chosen one
237
+ // then, in the background, the same cell in the other datasets: samples and first thumbnail, one dataset at a time
238
+ // lookups and thumbnails are kept (promises, so a load under way is joined, not repeated)
239
+
240
+ const lookups = new Map() // "<dataset>/<row>/<col>" -> samples
241
+ const thumbs = new Map() // "<dataset>/<part>/<parquet_row>" -> thumbnail
242
+
243
+ function samplesOf(name, row, col) {
244
+ const key = `${name}/${row}/${col}`
245
+ if (!lookups.has(key)) lookups.set(key, samplesAt(name, row, col).catch(e => { lookups.delete(key); throw e }))
246
+ return lookups.get(key)
247
+ }
248
+ function thumbnailOf(name, sample) {
249
+ const key = `${name}/${sample.part}/${sample.parquet_row}`, fresh = !thumbs.has(key)
250
+ if (fresh) thumbs.set(key, loadThumbnail(state.datasets[name], sample).catch(e => { thumbs.delete(key); throw e }))
251
+ return [thumbs.get(key), fresh]
252
+ }
253
+
254
+ /** The selected cell in every other dataset, in the panel's order, after the shown one. */
255
+ async function prefetchCell(cell) {
256
+ for (const name of Object.keys(state.datasets)) {
257
+ if (pick.cell !== cell) return // another cell was picked
258
+ if (name === state.name) continue
259
+ try {
260
+ const samples = await samplesOf(name, cell.row, cell.col)
261
+ if (samples.length) await thumbnailOf(name, samples[0])[0]
262
+ } catch { /* loaded again, with its error shown, when that dataset is shown */ }
263
+ }
264
+ }
265
 
266
  const pick = { cell: null, samples: [], chosen: 0, thumbnail: null, ticket: 0 }
267
 
 
288
  $('choices').replaceChildren(); $('pick-meta').replaceChildren(); $('pick-stats').textContent = ''
289
  let samples
290
  try {
291
+ samples = await samplesOf(name, row, col)
292
  } catch (error) {
293
  if (ticket === pick.ticket) showThumbnail(null, `Could not load: ${error.message}`)
294
  return
295
  }
296
  if (ticket !== pick.ticket) return
297
  pick.samples = samples
298
+ if (!samples.length) { showThumbnail(null, `No ${state.info.label} sample in this cell`); prefetchCell(pick.cell); return }
299
  if (samples.length > 1) {
300
  $('choices').replaceChildren(...samples.map((s, i) => {
301
  const b = document.createElement('button')
 
314
  meta(sample, null, info)
315
  showThumbnail(null, 'Loading the thumbnail…')
316
  try {
317
+ const [loading, fresh] = thumbnailOf(state.name, sample), t = await loading
318
+ if (ticket !== pick.ticket) return
 
319
  pick.thumbnail = t
320
  showThumbnail(t.src)
321
  meta(sample, t.productId, info)
322
+ $('pick-stats').textContent = `Thumbnail: ${kb(t.stats.bytes)} in ${t.stats.requests} requests · ${secs(t.stats.read)}` +
323
+ (fresh ? '' : ' (loaded before)')
324
+ prefetchCell(pick.cell)
325
  } catch (error) {
326
  if (ticket === pick.ticket) showThumbnail(null, `Could not load: ${error.message}`)
327
  }