@@ -31,6 +31,11 @@ Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOi
3131viewof parquet_path = Inputs.text({label:"Source", value:"https://storage.googleapis.com/opencontext-parquet/oc_isamples_pqg.parquet", width:"100%", submit:true});
3232```
3333
34+ ::: callout-warning
35+ #### Heads up: first interaction may be slow
36+ The first click or query can take a few seconds while the in‑browser database engine initializes and the remote Parquet file is fetched and indexed. Subsequent interactions are much faster because both the browser and DuckDB cache metadata and column chunks, so later queries reuse what was already loaded.
37+ :::
38+
3439``` {ojs}
3540//| code-fold: true
3641
@@ -42,24 +47,30 @@ db = {
4247}
4348
4449
45- async function loadData(query, params=[], waiting_id=null) {
50+ async function loadData(query, params = [], waiting_id = null, key = "default") {
51+ // latest-only guard per key
52+ loadData._latest = loadData._latest || new Map();
53+ const requestToken = Symbol();
54+ loadData._latest.set(key, requestToken);
55+
4656 // Get loading indicator
47- const waiter = document.getElementById(waiting_id);
48- if (waiter) {
49- waiter.hidden = false;
50- }
57+ const waiter = waiting_id ? document.getElementById(waiting_id) : null;
58+ if (waiter) waiter.hidden = false;
59+
5160 try {
5261 // Run the (slow) query
53- const _results = await db.query(query, ...params);
62+ const _results = await db.query(query, params);
63+ // Ignore stale responses
64+ if (loadData._latest.get(key) !== requestToken) return null;
5465 return _results;
5566 } catch (error) {
56- if (waiter) {
57- waiter.innerHtml = `<pre>${error}</pre>`;
67+ if (waiter && loadData._latest.get(key) === requestToken ) {
68+ waiter.innerHTML = `<pre>${error}</pre>`;
5869 }
5970 return null;
6071 } finally {
61- // Hide the waiter (if there is one)
62- if (waiter) {
72+ // Hide the waiter (if there is one) only if latest
73+ if (waiter && loadData._latest.get(key) === requestToken ) {
6374 waiter.hidden = true;
6475 }
6576 }
@@ -68,7 +79,7 @@ async function loadData(query, params=[], waiting_id=null) {
6879locations = {
6980 // get the content form the parquet file
7081 const query = `SELECT pid, latitude, longitude FROM nodes WHERE otype='GeospatialCoordLocation'`;
71- const data = await loadData(query, [], "loading_1");
82+ const data = await loadData(query, [], "loading_1", "locations" );
7283
7384 // Clear the existing PointPrimitiveCollection
7485 content.points.removeAll();
@@ -189,8 +200,8 @@ async function getGeoRecord(pid) {
189200 return "unset";
190201 }
191202 const q = `SELECT row_id, pid, otype, latitude, longitude FROM nodes WHERE otype='GeospatialCoordLocation' AND pid=?`;
192- const result = await db.queryRow (q, [pid]);
193- return result ;
203+ const rows = await loadData (q, [pid], "loading_geo", "geo" );
204+ return rows && rows.length ? rows[0] : null ;
194205}
195206
196207async function get_samples_1(pid) {
@@ -215,8 +226,8 @@ async function get_samples_1(pid) {
215226 AND g.otype = 'GeospatialCoordLocation'
216227 AND g.pid = ?
217228 `;
218- const result = await db.query (q, [pid]);
219- return result;
229+ const result = await loadData (q, [pid], "loading_s1", "samples_1" );
230+ return result ?? [] ;
220231}
221232
222233async function get_samples_2(pid) {
@@ -245,8 +256,8 @@ async function get_samples_2(pid) {
245256 AND g.otype = 'GeospatialCoordLocation'
246257 AND g.pid = ?
247258 `;
248- const result = await db.query (q, [pid]);
249- return result;
259+ const result = await loadData (q, [pid], "loading_s2", "samples_2" );
260+ return result ?? [] ;
250261}
251262
252263async function locationUsedBy(rowid){
@@ -258,7 +269,38 @@ async function locationUsedBy(rowid){
258269}
259270
260271mutable clickedPointId = "unset";
261- selectedGeoRecord = await getGeoRecord(clickedPointId);
272+ // Loading flags to control UI clearing while fetching
273+ mutable geoLoading = false;
274+ mutable s1Loading = false;
275+ mutable s2Loading = false;
276+
277+ // Precompute selection-driven data with loading flags
278+ selectedGeoRecord = {
279+ mutable geoLoading = true;
280+ try {
281+ return await getGeoRecord(clickedPointId);
282+ } finally {
283+ mutable geoLoading = false;
284+ }
285+ }
286+
287+ selectedSamples1 = {
288+ mutable s1Loading = true;
289+ try {
290+ return await get_samples_1(clickedPointId);
291+ } finally {
292+ mutable s1Loading = false;
293+ }
294+ }
295+
296+ selectedSamples2 = {
297+ mutable s2Loading = true;
298+ try {
299+ return await get_samples_2(clickedPointId);
300+ } finally {
301+ mutable s2Loading = false;
302+ }
303+ }
262304
263305md`Retrieved ${pointdata.length} locations from ${parquet_path}.`;
264306```
@@ -293,49 +335,22 @@ viewof pointdata = {
293335
294336The click point ID is "${clickedPointId}".
295337
338+ <div id =" loading_geo " hidden >Loading selected location…</div >
339+
296340``` {ojs}
297341//| echo: false
298- md`\`\`\`
342+ geoLoading ? md`(loading…)` : md`\`\`\`
299343${JSON.stringify(selectedGeoRecord, null, 2)}
300344\`\`\`
301345`
302346```
303347
304- ## Sample Data
305-
306- First 10 rows of the dataset to understand the data structure:
307-
308- ``` {ojs}
309- //| code-fold: true
310- sampleData = {
311- const query = `SELECT * FROM nodes LIMIT 10`;
312- const data = await loadData(query, [], "loading_sample");
313- return data;
314- }
315- ```
316-
317- <div id =" loading_sample " >Loading sample data...</div >
318-
319- ``` {ojs}
320- //| code-fold: true
321- viewof sampleTable = {
322- const data_table = Inputs.table(sampleData, {
323- layout: "auto",
324- width: {
325- pid: 200,
326- otype: 150
327- }
328- });
329- return data_table;
330- }
331- ```
332-
333348## getGeoRecord (selected)
334349
335350``` {ojs}
336351//| code-fold: true
337352pid = clickedPointId
338- testrecord = await getGeoRecord(pid) ;
353+ testrecord = selectedGeoRecord ;
339354```
340355
341356``` {ojs}
@@ -348,10 +363,17 @@ ${JSON.stringify(testrecord, null, 2)}
348363
349364## Related Sample Path 1 (selected)
350365
366+ <div id =" loading_s1 " hidden >Loading related samples (path 1)…</div >
367+
368+ Path 1 (direct_event_location): find MaterialSampleRecord items whose producing SamplingEvent has a direct sample_location pointing to the clicked GeospatialCoordLocation (pid).
369+
370+ - Chain: MaterialSampleRecord → produced_by → SamplingEvent → sample_location → GeospatialCoordLocation (clicked pid)
371+ - This matches the "direct_samples" concept in the Python notebook and is labeled as ` location_path = 'direct_event_location' ` in the query.
372+
351373``` {ojs}
352374//| echo: false
353- samples_1 = await get_samples_1(clickedPointId)
354- md`\`\`\`
375+ samples_1 = selectedSamples1
376+ s1Loading ? md`(loading…)` : md`\`\`\`
355377${JSON.stringify(samples_1, null, 2)}
356378\`\`\`
357379`
@@ -360,10 +382,17 @@ ${JSON.stringify(samples_1, null, 2)}
360382
361383## Related Sample Path 2 (selected)
362384
385+ <div id =" loading_s2 " hidden >Loading related samples (path 2)…</div >
386+
387+ Path 2 (via_site_location): find MaterialSampleRecord items whose producing SamplingEvent references a SamplingSite, and that site’s site_location points to the clicked GeospatialCoordLocation (pid).
388+
389+ - Chain: MaterialSampleRecord → produced_by → SamplingEvent → sampling_site → SamplingSite → site_location → GeospatialCoordLocation (clicked pid)
390+ - This matches the "samples_via_sites" concept in the Python notebook and is labeled as ` location_path = 'via_site_location' ` in the query.
391+
363392``` {ojs}
364393//| echo: false
365- samples_2 = await get_samples_2(clickedPointId)
366- md`\`\`\`
394+ samples_2 = selectedSamples2
395+ s2Loading ? md`(loading…)` : md`\`\`\`
367396${JSON.stringify(samples_2, null, 2)}
368397\`\`\`
369398`
0 commit comments