@@ -22,34 +22,6 @@ This page demonstrates how geospatial data can be dynamically accessed from a re
2222 #cesiumContainer {
2323 aspect-ratio : 1 /1 ;
2424 }
25- #sampleDetails {
26- margin-top : 1.5rem ;
27- }
28- #sampleDetails .sample-grid {
29- display : grid ;
30- gap : 1rem ;
31- }
32- @media (min-width : 768px ) {
33- #sampleDetails .sample-grid {
34- grid-template-columns : repeat (auto-fit , minmax (240px , 1fr ));
35- }
36- }
37- #sampleDetails .sample-card {
38- border : 1px solid #d9d9d9 ;
39- border-radius : 0.5rem ;
40- padding : 0.75rem ;
41- background : #fafafa ;
42- }
43- #sampleDetails .sample-card h3 {
44- margin-top : 0 ;
45- font-size : 1.05rem ;
46- }
47- #sampleDetails .sample-card img {
48- max-width : 140px ;
49- border-radius : 0.25rem ;
50- display : block ;
51- margin-top : 0.5rem ;
52- }
5325</style >
5426
5527``` {ojs}
@@ -202,13 +174,7 @@ class CView {
202174 this.selectHandler.setInputAction((e) => {
203175 const selectPoint = this.viewer.scene.pick(e.position);
204176 if (Cesium.defined(selectPoint) && selectPoint.hasOwnProperty("primitive")) {
205- console.log("Clicked point ID:", selectPoint.id);
206- // Store the clicked ID in the viewer instance for now
207- this.clickedId = selectPoint.id;
208- // Dispatch a custom event that can be picked up by Observable
209- document.dispatchEvent(new CustomEvent('pointSelected', {
210- detail: { pointId: selectPoint.id }
211- }));
177+ mutable clickedPointId = selectPoint.id;
212178 }
213179 },Cesium.ScreenSpaceEventType.LEFT_CLICK);
214180
@@ -227,125 +193,19 @@ async function getGeoRecord(pid) {
227193 }
228194 const q = `SELECT row_id, pid, otype, latitude, longitude FROM nodes WHERE otype='GeospatialCoordLocation' AND pid=?`;
229195 const result = await db.queryRow(q, [pid]);
230- console.log('Loaded geo record for PID', pid, result);
231196 return result;
232197}
233198
234- async function samplesAtLocation (rowid) {
199+ async function locationUsedBy (rowid){
235200 if (rowid === undefined || rowid === null) {
236201 return [];
237202 }
238- console.log('samplesAtLocation invoked with row_id', rowid);
239- const query = `
240- WITH edges AS (
241- SELECT s, p, unnest(o) AS o1
242- FROM nodes
243- WHERE otype = '_edge_'
244- ), events AS (
245- SELECT s AS event_row_id
246- FROM edges
247- WHERE p = 'sample_location' AND o1 = ?
248- ), sample_links AS (
249- SELECT s AS sample_row_id, o1 AS event_row_id
250- FROM edges
251- WHERE p = 'produced_by' AND o1 IN (SELECT event_row_id FROM events)
252- ), sample_nodes AS (
253- SELECT row_id, pid, label, description, thumbnail_url, alternate_identifiers
254- FROM nodes
255- WHERE row_id IN (SELECT sample_row_id FROM sample_links)
256- ), event_nodes AS (
257- SELECT row_id, label, project
258- FROM nodes
259- WHERE row_id IN (SELECT event_row_id FROM events)
260- ), concept_edges AS (
261- SELECT s, p, o1
262- FROM edges
263- WHERE s IN (SELECT row_id FROM sample_nodes)
264- AND p IN ('has_sample_object_type','has_material_category','has_context_category','keywords')
265- ), concept_labels AS (
266- SELECT row_id, label
267- FROM nodes
268- WHERE row_id IN (SELECT o1 FROM concept_edges)
269- ), keyword_text AS (
270- SELECT ce.s, LIST(DISTINCT cl.label) AS keywords
271- FROM concept_edges ce
272- JOIN concept_labels cl ON ce.o1 = cl.row_id
273- WHERE ce.p = 'keywords'
274- GROUP BY ce.s
275- ), sampling_sites AS (
276- SELECT s AS event_row_id, o1 AS site_row_id
277- FROM edges
278- WHERE p = 'sampling_site' AND s IN (SELECT event_row_id FROM events)
279- ), site_nodes AS (
280- SELECT row_id, label
281- FROM nodes
282- WHERE row_id IN (SELECT site_row_id FROM sampling_sites)
283- )
284- SELECT
285- sn.pid,
286- sn.label,
287- sn.description,
288- sn.thumbnail_url,
289- MAX(CASE WHEN ce.p = 'has_sample_object_type' THEN cl.label END) AS sample_object_type,
290- MAX(CASE WHEN ce.p = 'has_material_category' THEN cl.label END) AS material_category,
291- MAX(CASE WHEN ce.p = 'has_context_category' THEN cl.label END) AS context_category,
292- kt.keywords,
293- en.project,
294- en.label AS event_label,
295- snl.label AS site_label
296- FROM sample_nodes sn
297- LEFT JOIN sample_links sl ON sn.row_id = sl.sample_row_id
298- LEFT JOIN event_nodes en ON sl.event_row_id = en.row_id
299- LEFT JOIN concept_edges ce ON sn.row_id = ce.s
300- LEFT JOIN concept_labels cl ON ce.o1 = cl.row_id
301- LEFT JOIN keyword_text kt ON sn.row_id = kt.s
302- LEFT JOIN sampling_sites ss ON sl.event_row_id = ss.event_row_id
303- LEFT JOIN site_nodes snl ON ss.site_row_id = snl.row_id
304- GROUP BY sn.pid, sn.label, sn.description, sn.thumbnail_url, kt.keywords, en.project, en.label, snl.label
305- ORDER BY coalesce(sn.label, sn.pid)
306- LIMIT 6;
307- `;
308- try {
309- const result = await db.query(query, [rowid]);
310- const rows = result?.toArray ? result.toArray() : result;
311- console.log(`Samples retrieved for location ${rowid}:`, rows);
312- return Array.isArray(rows) ? rows : [];
313- } catch (error) {
314- console.error('Failed to load sample data for location', rowid, error);
315- return [];
316- }
203+ const q = `select pid, otype from nodes where row_id in (select nodes.s from nodes where list_contains(nodes.o, ?));`;
204+ return db.query(q, [rowid]);
317205}
318206
319- // Use a viewof pattern to create a reactive clickedPointId
320- viewof clickedPointId = {
321- const input = html`<input type="hidden" value="unset">`;
322-
323- // Listen for point selection events and update the input value
324- document.addEventListener('pointSelected', (event) => {
325- input.value = event.detail.pointId;
326- input.dispatchEvent(new Event('input'));
327- });
328-
329- return input;
330- }
331-
332- // Access the current value
333- clickedPointId;
334-
335- selectedGeoRecord = {
336- // This will re-execute whenever clickedPointId changes
337- const result = await getGeoRecord(clickedPointId);
338- return result;
339- }
340-
341- selectedSamples = {
342- // This will re-execute whenever selectedGeoRecord changes
343- if (selectedGeoRecord?.row_id) {
344- const samples = await samplesAtLocation(selectedGeoRecord.row_id);
345- return samples;
346- }
347- return [];
348- }
207+ mutable clickedPointId = "unset";
208+ selectedGeoRecord = await getGeoRecord(clickedPointId);
349209
350210md`Retrieved ${pointdata.length} locations from ${parquet_path}.`;
351211```
@@ -378,51 +238,14 @@ viewof pointdata = {
378238
379239:::
380240
241+ The click point ID is "${clickedPointId}".
242+
381243``` {ojs}
382244//| echo: false
383- // Enhanced UI with sample information
384- html`<section id="sampleDetails">
385- <h2>Selected Location</h2>
386- ${clickedPointId === "unset" ?
387- html`<p>Click a point on the map to view nearby sample records.</p>` :
388- html`${(() => {
389- const locationPid = selectedGeoRecord?.pid || clickedPointId;
390- const resolver = locationPid?.startsWith('http') ? locationPid : `https://n2t.net/${encodeURIComponent(locationPid)}`;
391- const lat = selectedGeoRecord?.latitude != null ? selectedGeoRecord.latitude.toFixed(4) : 'N/A';
392- const lon = selectedGeoRecord?.longitude != null ? selectedGeoRecord.longitude.toFixed(4) : 'N/A';
393- const samples = Array.isArray(selectedSamples) ? selectedSamples : [];
394- return html`<div>
395- <p><strong>Point PID:</strong> <a href="${resolver}" target="_blank" rel="noopener">${locationPid}</a></p>
396- <p><strong>Coordinates:</strong> ${lat}, ${lon}</p>
397- <p><strong>Sample records found:</strong> ${samples.length}</p>
398- ${samples.length
399- ? html`<div class="sample-grid">
400- ${samples.map(sample => {
401- const displayLabel = sample.label || sample.pid;
402- const sampleLink = sample.pid?.startsWith('http') ? sample.pid : `https://n2t.net/${encodeURIComponent(sample.pid)}`;
403- const typeParts = [sample.sample_object_type, sample.material_category].filter(Boolean);
404- const collectionParts = [sample.project, sample.site_label, sample.event_label].filter(Boolean);
405- const context = sample.context_category;
406- const keywordData = sample.keywords;
407- const keywords = Array.isArray(keywordData)
408- ? keywordData.filter(Boolean)
409- : typeof keywordData === 'string'
410- ? keywordData.split(',').map(d => d.trim()).filter(Boolean)
411- : [];
412- return html`<article class="sample-card">
413- <h3><a href="${sampleLink}" target="_blank" rel="noopener">${displayLabel}</a></h3>
414- ${sample.description ? html`<p>${sample.description}</p>` : null}
415- ${typeParts.length ? html`<p><strong>Type</strong>: ${typeParts.join(' · ')}</p>` : null}
416- ${context ? html`<p><strong>Context</strong>: ${context}</p>` : null}
417- ${collectionParts.length ? html`<p><strong>Collection</strong>: ${collectionParts.join(' · ')}</p>` : null}
418- ${keywords.length ? html`<p><strong>Keywords</strong>: ${keywords.join(', ')}</p>` : null}
419- ${sample.thumbnail_url ? html`<img src="${sample.thumbnail_url}" alt="Thumbnail for ${displayLabel}" loading="lazy">` : null}
420- </article>`;
421- })}
422- </div>`
423- : html`<p><em>No sample records were linked to this location.</em></p>`}
424- </div>`;
425- })()}`
426- }
427- </section>`
245+ md`\`\`\`
246+ ${JSON.stringify(selectedGeoRecord, null, 2)}
247+ \`\`\`
248+ `
428249```
250+
251+
0 commit comments