@oceanmetrics/engine 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1 (2026-10-23)
4
+
5
+ - Fix: `sliceSource().slice()` read the lobes' slabs without the `SELECT DISTINCT` that `runPreview` applies, so where two
6
+ places' boxes overlap (Channel Islands and Chumash) every cell of the overlap came back twice. Regression test with two
7
+ overlapping synthetic slabs.
8
+
3
9
  ## 0.1.0 (2026-10-23)
4
10
 
5
11
  The browser engine, build week 2 step 2b: erddap-places' engine as a platform package, so the indicators Map's `grid`
package/README.md CHANGED
@@ -112,7 +112,9 @@ const run = await runPreview({
112
112
  `@oceanmetrics/map`'s `gridSlot(spec, source)`: `slice(t)` fetches the one step `t` (a day or an ISO instant;
113
113
  `null` is the extent's end, or ERDDAP's `last` without an extent) of every lobe and returns `last_step`'s rows as
114
114
  `{ lat, lon, value, weight }`, the mask's own centres, a cell without data kept with `value: null`. The mask goes into
115
- the engine once, at the first slice.
115
+ the engine once, at the first slice. The slabs of the lobes are read as `SELECT DISTINCT *` (as `runPreview` does), so a
116
+ cell inside two overlapping boxes (Channel Islands and Chumash) comes back once, not twice (0.1.1); the mask you pass is
117
+ the places' union and must hold each cell once.
116
118
 
117
119
  ## DuckDB-WASM: the peer and the worker setup
118
120
 
package/dist/preview.js CHANGED
@@ -91,6 +91,9 @@ async function loadSlabs(engine, urls, format, signal, retry) {
91
91
  return files;
92
92
  }
93
93
  const readList = (files) => `read_parquet([${files.map((f) => `'${f}'`).join(', ')}])`;
94
+ // the lobes' slabs as one relation without a row twice: where two places' boxes overlap (Channel Islands and Chumash)
95
+ // both slabs hold the cells of the overlap, and the join over the mask would return each of them twice
96
+ const distinctSlab = (files) => `(SELECT DISTINCT * FROM ${readList(files)})`;
94
97
  /**
95
98
  * The statistics of `variable` over the place for [t0, t1], chunk by chunk. Each chunk's slab (one Parquet request per
96
99
  * lobe) is registered, aggregated with the template over the mask and dropped; `onChunk` gets its rows as soon as they
@@ -142,7 +145,7 @@ export async function runPreview(o) {
142
145
  files.push(f);
143
146
  }
144
147
  // DISTINCT: a coarse axis (the 8-day grid) can snap a chunk's two endpoints onto one step
145
- const slab = `(SELECT DISTINCT * FROM ${readList(files)})`;
148
+ const slab = distinctSlab(files);
146
149
  const out = await engine.runTemplate(name, { expr, slab, mask });
147
150
  aborted(signal);
148
151
  // two chunks snapped onto the same step of a coarse axis give that step twice, identical: keep the first
@@ -187,7 +190,7 @@ export function sliceSource(o) {
187
190
  try {
188
191
  await inserted;
189
192
  aborted(signal);
190
- const rows = await o.engine.runTemplate('last_step', { expr: valueExpr(o.variable), slab: readList(files), mask });
193
+ const rows = await o.engine.runTemplate('last_step', { expr: valueExpr(o.variable), slab: distinctSlab(files), mask });
191
194
  return rows.map((r) => ({
192
195
  lat: Number(r.latitude), lon: Number(r.longitude), weight: Number(r.weight),
193
196
  value: r.value === null || r.value === undefined ? null : Number(r.value),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oceanmetrics/engine",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "the browser engine of Ocean Metrics: the place mask, ERDDAP requests, the SQL shared with the Python engine, DuckDB-WASM statistics and the chunked live preview",
5
5
  "keywords": [
6
6
  "ocean",
package/src/preview.ts CHANGED
@@ -109,6 +109,9 @@ async function loadSlabs(engine: Engine, urls: string[], format: Format, signal:
109
109
  return files
110
110
  }
111
111
  const readList = (files: string[]) => `read_parquet([${files.map((f) => `'${f}'`).join(', ')}])`
112
+ // the lobes' slabs as one relation without a row twice: where two places' boxes overlap (Channel Islands and Chumash)
113
+ // both slabs hold the cells of the overlap, and the join over the mask would return each of them twice
114
+ const distinctSlab = (files: string[]) => `(SELECT DISTINCT * FROM ${readList(files)})`
112
115
 
113
116
  // ---- the preview ----
114
117
 
@@ -205,7 +208,7 @@ export async function runPreview(o: PreviewOptions): Promise<PreviewResult> {
205
208
  try {
206
209
  for (const s of slabs) { const f = `${uid('slab')}.parquet`; await engine.registerBuffer(f, s.buffer!); files.push(f) }
207
210
  // DISTINCT: a coarse axis (the 8-day grid) can snap a chunk's two endpoints onto one step
208
- const slab = `(SELECT DISTINCT * FROM ${readList(files)})`
211
+ const slab = distinctSlab(files)
209
212
  const out = await engine.runTemplate(name, { expr, slab, mask })
210
213
  aborted(signal)
211
214
  // two chunks snapped onto the same step of a coarse axis give that step twice, identical: keep the first
@@ -266,7 +269,7 @@ export function sliceSource(o: SliceSourceOptions): SliceSource {
266
269
  try {
267
270
  await inserted
268
271
  aborted(signal)
269
- const rows = await o.engine.runTemplate('last_step', { expr: valueExpr(o.variable), slab: readList(files), mask })
272
+ const rows = await o.engine.runTemplate('last_step', { expr: valueExpr(o.variable), slab: distinctSlab(files), mask })
270
273
  return rows.map((r) => ({
271
274
  lat: Number(r.latitude), lon: Number(r.longitude), weight: Number(r.weight),
272
275
  value: r.value === null || r.value === undefined ? null : Number(r.value),