Code
// Imports - use dynamic import to avoid CORS issues
duckdbModule = import("https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@1.28.0/+esm")Search and explore 6.7 million physical samples from scientific collections worldwide.
This app queries a ~280 MB Parquet file directly in your browser using DuckDB-WASM. No server required!
// Imports - use dynamic import to avoid CORS issues
duckdbModule = import("https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@1.28.0/+esm")parquet_url = "https://pub-a18234d962364c22a50c787b7ca09fa5.r2.dev/isamples_202601_wide.parquet"
// Source color scheme (consistent with iSamples conventions)
SOURCE_COLORS = ({
'SESAR': '#3366CC', // Blue
'OPENCONTEXT': '#DC3912', // Red
'GEOME': '#109618', // Green
'SMITHSONIAN': '#FF9900', // Orange
'default': '#808080' // Gray
})
// Cesium colors
function getCesiumColor(source) {
const colors = {
'SESAR': Cesium.Color.fromCssColorString('#3366CC'),
'OPENCONTEXT': Cesium.Color.fromCssColorString('#DC3912'),
'GEOME': Cesium.Color.fromCssColorString('#109618'),
'SMITHSONIAN': Cesium.Color.fromCssColorString('#FF9900'),
};
return colors[source] || Cesium.Color.GRAY;
}// Parse URL params for bookmarkable searches
initialParams = {
const params = new URLSearchParams(window.location.search);
return {
q: params.get("q") || "",
sources: params.get("sources")?.split(",").filter(s => s) || [],
view: ["list", "table", "globe"].includes(params.get("view")) ? params.get("view") : "globe"
};
}// Search input
viewof searchInput = Inputs.text({
placeholder: "Search samples (e.g., pottery, basalt, Cyprus...)",
value: initialParams.q,
submit: "Search"
})Source
// Source checkboxes with counts
viewof sourceCheckboxes = {
// Get source counts based on current search
const counts = await sourceCounts;
const options = counts.map(r => r.value);
return Inputs.checkbox(options, {
value: initialParams.sources.filter(s => options.includes(s)),
format: (x) => {
const r = counts.find(s => s.value === x);
const color = SOURCE_COLORS[x] || SOURCE_COLORS.default;
const count = r ? Number(r.count).toLocaleString() : "0";
return html`<span style="display: inline-flex; align-items: center; gap: 6px;">
<span style="width: 12px; height: 12px; border-radius: 50%; background: ${color};"></span>
${x} <span style="color: #888;">(${count})</span>
</span>`;
}
});
}html`<a href="?" style="font-size: 13px;">Clear All Filters</a>`Max Samples
viewof maxSamples = Inputs.range([1000, 100000], {
value: 25000,
step: 1000
})// Update URL without reloading
{
const params = new URLSearchParams();
if (searchInput) params.set("q", searchInput);
if (sourceCheckboxes?.length) params.set("sources", sourceCheckboxes.join(","));
if (viewMode !== "globe") params.set("view", viewMode);
const newUrl = params.toString() ? `?${params.toString()}` : window.location.pathname;
if (window.location.search !== `?${params.toString()}`) {
history.replaceState(null, "", newUrl);
}
}// View mode selector
viewof viewMode = Inputs.radio(["globe", "list", "table"], {
value: initialParams.view,
format: (x) => x === "globe" ? "π Globe" : x === "list" ? "π List" : "π Table"
})Loading dataβ¦
// Show result count
html`<div style="margin-bottom: 16px;">
<strong>Showing ${sampleData.length.toLocaleString()} of ${Number(totalCount).toLocaleString()} matching samples</strong>
</div>`// Render results based on view mode
{
if (viewMode === "globe") {
// Globe is rendered in its own section below
return html`<div style="color: #666; font-style: italic;">See globe view below</div>`;
} else if (viewMode === "table") {
return Inputs.table(sampleData, {
columns: ['source', 'label', 'latitude', 'longitude'],
header: {
source: 'Source',
label: 'Label',
latitude: 'Lat',
longitude: 'Lon'
},
format: {
source: (x) => html`<span style="color: ${SOURCE_COLORS[x] || '#808080'}; font-weight: 600;">${x}</span>`,
latitude: (x) => x?.toFixed(4),
longitude: (x) => x?.toFixed(4)
},
rows: 20
});
} else {
// List view
if (sampleData.length === 0) {
return html`<div style="color: #888; padding: 20px; text-align: center;">No results found</div>`;
}
const items = sampleData.map(r => {
const color = SOURCE_COLORS[r.source] || SOURCE_COLORS.default;
const desc = r.description || "";
const description = desc.length > 150 ? desc.slice(0, 150) + "..." : (desc || "<em>No description</em>");
return html`<div style="border: 1px solid #e0e0e0; border-radius: 8px; padding: 12px; margin-bottom: 8px; background: white;">
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
<span style="background: ${color}; color: white; padding: 2px 8px; border-radius: 4px; font-size: 12px; font-weight: 600;">
${r.source}
</span>
</div>
<div style="font-weight: 600; margin-bottom: 4px;">${r.label || "Unlabeled"}</div>
<div style="font-size: 13px; color: #666; margin-bottom: 8px;">${description}</div>
<div style="font-size: 12px; color: #888;">
π ${r.latitude?.toFixed(4)}, ${r.longitude?.toFixed(4)}
</div>
</div>`;
});
return html`<div style="max-height: 500px; overflow-y: auto;">${items}</div>`;
}
}db = {
const bundle = await duckdbModule.selectBundle(duckdbModule.getJsDelivrBundles());
const worker_url = URL.createObjectURL(
new Blob([`importScripts("${bundle.mainWorker}");`], {type: 'text/javascript'})
);
const worker = new Worker(worker_url);
const logger = new duckdbModule.ConsoleLogger(duckdbModule.LogLevel.WARNING);
const instance = new duckdbModule.AsyncDuckDB(logger, worker);
await instance.instantiate(bundle.mainModule, bundle.pthreadWorker);
URL.revokeObjectURL(worker_url);
// Create view for convenience
const conn = await instance.connect();
await conn.query(`CREATE VIEW samples AS SELECT * FROM read_parquet('${parquet_url}')`);
await conn.close();
return instance;
}
// Helper function to run queries
async function runQuery(sql) {
const conn = await db.connect();
try {
const result = await conn.query(sql);
return result.toArray().map(row => {
const obj = row.toJSON();
// Convert BigInt to Number
for (const key in obj) {
if (typeof obj[key] === 'bigint') obj[key] = Number(obj[key]);
}
return obj;
});
} finally {
await conn.close();
}
}// Build WHERE clause from current filters
whereClause = {
const conditions = [
"otype = 'MaterialSampleRecord'",
"latitude IS NOT NULL"
];
// Text search
if (searchInput?.trim()) {
const term = searchInput.trim().replace(/'/g, "''");
conditions.push(`(
label ILIKE '%${term}%'
OR description ILIKE '%${term}%'
OR CAST(place_name AS VARCHAR) ILIKE '%${term}%'
)`);
}
// Source filter
const sources = Array.from(sourceCheckboxes || []);
if (sources.length > 0) {
const sourceList = sources.map(s => `'${s}'`).join(", ");
conditions.push(`n IN (${sourceList})`);
}
return conditions.join(" AND ");
}// Get source facet counts (respects text search but not source filter)
sourceCounts = {
let baseWhere = "otype = 'MaterialSampleRecord' AND latitude IS NOT NULL";
if (searchInput?.trim()) {
const term = searchInput.trim().replace(/'/g, "''");
baseWhere += ` AND (
label ILIKE '%${term}%'
OR description ILIKE '%${term}%'
OR CAST(place_name AS VARCHAR) ILIKE '%${term}%'
)`;
}
const query = `
SELECT n as value, COUNT(*) as count
FROM samples
WHERE ${baseWhere}
GROUP BY n
ORDER BY count DESC
`;
try {
return await runQuery(query);
} catch (e) {
console.error("Facet query error:", e);
return [];
}
}// Get total count matching current filters
totalCount = {
const query = `SELECT COUNT(*) as count FROM samples WHERE ${whereClause}`;
try {
const rows = await runQuery(query);
return rows[0]?.count || 0;
} catch (e) {
return 0;
}
}// Load sample data
sampleData = {
const statusDiv = document.getElementById('loading_status');
if (statusDiv) {
statusDiv.style.display = 'block';
statusDiv.textContent = 'Loading samples...';
}
try {
const query = `
SELECT
row_id,
pid,
label,
COALESCE(description, '') as description,
latitude,
longitude,
n as source,
place_name
FROM samples
WHERE ${whereClause}
ORDER BY RANDOM()
LIMIT ${maxSamples}
`;
const data = await runQuery(query);
if (statusDiv) {
statusDiv.textContent = `Loaded ${data.length.toLocaleString()} samples`;
setTimeout(() => { statusDiv.style.display = 'none'; }, 2000);
}
return data;
} catch (error) {
if (statusDiv) {
statusDiv.textContent = `Error: ${error.message}`;
statusDiv.style.background = '#ffebee';
}
return [];
}
}mutable clickedPointId = null
mutable clickedPointIndex = nullSESAR OpenContext GEOME Smithsonian
// Cesium viewer setup
viewer = {
// Wait for Cesium to be available
await new Promise(resolve => {
if (typeof Cesium !== 'undefined') resolve();
else {
const check = setInterval(() => {
if (typeof Cesium !== 'undefined') {
clearInterval(check);
resolve();
}
}, 100);
}
});
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIwNzk3NjkyMy1iNGI1LTRkN2UtODRiMy04OTYwYWE0N2M3ZTkiLCJpZCI6Njk1MTcsImlhdCI6MTYzMzU0MTQ3N30.e70dpNzOCDRLDGxRguQCC-tRzGzA-23Xgno5lNgCeB4';
const container = document.getElementById('cesiumContainer');
const v = new Cesium.Viewer(container, {
timeline: false,
animation: false,
baseLayerPicker: false,
fullscreenElement: container,
terrain: Cesium.Terrain.fromWorldTerrain()
});
// Point collection for efficient rendering
v.pointCollection = new Cesium.PointPrimitiveCollection();
v.scene.primitives.add(v.pointCollection);
// Click handler
const handler = new Cesium.ScreenSpaceEventHandler(v.scene.canvas);
handler.setInputAction((e) => {
const picked = v.scene.pick(e.position);
if (Cesium.defined(picked) && picked.primitive && picked.id !== undefined) {
mutable clickedPointId = picked.id.pid;
mutable clickedPointIndex = picked.id.index;
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
return v;
}// Render points on globe
renderPoints = {
if (!viewer || sampleData.length === 0) return null;
const statusDiv = document.getElementById('loading_status');
if (statusDiv) {
statusDiv.style.display = 'block';
statusDiv.textContent = 'Rendering points...';
}
// Clear existing points
viewer.pointCollection.removeAll();
const scalar = new Cesium.NearFarScalar(1.5e2, 3, 8.0e6, 0.5);
const CHUNK_SIZE = 500;
for (let i = 0; i < sampleData.length; i += CHUNK_SIZE) {
const chunk = sampleData.slice(i, i + CHUNK_SIZE);
for (let j = 0; j < chunk.length; j++) {
const row = chunk[j];
viewer.pointCollection.add({
id: { pid: row.pid, index: i + j, row: row },
position: Cesium.Cartesian3.fromDegrees(row.longitude, row.latitude, 0),
pixelSize: 5,
color: getCesiumColor(row.source),
scaleByDistance: scalar,
});
}
// Update progress
if (statusDiv && i % 5000 === 0) {
const pct = Math.round((i / sampleData.length) * 100);
statusDiv.textContent = `Rendering points... ${pct}%`;
}
// Yield to browser
if (i + CHUNK_SIZE < sampleData.length) {
await new Promise(resolve => setTimeout(resolve, 0));
}
}
if (statusDiv) {
statusDiv.textContent = `Rendered ${sampleData.length.toLocaleString()} points`;
setTimeout(() => { statusDiv.style.display = 'none'; }, 2000);
}
return sampleData.length;
}// Get selected sample data
selectedSample = {
if (clickedPointIndex === null || clickedPointIndex < 0) return null;
if (clickedPointIndex >= sampleData.length) return null;
return sampleData[clickedPointIndex];
}// Render sample card
sampleCard = {
if (!selectedSample) {
return html`<div style="padding: 20px; color: #666; text-align: center; background: #f9f9f9; border-radius: 8px;">
Click a point on the globe to see details
</div>`;
}
const s = selectedSample;
const sourceColor = SOURCE_COLORS[s.source] || SOURCE_COLORS.default;
const label = s.label || 'No label';
const description = s.description || '';
const truncDesc = description.length > 200 ? description.substring(0, 200) + '...' : description;
let placeStr = '';
if (s.place_name) {
if (Array.isArray(s.place_name)) {
placeStr = s.place_name.filter(p => p).join(' > ');
} else {
placeStr = String(s.place_name);
}
}
const placeHtml = placeStr ? `<div style="margin-bottom: 4px;"><strong>Place:</strong> ${placeStr.substring(0, 100)}</div>` : '';
const pidShort = s.pid ? (s.pid.length > 50 ? s.pid.substring(0, 50) + '...' : s.pid) : '';
return html`
<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
border: 1px solid #ddd; border-radius: 8px; padding: 16px;
max-width: 500px; background: white; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
<div style="display: flex; align-items: center; margin-bottom: 12px;">
<span style="background: ${sourceColor}; color: white; padding: 4px 8px;
border-radius: 4px; font-size: 12px; font-weight: 600;">${s.source}</span>
</div>
<h3 style="margin: 0 0 8px 0; font-size: 16px; color: #333;">${label}</h3>
<p style="margin: 0 0 12px 0; font-size: 13px; color: #666; line-height: 1.4;">
${truncDesc || '<em>No description</em>'}
</p>
<div style="font-size: 12px; color: #888;">
<div style="margin-bottom: 4px;"><strong>Location:</strong> ${s.latitude.toFixed(4)}, ${s.longitude.toFixed(4)}</div>
${placeHtml}
<div><strong>ID:</strong> <code style="background: #f5f5f5; padding: 2px 4px; border-radius: 3px;">${pidShort}</code></div>
</div>
</div>
`;
}html`<pre style="font-size: 12px; background: #f5f5f5; padding: 12px; border-radius: 4px; overflow-x: auto;">
State:
search: "${searchInput || ''}"
sources: ${JSON.stringify(Array.from(sourceCheckboxes || []))}
view: "${viewMode}"
maxSamples: ${maxSamples}
WHERE clause:
${whereClause}
Total matching: ${Number(totalCount).toLocaleString()}
Loaded: ${sampleData.length.toLocaleString()}
</pre>`