@walkthru-earth/objex 1.2.0 → 1.3.0

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.
Files changed (76) hide show
  1. package/README.md +6 -3
  2. package/dist/components/browser/FileTreeSidebar.svelte +1 -1
  3. package/dist/components/layout/ConnectionDialog.svelte +35 -3
  4. package/dist/components/layout/Sidebar.svelte +28 -2
  5. package/dist/components/viewers/ArchiveViewer.svelte +4 -4
  6. package/dist/components/viewers/CodeViewer.svelte +72 -19
  7. package/dist/components/viewers/CodeViewer.svelte.d.ts +11 -1
  8. package/dist/components/viewers/CogControls.svelte +151 -22
  9. package/dist/components/viewers/CogControls.svelte.d.ts +5 -1
  10. package/dist/components/viewers/CogViewer.svelte +45 -10
  11. package/dist/components/viewers/CopcViewer.svelte +20 -2
  12. package/dist/components/viewers/FlatGeobufViewer.svelte +15 -9
  13. package/dist/components/viewers/MultiCogViewer.svelte +416 -0
  14. package/dist/components/viewers/MultiCogViewer.svelte.d.ts +9 -0
  15. package/dist/components/viewers/PmtilesViewer.svelte +2 -2
  16. package/dist/components/viewers/StacMapViewer.svelte +34 -12
  17. package/dist/components/viewers/StacMapViewer.svelte.d.ts +1 -0
  18. package/dist/components/viewers/StacMosaicViewer.svelte +699 -0
  19. package/dist/components/viewers/StacMosaicViewer.svelte.d.ts +9 -0
  20. package/dist/components/viewers/StacTabViewer.svelte +254 -0
  21. package/dist/components/viewers/StacTabViewer.svelte.d.ts +13 -0
  22. package/dist/components/viewers/TableViewer.svelte +50 -21
  23. package/dist/components/viewers/ViewerRouter.svelte +155 -2
  24. package/dist/components/viewers/ViewerRouter.svelte.d.ts +1 -1
  25. package/dist/components/viewers/ZarrMapViewer.svelte +147 -8
  26. package/dist/components/viewers/ZarrMapViewer.svelte.d.ts +8 -2
  27. package/dist/components/viewers/ZarrViewer.svelte +3 -2
  28. package/dist/components/viewers/pmtiles/PmtilesMapView.svelte +0 -1
  29. package/dist/i18n/ar.js +28 -0
  30. package/dist/i18n/en.js +28 -0
  31. package/dist/index.d.ts +4 -0
  32. package/dist/index.js +2 -0
  33. package/dist/query/index.d.ts +1 -1
  34. package/dist/query/index.js +1 -1
  35. package/dist/query/source.d.ts +12 -0
  36. package/dist/query/source.js +25 -8
  37. package/dist/query/stac-geoparquet.d.ts +31 -0
  38. package/dist/query/stac-geoparquet.js +136 -0
  39. package/dist/query/wasm.js +130 -23
  40. package/dist/storage/adapter.d.ts +9 -0
  41. package/dist/storage/adapter.js +13 -1
  42. package/dist/storage/browser-azure.d.ts +1 -1
  43. package/dist/storage/browser-azure.js +4 -0
  44. package/dist/storage/browser-cloud.d.ts +1 -1
  45. package/dist/storage/browser-cloud.js +7 -0
  46. package/dist/storage/presign.d.ts +13 -0
  47. package/dist/storage/presign.js +55 -0
  48. package/dist/storage/providers.d.ts +6 -0
  49. package/dist/storage/providers.js +13 -2
  50. package/dist/stores/browser.svelte.d.ts +2 -0
  51. package/dist/stores/browser.svelte.js +17 -1
  52. package/dist/stores/connections.svelte.d.ts +38 -23
  53. package/dist/stores/connections.svelte.js +105 -114
  54. package/dist/utils/cog.d.ts +80 -18
  55. package/dist/utils/cog.js +187 -125
  56. package/dist/utils/colormap-sprite.d.ts +39 -0
  57. package/dist/utils/colormap-sprite.js +77 -0
  58. package/dist/utils/connection-identity.d.ts +51 -0
  59. package/dist/utils/connection-identity.js +97 -0
  60. package/dist/utils/host-detection.js +48 -302
  61. package/dist/utils/parquet-metadata.d.ts +7 -1
  62. package/dist/utils/parquet-metadata.js +35 -1
  63. package/dist/utils/stac-geoparquet.d.ts +90 -0
  64. package/dist/utils/stac-geoparquet.js +223 -0
  65. package/dist/utils/stac-hydrate.d.ts +38 -0
  66. package/dist/utils/stac-hydrate.js +243 -0
  67. package/dist/utils/stac.d.ts +136 -0
  68. package/dist/utils/stac.js +176 -0
  69. package/dist/utils/storage-url.d.ts +26 -0
  70. package/dist/utils/storage-url.js +164 -28
  71. package/dist/utils/url.d.ts +13 -0
  72. package/dist/utils/url.js +36 -0
  73. package/dist/utils/wkb.js +22 -8
  74. package/dist/utils/zarr.d.ts +34 -0
  75. package/dist/utils/zarr.js +94 -0
  76. package/package.json +14 -13
@@ -1,15 +1,20 @@
1
1
  <script lang="ts">
2
+ import { MapboxOverlay } from '@deck.gl/mapbox';
2
3
  import type maplibregl from 'maplibre-gl';
3
4
  import maplibreModule from 'maplibre-gl';
4
5
  import { onDestroy, untrack } from 'svelte';
5
6
  import { t } from '../../i18n/index.svelte.js';
6
7
  import { tabResources } from '../../stores/tab-resources.svelte.js';
7
- import type { Tab } from '../../types';
8
- import { buildHttpsUrl } from '../../utils/url.js';
8
+ import type { Tab } from '../../types.js';
9
+ import { createEpsgResolver } from '../../utils/cog.js';
10
+ import { buildHttpsUrlAsync } from '../../utils/url.js';
9
11
  import {
12
+ detectGeoZarr,
10
13
  ensureCodecsRegistered,
11
14
  extractZarrStoreUrl,
15
+ type GeoZarrInfo,
12
16
  inferDims,
17
+ type ZarrHierarchy,
13
18
  type ZarrNode
14
19
  } from '../../utils/zarr.js';
15
20
  import MapContainer from './map/MapContainer.svelte';
@@ -40,21 +45,40 @@ let {
40
45
  variables,
41
46
  coords = [],
42
47
  spatialRefAttrs,
43
- zarrVersion = null
48
+ zarrVersion = null,
49
+ hierarchy = null
44
50
  }: {
45
51
  tab: Tab;
46
52
  variables: ZarrNode[];
47
53
  coords?: ZarrNode[];
48
54
  spatialRefAttrs: Record<string, any> | null;
49
55
  zarrVersion?: number | null;
56
+ /**
57
+ * Full pre-loaded hierarchy. When present, `detectGeoZarr` can short-circuit
58
+ * to the `@developmentseed/deck.gl-zarr` path for GeoZarr-valid stores.
59
+ * Non-GeoZarr stores fall through to `@carbonplan/zarr-layer`.
60
+ */
61
+ hierarchy?: ZarrHierarchy | null;
50
62
  } = $props();
51
63
 
64
+ // GeoZarr detection runs once per hierarchy so the branch decision is stable
65
+ // across selector-slider tweaks. Returns null for non-GeoZarr stores, which
66
+ // sends everything through the existing carbonplan path.
67
+ const geoZarrInfo = $derived<GeoZarrInfo | null>(hierarchy ? detectGeoZarr(hierarchy) : null);
68
+
69
+ // MapboxOverlay holder for the deck.gl-zarr path. Separate from zarrLayer so
70
+ // the two paths can be cleaned up independently.
71
+ let dsZarrOverlay: MapboxOverlay | null = null;
72
+ const dsZarrEpsg = createEpsgResolver();
73
+
52
74
  let loading = $state(true);
53
75
  let error = $state<string | null>(null);
54
76
  let selectedVar = $state('');
55
77
  let zarrLayer: any = null;
56
78
  let mapRef: maplibregl.Map | null = null;
57
79
  let inspectPopup: maplibregl.Popup | null = null;
80
+ let loadGen = 0;
81
+ let addAbort = new AbortController();
58
82
 
59
83
  // Extract proj4 from spatial_ref if available
60
84
  const proj4String = $derived(extractProj4(spatialRefAttrs));
@@ -359,20 +383,39 @@ async function onMapReady(map: maplibregl.Map) {
359
383
  }
360
384
 
361
385
  async function addZarrLayer(map: maplibregl.Map) {
386
+ addAbort.abort();
387
+ addAbort = new AbortController();
388
+ const signal = addAbort.signal;
389
+ const gen = ++loadGen;
362
390
  loading = true;
363
391
  error = null;
364
392
 
365
393
  try {
366
- // Remove existing layer
367
394
  if (zarrLayer && map.getLayer(zarrLayer.id)) {
368
395
  map.removeLayer(zarrLayer.id);
369
396
  }
397
+ if (dsZarrOverlay) {
398
+ try {
399
+ map.removeControl(dsZarrOverlay as unknown as maplibregl.IControl);
400
+ } catch {
401
+ /* already removed */
402
+ }
403
+ dsZarrOverlay = null;
404
+ }
405
+
406
+ if (geoZarrInfo) {
407
+ const used = await tryAddGeoZarrLayer(map, gen, signal);
408
+ if (gen !== loadGen || signal.aborted) return;
409
+ if (used) return;
410
+ }
370
411
 
371
- // Ensure numcodecs codecs (shuffle, zlib, etc.) are registered before zarr-layer uses zarrita
372
412
  await ensureCodecsRegistered();
413
+ if (gen !== loadGen || signal.aborted) return;
373
414
  const { ZarrLayer } = await import('@carbonplan/zarr-layer');
415
+ if (gen !== loadGen || signal.aborted) return;
374
416
 
375
- const storeUrl = buildStoreUrl();
417
+ const storeUrl = await buildStoreUrl();
418
+ if (gen !== loadGen || signal.aborted) return;
376
419
  const selector = buildSelector();
377
420
 
378
421
  const opts: any = {
@@ -452,11 +495,102 @@ async function addZarrLayer(map: maplibregl.Map) {
452
495
  }
453
496
  }
454
497
 
455
- function buildStoreUrl(): string {
456
- const rawUrl = buildHttpsUrl(tab).replace(/\/+$/, '');
498
+ async function buildStoreUrl(): Promise<string> {
499
+ const rawUrl = (await buildHttpsUrlAsync(tab)).replace(/\/+$/, '');
457
500
  return extractZarrStoreUrl(rawUrl) ?? rawUrl;
458
501
  }
459
502
 
503
+ /**
504
+ * Attempt to render via `@developmentseed/deck.gl-zarr`. Returns true on
505
+ * success (carbonplan fallback is skipped), false on any setup error so the
506
+ * caller can fall through to the legacy path. Errors thrown inside the layer
507
+ * after setup propagate through the overlay's `onError`.
508
+ */
509
+ async function tryAddGeoZarrLayer(
510
+ map: maplibregl.Map,
511
+ gen: number,
512
+ signal: AbortSignal
513
+ ): Promise<boolean> {
514
+ if (!geoZarrInfo) return false;
515
+ try {
516
+ const zarrita = await import('zarrita');
517
+ if (gen !== loadGen || signal.aborted) return false;
518
+ const { ZarrLayer } = await import('@developmentseed/deck.gl-zarr');
519
+ if (gen !== loadGen || signal.aborted) return false;
520
+ const storeUrl = await buildStoreUrl();
521
+ if (gen !== loadGen || signal.aborted) return false;
522
+ const store = new zarrita.FetchStore(storeUrl);
523
+ const group = await zarrita.open(store, { kind: 'group' });
524
+ if (gen !== loadGen || signal.aborted) return false;
525
+
526
+ const zarrInfoSnapshot = $state.snapshot(geoZarrInfo) as GeoZarrInfo;
527
+ const layer = new ZarrLayer({
528
+ id: `geozarr-${tab.id}`,
529
+ source: group,
530
+ variable: zarrInfoSnapshot.variantPath || undefined,
531
+ selection: {},
532
+ epsgResolver: dsZarrEpsg,
533
+ getTileData: async (arr, options) => {
534
+ const chunk = await zarrita.get(arr, options.sliceSpec);
535
+ if (gen !== loadGen || signal.aborted) {
536
+ throw new DOMException('Aborted', 'AbortError');
537
+ }
538
+ const data = chunk.data as unknown as ArrayLike<number> & { length: number };
539
+ return {
540
+ width: options.width,
541
+ height: options.height,
542
+ data,
543
+ byteLength: data.length
544
+ };
545
+ },
546
+ renderTile: (data) => {
547
+ const raw = (data as unknown as { data: ArrayLike<number> & { length: number } }).data;
548
+ if (!raw) return { image: undefined } as never;
549
+ let clamped: Uint8ClampedArray;
550
+ const asTyped = raw as unknown as {
551
+ buffer?: ArrayBufferLike;
552
+ byteOffset?: number;
553
+ byteLength?: number;
554
+ };
555
+ if (
556
+ asTyped.buffer &&
557
+ typeof asTyped.byteOffset === 'number' &&
558
+ typeof asTyped.byteLength === 'number'
559
+ ) {
560
+ clamped = new Uint8ClampedArray(asTyped.buffer, asTyped.byteOffset, asTyped.byteLength);
561
+ } else {
562
+ clamped = new Uint8ClampedArray(raw as unknown as Uint8Array);
563
+ }
564
+ const img = new ImageData(
565
+ clamped as unknown as Uint8ClampedArray<ArrayBuffer>,
566
+ data.width,
567
+ data.height
568
+ );
569
+ return { image: img };
570
+ }
571
+ });
572
+
573
+ const overlay = new MapboxOverlay({
574
+ interleaved: false,
575
+ layers: [layer],
576
+ onError: (err) => {
577
+ error = err?.message || String(err);
578
+ loading = false;
579
+ }
580
+ });
581
+ dsZarrOverlay = overlay;
582
+ map.addControl(overlay as unknown as maplibregl.IControl);
583
+ loading = false;
584
+ return true;
585
+ } catch {
586
+ // Fall back to carbonplan path on any setup failure (e.g. the store
587
+ // looked like GeoZarr by shape but zarrita open failed, or the group
588
+ // attrs don't actually parse). Silent by design, the caller will mount
589
+ // carbonplan's ZarrLayer which surfaces its own errors.
590
+ return false;
591
+ }
592
+ }
593
+
460
594
  // Re-render when selector changes
461
595
  async function updateSelector() {
462
596
  if (!zarrLayer) return;
@@ -476,6 +610,7 @@ async function changeVariable() {
476
610
  }
477
611
 
478
612
  function cleanup() {
613
+ addAbort.abort();
479
614
  inspectPopup?.remove();
480
615
  inspectPopup = null;
481
616
  try {
@@ -483,10 +618,14 @@ function cleanup() {
483
618
  if (zarrLayer && mapRef?.getLayer('zarr-data')) {
484
619
  mapRef.removeLayer('zarr-data');
485
620
  }
621
+ if (mapRef && dsZarrOverlay) {
622
+ mapRef.removeControl(dsZarrOverlay as unknown as maplibregl.IControl);
623
+ }
486
624
  } catch {
487
625
  // map may already be destroyed
488
626
  }
489
627
  zarrLayer = null;
628
+ dsZarrOverlay = null;
490
629
  mapRef = null;
491
630
  }
492
631
 
@@ -1,11 +1,17 @@
1
- import type { Tab } from '../../types';
2
- import { type ZarrNode } from '../../utils/zarr.js';
1
+ import type { Tab } from '../../types.js';
2
+ import { type ZarrHierarchy, type ZarrNode } from '../../utils/zarr.js';
3
3
  type $$ComponentProps = {
4
4
  tab: Tab;
5
5
  variables: ZarrNode[];
6
6
  coords?: ZarrNode[];
7
7
  spatialRefAttrs: Record<string, any> | null;
8
8
  zarrVersion?: number | null;
9
+ /**
10
+ * Full pre-loaded hierarchy. When present, `detectGeoZarr` can short-circuit
11
+ * to the `@developmentseed/deck.gl-zarr` path for GeoZarr-valid stores.
12
+ * Non-GeoZarr stores fall through to `@carbonplan/zarr-layer`.
13
+ */
14
+ hierarchy?: ZarrHierarchy | null;
9
15
  };
10
16
  declare const ZarrMapViewer: import("svelte").Component<$$ComponentProps, {}, "">;
11
17
  type ZarrMapViewer = ReturnType<typeof ZarrMapViewer>;
@@ -9,7 +9,7 @@ import {
9
9
  } from '../ui/resizable/index.js';
10
10
  import { t } from '../../i18n/index.svelte.js';
11
11
  import type { Tab } from '../../types';
12
- import { buildHttpsUrl } from '../../utils/url.js';
12
+ import { buildHttpsUrlAsync } from '../../utils/url.js';
13
13
  import { getUrlView, updateUrlView } from '../../utils/url-state.js';
14
14
  import {
15
15
  computeChunkCount,
@@ -120,7 +120,7 @@ async function loadHierarchy() {
120
120
  error = null;
121
121
 
122
122
  try {
123
- const rawUrl = buildHttpsUrl(tab).replace(/\/+$/, '');
123
+ const rawUrl = (await buildHttpsUrlAsync(tab)).replace(/\/+$/, '');
124
124
  const url = extractZarrStoreUrl(rawUrl) ?? rawUrl;
125
125
  const storeName = tab.name.replace(/\.(zarr|zr3)$/, '');
126
126
 
@@ -474,6 +474,7 @@ function selectStoreAttrs() {
474
474
  coords={coordArrays}
475
475
  spatialRefAttrs={hierarchy?.spatialRefAttrs ?? null}
476
476
  zarrVersion={hierarchy?.zarrVersion}
477
+ hierarchy={hierarchy ?? null}
477
478
  />
478
479
  {/await}
479
480
  {/key}
@@ -12,7 +12,6 @@ import type { Tab } from '../../../types';
12
12
  import { setupSelectionLayer, updateSelection } from '../../../utils/map-selection.js';
13
13
  import { buildPmtilesLayers, getPmtilesProtocol, type PmtilesMetadata } from '../../../utils/pmtiles';
14
14
  import { layerHue } from '../../../utils/pmtiles-tile.js';
15
- import { buildHttpsUrl } from '../../../utils/url.js';
16
15
  import AttributeTable from '../map/AttributeTable.svelte';
17
16
  import MapContainer from '../map/MapContainer.svelte';
18
17
 
package/dist/i18n/ar.js CHANGED
@@ -40,6 +40,8 @@ export const ar = {
40
40
  'connection.readOnlyCliTitle': 'تقييد عبر سطر الأوامر',
41
41
  'connection.testSuccess': 'الاتصال ناجح',
42
42
  'connection.testFail': 'فشل الاتصال. تحقق من الإعدادات وحاول مرة أخرى.',
43
+ 'connection.duplicateMerged': 'تم العثور على اتصال موجود ("{name}") لهذه الحاوية. تم تحديث بيانات الاعتماد.',
44
+ 'connection.duplicateBlocked': 'يوجد بالفعل اتصال آخر ("{name}") يستخدم هذه الحاوية. عدّله بدلاً من ذلك.',
43
45
  'connection.testButton': 'اختبار الاتصال',
44
46
  'connection.testing': 'جارٍ الاختبار...',
45
47
  'connection.cancel': 'إلغاء',
@@ -300,6 +302,7 @@ export const ar = {
300
302
  'code.stacCatalog': 'كتالوج STAC',
301
303
  'code.stacCollection': 'مجموعة STAC',
302
304
  'code.stacItem': 'عنصر STAC',
305
+ 'code.stacGeoparquet': 'stac-geoparquet',
303
306
  'code.browseStac': 'تصفح',
304
307
  'code.keplerGl': 'Kepler.gl',
305
308
  'code.openKepler': 'فتح الخريطة',
@@ -342,6 +345,7 @@ export const ar = {
342
345
  'map.flatgeobufInfo': 'معلومات FlatGeobuf',
343
346
  'map.cogInfo': 'معلومات COG',
344
347
  'map.cogCorsError': 'تعذّر تحميل COG: الخادم لا يسمح بطلبات عبر النطاقات (CORS). يجب استضافة الملف مع تفعيل ترويسات CORS.',
348
+ 'map.cogInvalidTiff': 'هذا الملف ليس ملف TIFF صالح. نوع المحتوى image/tiff لكن الأجزاء الأولى من الملف لا تطابق توقيع TIFF، قد يكون الملف تالفاً أو مشفّراً أو مُعنوناً بشكل خاطئ.',
345
349
  'map.cogUnsupportedFormat': 'يستخدم هذا الملف صيغة {{type}} غير مدعومة لعرض الخريطة. يمكن عرض ملفات COG بصيغة RGB فقط.',
346
350
  'map.noGeoColumn': 'لم يتم اكتشاف عمود هندسي في المخطط',
347
351
  'map.noData': 'لا تتوفر بيانات لعرض الخريطة',
@@ -381,11 +385,35 @@ export const ar = {
381
385
  'mapInfo.columns': 'الأعمدة',
382
386
  'mapInfo.size': 'الحجم',
383
387
  'mapInfo.bands': 'النطاقات',
388
+ 'map.mosaicEmpty': 'كتالوج STAC لا يحتوي على عناصر بها أصول COG.',
389
+ 'map.mosaicNoAssets': 'لا تحتوي عناصر STAC على عنوان URL صالح لأصل COG.',
390
+ 'map.multiCogMissingBands': 'عنصر STAC هذا ينقصه نطاقات الأحمر والأخضر والأزرق اللازمة للتركيب.',
391
+ 'map.multiCogPreset.label': 'الإعداد المسبق',
392
+ 'map.multiCogPreset.trueColor': 'ألوان طبيعية',
393
+ 'map.multiCogPreset.falseColorIR': 'أشعة تحت حمراء كاذبة اللون',
394
+ 'map.multiCogPreset.swir': 'الأشعة تحت الحمراء قصيرة الموجة',
395
+ 'map.multiCogPreset.vegetation': 'الغطاء النباتي',
396
+ 'map.multiCogPreset.agriculture': 'الزراعة',
397
+ // تبديل عرض STAC (StacTabViewer)
398
+ 'stac.viewMosaic': 'خريطة',
399
+ 'stac.viewMultiCog': 'خريطة (نطاقات)',
400
+ 'stac.viewStacMap': 'stac-map',
401
+ 'stac.viewBrowser': 'متصفح STAC',
402
+ 'stac.viewJson': 'JSON',
403
+ 'stac.viewTable': 'جدول',
404
+ 'stac.stacBrowserJsonOnly': 'متصفح STAC يدعم كتالوجات JSON فقط. استخدم stac-map لملفات parquet.',
405
+ 'stac.iframeDisabledPrivate': 'معطل للبكتات الخاصة، الإطار الخارجي لا يستطيع توقيع طلباته. استخدم الخريطة أو JSON بدلاً من ذلك.',
406
+ 'stac.iframePrivateBucketWarning': 'بكت خاص، الإطار الخارجي لا يستطيع توقيع طلباته، لذا ستفشل طلبات العناصر الفرعية بـ 403. لكن المانيفست الجذري سيعمل.',
407
+ 'stac.mosaicSourcesOne': 'فسيفساء، {count} مصدر',
408
+ 'stac.mosaicSourcesOther': 'فسيفساء، {count} مصادر',
409
+ 'stac.mosaicInfo': 'معلومات الفسيفساء',
410
+ 'stac.mosaicSourcesLabel': 'المصادر',
384
411
  // COG Controls
385
412
  'cog.style': 'النمط',
386
413
  'cog.band': 'النطاق',
387
414
  'cog.singleBand': 'نطاق واحد',
388
415
  'cog.colorRamp': 'تدرج الألوان',
416
+ 'cog.colorRampSearch': 'بحث في التدرجات…',
389
417
  'cog.pixelValue': 'قيمة البكسل',
390
418
  'cog.reading': 'قراءة البكسل...',
391
419
  'cog.rescale': 'إعادة القياس',
package/dist/i18n/en.js CHANGED
@@ -40,6 +40,8 @@ export const en = {
40
40
  'connection.readOnlyCliTitle': 'Restrict via CLI',
41
41
  'connection.testSuccess': 'Connection successful',
42
42
  'connection.testFail': 'Connection failed. Check your settings and try again.',
43
+ 'connection.duplicateMerged': 'Matched an existing connection ("{name}") for this bucket. Credentials were updated.',
44
+ 'connection.duplicateBlocked': 'Another connection ("{name}") already uses this bucket. Edit that one instead.',
43
45
  'connection.testButton': 'Test Connection',
44
46
  'connection.testing': 'Testing...',
45
47
  'connection.cancel': 'Cancel',
@@ -300,6 +302,7 @@ export const en = {
300
302
  'code.stacCatalog': 'STAC Catalog',
301
303
  'code.stacCollection': 'STAC Collection',
302
304
  'code.stacItem': 'STAC Item',
305
+ 'code.stacGeoparquet': 'stac-geoparquet',
303
306
  'code.browseStac': 'Browse',
304
307
  'code.keplerGl': 'Kepler.gl',
305
308
  'code.openKepler': 'Open Map',
@@ -342,6 +345,7 @@ export const en = {
342
345
  'map.flatgeobufInfo': 'FlatGeobuf Info',
343
346
  'map.cogInfo': 'COG Info',
344
347
  'map.cogCorsError': 'Cannot load COG: the server does not allow cross-origin requests (CORS). The file must be hosted with CORS headers enabled.',
348
+ 'map.cogInvalidTiff': 'This file is not a valid TIFF. The server advertises image/tiff but the bytes do not match the TIFF signature, the file may be corrupt, encrypted, or mislabeled.',
345
349
  'map.cogUnsupportedFormat': 'This COG uses {{type}} format which is not supported for map rendering. Only RGB COGs can be displayed.',
346
350
  'map.noGeoColumn': 'No geometry column detected in schema',
347
351
  'map.noData': 'No data available for map view',
@@ -381,11 +385,35 @@ export const en = {
381
385
  'mapInfo.columns': 'Columns',
382
386
  'mapInfo.size': 'Size',
383
387
  'mapInfo.bands': 'Bands',
388
+ 'map.mosaicEmpty': 'STAC catalog has no items with COG assets.',
389
+ 'map.mosaicNoAssets': 'None of the STAC items expose a usable COG asset URL.',
390
+ 'map.multiCogMissingBands': 'This STAC item is missing the red/green/blue bands required for a composite.',
391
+ 'map.multiCogPreset.label': 'Preset',
392
+ 'map.multiCogPreset.trueColor': 'True Color',
393
+ 'map.multiCogPreset.falseColorIR': 'False-Color IR',
394
+ 'map.multiCogPreset.swir': 'SWIR',
395
+ 'map.multiCogPreset.vegetation': 'Vegetation',
396
+ 'map.multiCogPreset.agriculture': 'Agriculture',
397
+ // STAC tab toggle (StacTabViewer)
398
+ 'stac.viewMosaic': 'Map',
399
+ 'stac.viewMultiCog': 'Map (Bands)',
400
+ 'stac.viewStacMap': 'stac-map',
401
+ 'stac.viewBrowser': 'STAC Browser',
402
+ 'stac.viewJson': 'JSON',
403
+ 'stac.viewTable': 'Table',
404
+ 'stac.stacBrowserJsonOnly': 'STAC Browser supports JSON catalogs only. Use stac-map for parquet.',
405
+ 'stac.iframeDisabledPrivate': 'Disabled for private buckets, the external iframe cannot sign its own crawl. Use Map or JSON instead.',
406
+ 'stac.iframePrivateBucketWarning': 'Private bucket, the external iframe cannot sign its own crawl, so child items will 403. The top manifest still renders.',
407
+ 'stac.mosaicSourcesOne': 'Mosaic, {count} source',
408
+ 'stac.mosaicSourcesOther': 'Mosaic, {count} sources',
409
+ 'stac.mosaicInfo': 'Mosaic info',
410
+ 'stac.mosaicSourcesLabel': 'Sources',
384
411
  // COG Controls
385
412
  'cog.style': 'Style',
386
413
  'cog.band': 'Band',
387
414
  'cog.singleBand': 'Single',
388
415
  'cog.colorRamp': 'Color ramp',
416
+ 'cog.colorRampSearch': 'Search ramps…',
389
417
  'cog.pixelValue': 'Pixel Value',
390
418
  'cog.reading': 'Reading pixel...',
391
419
  'cog.rescale': 'Rescale',
package/dist/index.d.ts CHANGED
@@ -14,6 +14,8 @@ export type { CogInfo, GeoBounds } from './utils/cog.js';
14
14
  export { buildDataTypeLabel, clampBounds, SF_LABELS, safeClamp } from './utils/cog.js';
15
15
  export type { TypeCategory } from './utils/column-types.js';
16
16
  export { classifyType, typeBadgeClass, typeColor, typeLabel } from './utils/column-types.js';
17
+ export type { ConnectionIdentityInput } from './utils/connection-identity.js';
18
+ export { connectionIdentityKey, isSameConnectionIdentity, normalizeEndpoint, normalizeProvider } from './utils/connection-identity.js';
17
19
  export { handleLoadError } from './utils/error.js';
18
20
  export { escapeCsvField, serializeToCsv, serializeToJson } from './utils/export.js';
19
21
  export type { SortConfig, SortDirection, SortField } from './utils/file-sort.js';
@@ -28,6 +30,8 @@ export type { ParsedMarkdownDocument, SqlBlock } from './utils/markdown-sql.js';
28
30
  export { interpolateTemplates, markSqlBlocks, parseMarkdownDocument } from './utils/markdown-sql.js';
29
31
  export type { GeoColumnMeta, GeoParquetMeta, ParquetFileMetadata } from './utils/parquet-metadata.js';
30
32
  export { extractBounds, extractEpsgFromGeoMeta, extractGeometryTypes, readParquetMetadata } from './utils/parquet-metadata.js';
33
+ export type { StacBboxStruct, StacGeoparquetRow, StacGeoparquetSchemaColumn, StacRowToItemOptions } from './utils/stac-geoparquet.js';
34
+ export { flattenStacBbox, isStacGeoparquetSchema, pickStacPrimaryAsset, resolveStacAssetHref, STAC_GEOPARQUET_REQUIRED_COLUMNS, stacRowToItem } from './utils/stac-geoparquet.js';
31
35
  export type { Defaults, ParsedStorageUrl, StorageProvider } from './utils/storage-url.js';
32
36
  export { describeParseResult, looksLikeUrl, parseStorageUrl } from './utils/storage-url.js';
33
37
  export type { GeoType, ParsedGeometry } from './utils/wkb.js';
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ export { copyToClipboard, wireCodeCopyButtons } from './utils/clipboard.js';
12
12
  export { getNativeScheme, resolveCloudUrl, safeDecodeURIComponent } from './utils/cloud-url.js';
13
13
  export { buildDataTypeLabel, clampBounds, SF_LABELS, safeClamp } from './utils/cog.js';
14
14
  export { classifyType, typeBadgeClass, typeColor, typeLabel } from './utils/column-types.js';
15
+ export { connectionIdentityKey, isSameConnectionIdentity, normalizeEndpoint, normalizeProvider } from './utils/connection-identity.js';
15
16
  // Error handling
16
17
  export { handleLoadError } from './utils/error.js';
17
18
  // Data export / serialization
@@ -24,6 +25,7 @@ export { generateHexDump } from './utils/hex.js';
24
25
  export { loadFromStorage, persistToStorage } from './utils/local-storage.js';
25
26
  export { interpolateTemplates, markSqlBlocks, parseMarkdownDocument } from './utils/markdown-sql.js';
26
27
  export { extractBounds, extractEpsgFromGeoMeta, extractGeometryTypes, readParquetMetadata } from './utils/parquet-metadata.js';
28
+ export { flattenStacBbox, isStacGeoparquetSchema, pickStacPrimaryAsset, resolveStacAssetHref, STAC_GEOPARQUET_REQUIRED_COLUMNS, stacRowToItem } from './utils/stac-geoparquet.js';
27
29
  export { describeParseResult, looksLikeUrl, parseStorageUrl } from './utils/storage-url.js';
28
30
  // Utilities
29
31
  export { findGeoColumn, findGeoColumnFromRows, parseWKB, toBinary } from './utils/wkb.js';
@@ -2,4 +2,4 @@ import type { QueryEngine } from './engine';
2
2
  export declare function getQueryEngine(): Promise<QueryEngine>;
3
3
  export type { MapQueryHandle, MapQueryResult, QueryEngine, QueryHandle, QueryResult, QuerySource, SchemaField } from './engine';
4
4
  export { QueryCancelledError } from './engine';
5
- export { type ResolvedTableSource, resolveTableSource } from './source.js';
5
+ export { type ResolvedTableSource, resolveTableSource, resolveTableSourceAsync } from './source.js';
@@ -17,4 +17,4 @@ export async function getQueryEngine() {
17
17
  return enginePromise;
18
18
  }
19
19
  export { QueryCancelledError } from './engine';
20
- export { resolveTableSource } from './source.js';
20
+ export { resolveTableSource, resolveTableSourceAsync } from './source.js';
@@ -10,6 +10,12 @@
10
10
  */
11
11
  import type { Tab } from '../types.js';
12
12
  import type { QuerySource } from './engine.js';
13
+ /**
14
+ * True when a source ref points at a self-authenticating HTTPS URL (e.g. a
15
+ * presigned `read_parquet('https://...?X-Amz-Signature=...')`). Used to decide
16
+ * whether DuckDB needs S3 credential config — presigned URLs don't.
17
+ */
18
+ export declare function isHttpsSourceRef(ref: string): boolean;
13
19
  export interface ResolvedTableSource extends QuerySource {
14
20
  /**
15
21
  * True when the tab is file-backed and hyparquet / parquet metadata
@@ -28,3 +34,9 @@ export interface ResolvedTableSource extends QuerySource {
28
34
  * over a tab's lifetime.
29
35
  */
30
36
  export declare function resolveTableSource(tab: Tab): ResolvedTableSource;
37
+ /**
38
+ * Async counterpart of `resolveTableSource`. Returns a presigned HTTPS URL
39
+ * for `signed-s3` connections so DuckDB httpfs can fetch without the
40
+ * `Authorization` header preflight.
41
+ */
42
+ export declare function resolveTableSourceAsync(tab: Tab): Promise<ResolvedTableSource>;
@@ -9,13 +9,16 @@
9
9
  * stay free of ad-hoc branching.
10
10
  */
11
11
  import { buildDuckDbSource } from '../file-icons/index.js';
12
- import { buildDuckDbUrl } from '../utils/url.js';
12
+ import { buildDuckDbUrl, buildDuckDbUrlAsync } from '../utils/url.js';
13
13
  /**
14
- * Resolve a tab to its QuerySource. Must be called lazily (inside reactive
15
- * expressions or functions) because `tab.sourceRef` and `tab.path` can change
16
- * over a tab's lifetime.
14
+ * True when a source ref points at a self-authenticating HTTPS URL (e.g. a
15
+ * presigned `read_parquet('https://...?X-Amz-Signature=...')`). Used to decide
16
+ * whether DuckDB needs S3 credential config — presigned URLs don't.
17
17
  */
18
- export function resolveTableSource(tab) {
18
+ export function isHttpsSourceRef(ref) {
19
+ return /(?:^|\(\s*['"])https:\/\//.test(ref);
20
+ }
21
+ function toResolved(tab, fileUrl) {
19
22
  if (tab.sourceRef) {
20
23
  return {
21
24
  ref: tab.sourceRef,
@@ -25,13 +28,27 @@ export function resolveTableSource(tab) {
25
28
  label: tab.name
26
29
  };
27
30
  }
28
- const fileUrl = buildDuckDbUrl(tab);
29
- const ref = buildDuckDbSource(tab.path, fileUrl);
30
31
  return {
31
- ref,
32
+ ref: buildDuckDbSource(tab.path, fileUrl ?? ''),
32
33
  filePath: tab.path,
33
34
  isFileSource: true,
34
35
  fileUrl,
35
36
  label: tab.name
36
37
  };
37
38
  }
39
+ /**
40
+ * Resolve a tab to its QuerySource. Must be called lazily (inside reactive
41
+ * expressions or functions) because `tab.sourceRef` and `tab.path` can change
42
+ * over a tab's lifetime.
43
+ */
44
+ export function resolveTableSource(tab) {
45
+ return toResolved(tab, tab.sourceRef ? null : buildDuckDbUrl(tab));
46
+ }
47
+ /**
48
+ * Async counterpart of `resolveTableSource`. Returns a presigned HTTPS URL
49
+ * for `signed-s3` connections so DuckDB httpfs can fetch without the
50
+ * `Authorization` header preflight.
51
+ */
52
+ export async function resolveTableSourceAsync(tab) {
53
+ return toResolved(tab, tab.sourceRef ? null : await buildDuckDbUrlAsync(tab));
54
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Read a stac-geoparquet file through the existing DuckDB-WASM engine and
3
+ * materialize a standard STAC FeatureCollection in memory.
4
+ *
5
+ * Reuses:
6
+ * - `getQueryEngine()` + `queryCancellable`/`query` for the single worker
7
+ * - `resolveTableSourceAsync(tab)` for presigned `signed-s3` URL handling
8
+ * - `stacRowToItem` from `utils/stac-geoparquet.js` for the pure transform
9
+ * - `parseWKB` from `utils/wkb.js` for geometry decoding
10
+ *
11
+ * The returned `FeatureCollection` is the same shape `classifyStac()` returns
12
+ * as `{ kind: 'item-collection', fc }`, so downstream viewers
13
+ * (`StacMosaicViewer`, `MultiCogViewer`) consume it unchanged.
14
+ */
15
+ import type { Tab } from '../types.js';
16
+ import type { StacFeatureCollection } from '../utils/stac.js';
17
+ export interface QueryStacGeoparquetOptions {
18
+ signal?: AbortSignal;
19
+ /** Hard cap on rows. Matches `hydrateStacItems` default. */
20
+ limit?: number;
21
+ }
22
+ /**
23
+ * Query a stac-geoparquet tab and return a STAC FeatureCollection whose
24
+ * features are proper STAC Items (assets absolutized, WKB decoded, bbox
25
+ * flattened).
26
+ *
27
+ * @param tab - the tab pointing at the `.parquet` file
28
+ * @param connId - connection id used for DuckDB's httpfs S3 config; pass
29
+ * an empty string for URL-source tabs (DuckDB will use anonymous httpfs)
30
+ */
31
+ export declare function queryStacGeoparquetFeatureCollection(tab: Tab, connId: string, opts?: QueryStacGeoparquetOptions): Promise<StacFeatureCollection>;