@walkthru-earth/objex-utils 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.
package/README.md CHANGED
@@ -29,6 +29,13 @@ import {
29
29
  extractEpsgFromGeoMeta,
30
30
  extractBounds,
31
31
 
32
+ // stac-geoparquet (detection + row → STAC Item)
33
+ isStacGeoparquetSchema,
34
+ stacRowToItem,
35
+ flattenStacBbox,
36
+ pickStacPrimaryAsset,
37
+ resolveStacAssetHref,
38
+
32
39
  // Storage URLs
33
40
  parseStorageUrl,
34
41
  resolveCloudUrl,
@@ -67,6 +74,7 @@ Full per-module developer reference lives in [`docs/`](./docs/README.md). Each p
67
74
  | [`docs/geometry.md`](./docs/geometry.md) | WKB parser, GeoArrow builder, geometry-column detection |
68
75
  | [`docs/cog.md`](./docs/cog.md) | Cloud-Optimized GeoTIFF pipeline helpers, band configs, color ramps |
69
76
  | [`docs/parquet-metadata.md`](./docs/parquet-metadata.md) | `readParquetMetadata` + CRS / bounds / geometry-type extractors |
77
+ | [`docs/stac-geoparquet.md`](./docs/stac-geoparquet.md) | stac-geoparquet detection + row → STAC Item transforms |
70
78
  | [`docs/storage.md`](./docs/storage.md) | URL parsing, provider registry, `StorageAdapter`, `UrlAdapter` |
71
79
  | [`docs/query-engine.md`](./docs/query-engine.md) | `QueryEngine` interface + handle / result types |
72
80
  | [`docs/file-types.md`](./docs/file-types.md) | File-type registry: `getFileTypeInfo`, `getViewerKind`, `getDuckDbReadFn`, … |
package/dist/index.cjs CHANGED
@@ -1357,6 +1357,11 @@ function buildEndpointFromTemplate(id, region) {
1357
1357
  if (!def?.endpointTemplate) return "";
1358
1358
  return def.endpointTemplate.replace("{region}", region);
1359
1359
  }
1360
+ function resolveProviderEndpoint(provider, region) {
1361
+ const def = PROVIDERS[provider];
1362
+ if (!def?.endpointTemplate) return "";
1363
+ return buildEndpointFromTemplate(provider, region || def.defaultRegion);
1364
+ }
1360
1365
  function buildProviderBaseUrl(provider, endpoint, bucket, region) {
1361
1366
  if (endpoint) {
1362
1367
  return `${endpoint.replace(/\/$/, "")}/${bucket}`;
@@ -1371,6 +1376,15 @@ function buildProviderBaseUrl(provider, endpoint, bucket, region) {
1371
1376
  function isGcsProvider(provider, endpoint) {
1372
1377
  return provider === "gcs" || !!endpoint && /storage\.googleapis\.com/i.test(endpoint);
1373
1378
  }
1379
+ function getAccessMode(conn) {
1380
+ if (conn.provider === "azure") return "sas-https";
1381
+ if (conn.anonymous) return "public-https";
1382
+ return "signed-s3";
1383
+ }
1384
+ function isPubliclyStreamable(conn) {
1385
+ const mode = getAccessMode(conn);
1386
+ return mode === "public-https" || mode === "sas-https";
1387
+ }
1374
1388
 
1375
1389
  // ../../src/lib/storage/url-adapter.ts
1376
1390
  var UrlAdapter = class {
@@ -1640,6 +1654,17 @@ function formatValue(value) {
1640
1654
  }
1641
1655
 
1642
1656
  // ../../src/lib/utils/export.ts
1657
+ function triggerDownload(content, filename, mimeType) {
1658
+ const blob = new Blob([content], { type: mimeType });
1659
+ const url = URL.createObjectURL(blob);
1660
+ const a = document.createElement("a");
1661
+ a.href = url;
1662
+ a.download = filename;
1663
+ document.body.appendChild(a);
1664
+ a.click();
1665
+ document.body.removeChild(a);
1666
+ URL.revokeObjectURL(url);
1667
+ }
1643
1668
  function formatCellValue(value) {
1644
1669
  if (value === null || value === void 0) return "";
1645
1670
  if (value instanceof Date) return value.toISOString();
@@ -1674,6 +1699,20 @@ function serializeToJson(columns, rows) {
1674
1699
  });
1675
1700
  return JSON.stringify(data, jsonReplacerBigInt, 2);
1676
1701
  }
1702
+ function exportToCsv(columns, rows, filename) {
1703
+ triggerDownload(
1704
+ serializeToCsv(columns, rows),
1705
+ filename.endsWith(".csv") ? filename : `${filename}.csv`,
1706
+ "text/csv;charset=utf-8;"
1707
+ );
1708
+ }
1709
+ function exportToJson(columns, rows, filename) {
1710
+ triggerDownload(
1711
+ serializeToJson(columns, rows),
1712
+ filename.endsWith(".json") ? filename : `${filename}.json`,
1713
+ "application/json"
1714
+ );
1715
+ }
1677
1716
 
1678
1717
  // ../../src/lib/utils/file-sort.ts
1679
1718
  function sortFileEntries(entries, config) {
@@ -2438,6 +2477,15 @@ async function readParquetMetadata(url) {
2438
2477
  name: col.name,
2439
2478
  type: mapParquetType(col)
2440
2479
  }));
2480
+ const topLevelColumns = [];
2481
+ const root = metadata.schema[0];
2482
+ const rootChildren = root?.num_children ?? 0;
2483
+ let cursor = 1;
2484
+ for (let i = 0; i < rootChildren && cursor < metadata.schema.length; i++) {
2485
+ const el = metadata.schema[cursor];
2486
+ if (el?.name) topLevelColumns.push(el.name);
2487
+ cursor += countSubtree(metadata.schema, cursor);
2488
+ }
2441
2489
  let geo = null;
2442
2490
  let legacyGeoParquet = false;
2443
2491
  const geoKv = metadata.key_value_metadata?.find((kv) => kv.key === "geo");
@@ -2479,7 +2527,26 @@ async function readParquetMetadata(url) {
2479
2527
  compression = [...codecs].join(", ");
2480
2528
  }
2481
2529
  }
2482
- return { rowCount, schema, geo, legacyGeoParquet, createdBy, numRowGroups, compression };
2530
+ return {
2531
+ rowCount,
2532
+ schema,
2533
+ topLevelColumns,
2534
+ geo,
2535
+ legacyGeoParquet,
2536
+ createdBy,
2537
+ numRowGroups,
2538
+ compression
2539
+ };
2540
+ }
2541
+ function countSubtree(schema, start) {
2542
+ const el = schema[start];
2543
+ if (!el) return 0;
2544
+ const n = el.num_children ?? 0;
2545
+ let cursor = start + 1;
2546
+ for (let i = 0; i < n; i++) {
2547
+ cursor += countSubtree(schema, cursor);
2548
+ }
2549
+ return cursor - start;
2483
2550
  }
2484
2551
  function extractEpsgFromGeoMeta(geo) {
2485
2552
  const primaryCol = geo.columns[geo.primaryColumn];
@@ -2518,6 +2585,144 @@ function extractBounds(geo) {
2518
2585
  return [primaryCol.bbox[0], primaryCol.bbox[1], primaryCol.bbox[2], primaryCol.bbox[3]];
2519
2586
  }
2520
2587
 
2588
+ // ../../src/lib/utils/stac-geoparquet.ts
2589
+ var STAC_GEOPARQUET_REQUIRED_COLUMNS = [
2590
+ "stac_version",
2591
+ "type",
2592
+ "geometry",
2593
+ "assets"
2594
+ ];
2595
+ function isStacGeoparquetSchema(schema) {
2596
+ if (!Array.isArray(schema) || schema.length === 0) return false;
2597
+ const names = new Set(schema.map((c) => c.name));
2598
+ return STAC_GEOPARQUET_REQUIRED_COLUMNS.every((c) => names.has(c));
2599
+ }
2600
+ function flattenStacBbox(bbox) {
2601
+ if (!bbox) return null;
2602
+ if (Array.isArray(bbox)) {
2603
+ if (bbox.length < 4) return null;
2604
+ const [minX, minY, maxX, maxY] = bbox;
2605
+ if (![minX, minY, maxX, maxY].every((v) => Number.isFinite(v))) return null;
2606
+ return [minX, minY, maxX, maxY];
2607
+ }
2608
+ if (typeof bbox === "object") {
2609
+ const { xmin, ymin, xmax, ymax } = bbox;
2610
+ if (![xmin, ymin, xmax, ymax].every((v) => Number.isFinite(v))) return null;
2611
+ return [xmin, ymin, xmax, ymax];
2612
+ }
2613
+ return null;
2614
+ }
2615
+ function resolveStacAssetHref(href, baseUrl) {
2616
+ if (!href) return href;
2617
+ if (/^[a-z][a-z0-9+\-.]*:\/\//i.test(href)) return href;
2618
+ try {
2619
+ return new URL(href, baseUrl).toString();
2620
+ } catch {
2621
+ return href;
2622
+ }
2623
+ }
2624
+ function pickStacPrimaryAsset(assets, preferredKeys) {
2625
+ if (!assets || typeof assets !== "object") return null;
2626
+ const entries = Object.entries(assets).filter(
2627
+ ([, a]) => a && typeof a === "object" && typeof a.href === "string"
2628
+ );
2629
+ if (entries.length === 0) return null;
2630
+ if (preferredKeys) {
2631
+ for (const key of preferredKeys) {
2632
+ const match = entries.find(([k]) => k === key);
2633
+ if (match) return { key: match[0], asset: match[1] };
2634
+ }
2635
+ }
2636
+ const data = entries.find(([k]) => k === "data");
2637
+ if (data) return { key: data[0], asset: data[1] };
2638
+ const byRole = entries.find(([, a]) => Array.isArray(a.roles) && a.roles.includes("data"));
2639
+ if (byRole) return { key: byRole[0], asset: byRole[1] };
2640
+ return { key: entries[0][0], asset: entries[0][1] };
2641
+ }
2642
+ function normalizeAssetsField(value, baseUrl) {
2643
+ if (!value || typeof value !== "object") return void 0;
2644
+ const out = {};
2645
+ for (const [key, raw] of Object.entries(value)) {
2646
+ if (!raw || typeof raw !== "object") continue;
2647
+ const asset = raw;
2648
+ if (typeof asset.href !== "string" || !asset.href) continue;
2649
+ out[key] = {
2650
+ ...asset,
2651
+ href: resolveStacAssetHref(asset.href, baseUrl)
2652
+ };
2653
+ }
2654
+ return Object.keys(out).length > 0 ? out : void 0;
2655
+ }
2656
+ function normalizeLinksField(value, baseUrl) {
2657
+ if (!Array.isArray(value)) return void 0;
2658
+ const links = [];
2659
+ for (const raw of value) {
2660
+ if (!raw || typeof raw !== "object") continue;
2661
+ const link = raw;
2662
+ if (typeof link.href !== "string" || typeof link.rel !== "string") continue;
2663
+ links.push({ ...link, href: resolveStacAssetHref(link.href, baseUrl) });
2664
+ }
2665
+ return links.length > 0 ? links : void 0;
2666
+ }
2667
+ function stacRowToItem(row, baseUrl, opts = {}) {
2668
+ const { wkbParser, wkbColumn = "geom_wkb", geometryColumn = "geometry" } = opts;
2669
+ let geometry = row[geometryColumn];
2670
+ if (!geometry) {
2671
+ const wkb = row[wkbColumn];
2672
+ if (wkb && wkbParser) {
2673
+ const bytes = wkb instanceof Uint8Array ? wkb : toUint8Array(wkb);
2674
+ if (bytes) {
2675
+ try {
2676
+ geometry = wkbParser(bytes) ?? void 0;
2677
+ } catch {
2678
+ geometry = void 0;
2679
+ }
2680
+ }
2681
+ }
2682
+ }
2683
+ const bbox = flattenStacBbox(row.bbox) ?? void 0;
2684
+ const assets = normalizeAssetsField(row.assets, baseUrl);
2685
+ const links = normalizeLinksField(row.links, baseUrl);
2686
+ const properties = {};
2687
+ for (const [key, value] of Object.entries(row)) {
2688
+ if (value === null || value === void 0) continue;
2689
+ if (key.startsWith("proj:") || key.startsWith("raster:") || key.startsWith("eo:")) {
2690
+ properties[key] = value;
2691
+ }
2692
+ if (key === "datetime") {
2693
+ properties.datetime = value instanceof Date ? value.toISOString() : String(value);
2694
+ }
2695
+ if (key === "bands") {
2696
+ properties.bands = value;
2697
+ }
2698
+ }
2699
+ const item = {
2700
+ type: "Feature",
2701
+ stac_version: typeof row.stac_version === "string" ? row.stac_version : "1.0.0",
2702
+ id: typeof row.id === "string" ? row.id : String(row.id ?? ""),
2703
+ properties
2704
+ };
2705
+ if (typeof row.collection === "string") item.collection = row.collection;
2706
+ if (Array.isArray(row.stac_extensions)) {
2707
+ item.stac_extensions = row.stac_extensions;
2708
+ }
2709
+ if (bbox) item.bbox = bbox;
2710
+ if (geometry) item.geometry = geometry;
2711
+ if (assets) item.assets = assets;
2712
+ if (links) item.links = links;
2713
+ return item;
2714
+ }
2715
+ function toUint8Array(value) {
2716
+ if (value instanceof Uint8Array) return value;
2717
+ if (value instanceof ArrayBuffer) return new Uint8Array(value);
2718
+ if (ArrayBuffer.isView(value)) {
2719
+ const view = value;
2720
+ return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
2721
+ }
2722
+ if (Array.isArray(value)) return new Uint8Array(value);
2723
+ return null;
2724
+ }
2725
+
2521
2726
  // ../../src/lib/utils/storage-url.ts
2522
2727
  function buildSchemeMap() {
2523
2728
  const map = {};
@@ -2531,6 +2736,32 @@ function buildSchemeMap() {
2531
2736
  return map;
2532
2737
  }
2533
2738
  var SCHEME_MAP = buildSchemeMap();
2739
+ var AWS_VHOST_RE = /^(.+)\.s3[.-]([a-z0-9-]+)\.amazonaws\.com$/;
2740
+ var AWS_PATH_RE = /^s3[.-]([a-z0-9-]+)\.amazonaws\.com$/;
2741
+ var AWS_GLOBAL_HOST = "s3.amazonaws.com";
2742
+ var R2_RE = /^([a-z0-9]+)\.r2\.cloudflarestorage\.com$/;
2743
+ var GCS_GLOBAL_HOST = "storage.googleapis.com";
2744
+ var GCS_VHOST_RE = /^(.+)\.storage\.googleapis\.com$/;
2745
+ var DO_VHOST_RE = /^(.+)\.([a-z0-9-]+)\.digitaloceanspaces\.com$/;
2746
+ var DO_PATH_RE = /^([a-z0-9-]+)\.digitaloceanspaces\.com$/;
2747
+ var WASABI_RE = /^s3\.([a-z0-9-]+)\.wasabisys\.com$/;
2748
+ var B2_S3_RE = /^(.+)\.s3\.([a-z0-9-]+)\.backblazeb2\.com$/;
2749
+ var B2_NATIVE_RE = /^f[a-z0-9]+\.backblazeb2\.com$/;
2750
+ var OSS_RE = /^(.+)\.(oss-[a-z0-9-]+)\.aliyuncs\.com$/;
2751
+ var COS_RE = /^(.+)\.cos\.([a-z0-9-]+)\.myqcloud\.com$/;
2752
+ var YANDEX_HOST = "storage.yandexcloud.net";
2753
+ var CONTABO_RE = /^([a-z0-9]+)\.contabostorage\.com$/;
2754
+ var HETZNER_RE = /^([a-z0-9]+)\.your-objectstorage\.com$/;
2755
+ var LINODE_VHOST_RE = /^(.+)\.([a-z0-9-]+)\.linodeobjects\.com$/;
2756
+ var LINODE_PATH_RE = /^([a-z0-9-]+)\.linodeobjects\.com$/;
2757
+ var OVH_RE = /^s3\.([a-z0-9-]+)\.io\.cloud\.ovh\.(?:net|us)$/;
2758
+ var AZURE_BLOB_RE = /^([a-z0-9]+)\.blob\.core\.windows\.net$/;
2759
+ var STORJ_GATEWAY_RE = /^gateway\.(?:([a-z0-9]+)\.)?storjshare\.io$/;
2760
+ var STORJ_LINK_RE = /^link\.(?:([a-z0-9]+)\.)?storjshare\.io$/;
2761
+ function isMinioLikeHost(host) {
2762
+ return host.includes("minio") || host === "localhost" || host === "127.0.0.1" || host.startsWith("192.168.") || host.startsWith("10.");
2763
+ }
2764
+ var STAC_API_PATH_RE = /\/(collections|items|catalogs|search)(\/|\?|$)/i;
2534
2765
  function defaultResult(defaults) {
2535
2766
  return {
2536
2767
  bucket: "",
@@ -2571,7 +2802,7 @@ function parseStorageUrl(input, defaults = {}) {
2571
2802
  const url = new URL(trimmed);
2572
2803
  const host = url.hostname;
2573
2804
  const pathParts = url.pathname.replace(/^\//, "").split("/").filter(Boolean);
2574
- const awsVhost = host.match(/^(.+)\.s3[.-]([a-z0-9-]+)\.amazonaws\.com$/);
2805
+ const awsVhost = host.match(AWS_VHOST_RE);
2575
2806
  if (awsVhost) {
2576
2807
  return {
2577
2808
  bucket: awsVhost[1],
@@ -2581,7 +2812,7 @@ function parseStorageUrl(input, defaults = {}) {
2581
2812
  prefix: pathParts.join("/")
2582
2813
  };
2583
2814
  }
2584
- const awsPath = host.match(/^s3[.-]([a-z0-9-]+)\.amazonaws\.com$/);
2815
+ const awsPath = host.match(AWS_PATH_RE);
2585
2816
  if (awsPath && pathParts.length > 0) {
2586
2817
  return {
2587
2818
  bucket: pathParts[0],
@@ -2591,7 +2822,7 @@ function parseStorageUrl(input, defaults = {}) {
2591
2822
  prefix: pathParts.slice(1).join("/")
2592
2823
  };
2593
2824
  }
2594
- if (host === "s3.amazonaws.com" && pathParts.length > 0) {
2825
+ if (host === AWS_GLOBAL_HOST && pathParts.length > 0) {
2595
2826
  return {
2596
2827
  bucket: pathParts[0],
2597
2828
  region: defaults.region || "us-east-1",
@@ -2600,7 +2831,7 @@ function parseStorageUrl(input, defaults = {}) {
2600
2831
  prefix: pathParts.slice(1).join("/")
2601
2832
  };
2602
2833
  }
2603
- const r2Match = host.match(/^([a-z0-9]+)\.r2\.cloudflarestorage\.com$/);
2834
+ const r2Match = host.match(R2_RE);
2604
2835
  if (r2Match && pathParts.length > 0) {
2605
2836
  return {
2606
2837
  bucket: pathParts[0],
@@ -2610,7 +2841,7 @@ function parseStorageUrl(input, defaults = {}) {
2610
2841
  prefix: pathParts.slice(1).join("/")
2611
2842
  };
2612
2843
  }
2613
- if (host === "storage.googleapis.com" && pathParts.length > 0) {
2844
+ if (host === GCS_GLOBAL_HOST && pathParts.length > 0) {
2614
2845
  return {
2615
2846
  bucket: pathParts[0],
2616
2847
  region: defaults.region || "us",
@@ -2619,7 +2850,7 @@ function parseStorageUrl(input, defaults = {}) {
2619
2850
  prefix: pathParts.slice(1).join("/")
2620
2851
  };
2621
2852
  }
2622
- const gcsVhost = host.match(/^(.+)\.storage\.googleapis\.com$/);
2853
+ const gcsVhost = host.match(GCS_VHOST_RE);
2623
2854
  if (gcsVhost) {
2624
2855
  return {
2625
2856
  bucket: gcsVhost[1],
@@ -2629,7 +2860,7 @@ function parseStorageUrl(input, defaults = {}) {
2629
2860
  prefix: pathParts.join("/")
2630
2861
  };
2631
2862
  }
2632
- const doVhost = host.match(/^(.+)\.([a-z0-9-]+)\.digitaloceanspaces\.com$/);
2863
+ const doVhost = host.match(DO_VHOST_RE);
2633
2864
  if (doVhost) {
2634
2865
  return {
2635
2866
  bucket: doVhost[1],
@@ -2639,7 +2870,7 @@ function parseStorageUrl(input, defaults = {}) {
2639
2870
  prefix: pathParts.join("/")
2640
2871
  };
2641
2872
  }
2642
- const doPath = host.match(/^([a-z0-9-]+)\.digitaloceanspaces\.com$/);
2873
+ const doPath = host.match(DO_PATH_RE);
2643
2874
  if (doPath && pathParts.length > 0) {
2644
2875
  return {
2645
2876
  bucket: pathParts[0],
@@ -2649,7 +2880,7 @@ function parseStorageUrl(input, defaults = {}) {
2649
2880
  prefix: pathParts.slice(1).join("/")
2650
2881
  };
2651
2882
  }
2652
- const wasabiMatch = host.match(/^s3\.([a-z0-9-]+)\.wasabisys\.com$/);
2883
+ const wasabiMatch = host.match(WASABI_RE);
2653
2884
  if (wasabiMatch && pathParts.length > 0) {
2654
2885
  return {
2655
2886
  bucket: pathParts[0],
@@ -2659,7 +2890,7 @@ function parseStorageUrl(input, defaults = {}) {
2659
2890
  prefix: pathParts.slice(1).join("/")
2660
2891
  };
2661
2892
  }
2662
- const b2S3 = host.match(/^(.+)\.s3\.([a-z0-9-]+)\.backblazeb2\.com$/);
2893
+ const b2S3 = host.match(B2_S3_RE);
2663
2894
  if (b2S3) {
2664
2895
  return {
2665
2896
  bucket: b2S3[1],
@@ -2669,7 +2900,7 @@ function parseStorageUrl(input, defaults = {}) {
2669
2900
  prefix: pathParts.join("/")
2670
2901
  };
2671
2902
  }
2672
- const b2Native = host.match(/^f[a-z0-9]+\.backblazeb2\.com$/);
2903
+ const b2Native = host.match(B2_NATIVE_RE);
2673
2904
  if (b2Native && pathParts[0] === "file" && pathParts.length > 1) {
2674
2905
  return {
2675
2906
  bucket: pathParts[1],
@@ -2679,7 +2910,7 @@ function parseStorageUrl(input, defaults = {}) {
2679
2910
  prefix: pathParts.slice(2).join("/")
2680
2911
  };
2681
2912
  }
2682
- const ossMatch = host.match(/^(.+)\.(oss-[a-z0-9-]+)\.aliyuncs\.com$/);
2913
+ const ossMatch = host.match(OSS_RE);
2683
2914
  if (ossMatch) {
2684
2915
  return {
2685
2916
  bucket: ossMatch[1],
@@ -2689,7 +2920,7 @@ function parseStorageUrl(input, defaults = {}) {
2689
2920
  prefix: pathParts.join("/")
2690
2921
  };
2691
2922
  }
2692
- const cosMatch = host.match(/^(.+)\.cos\.([a-z0-9-]+)\.myqcloud\.com$/);
2923
+ const cosMatch = host.match(COS_RE);
2693
2924
  if (cosMatch) {
2694
2925
  return {
2695
2926
  bucket: cosMatch[1],
@@ -2699,7 +2930,7 @@ function parseStorageUrl(input, defaults = {}) {
2699
2930
  prefix: pathParts.join("/")
2700
2931
  };
2701
2932
  }
2702
- if (host === "storage.yandexcloud.net" && pathParts.length > 0) {
2933
+ if (host === YANDEX_HOST && pathParts.length > 0) {
2703
2934
  return {
2704
2935
  bucket: pathParts[0],
2705
2936
  region: defaults.region || "ru-central1",
@@ -2708,7 +2939,7 @@ function parseStorageUrl(input, defaults = {}) {
2708
2939
  prefix: pathParts.slice(1).join("/")
2709
2940
  };
2710
2941
  }
2711
- const contaboMatch = host.match(/^([a-z0-9]+)\.contabostorage\.com$/);
2942
+ const contaboMatch = host.match(CONTABO_RE);
2712
2943
  if (contaboMatch && pathParts.length > 0) {
2713
2944
  return {
2714
2945
  bucket: pathParts[0],
@@ -2718,7 +2949,7 @@ function parseStorageUrl(input, defaults = {}) {
2718
2949
  prefix: pathParts.slice(1).join("/")
2719
2950
  };
2720
2951
  }
2721
- const hetznerMatch = host.match(/^([a-z0-9]+)\.your-objectstorage\.com$/);
2952
+ const hetznerMatch = host.match(HETZNER_RE);
2722
2953
  if (hetznerMatch && pathParts.length > 0) {
2723
2954
  return {
2724
2955
  bucket: pathParts[0],
@@ -2728,7 +2959,7 @@ function parseStorageUrl(input, defaults = {}) {
2728
2959
  prefix: pathParts.slice(1).join("/")
2729
2960
  };
2730
2961
  }
2731
- const linodeVhost = host.match(/^(.+)\.([a-z0-9-]+)\.linodeobjects\.com$/);
2962
+ const linodeVhost = host.match(LINODE_VHOST_RE);
2732
2963
  if (linodeVhost) {
2733
2964
  return {
2734
2965
  bucket: linodeVhost[1],
@@ -2738,7 +2969,7 @@ function parseStorageUrl(input, defaults = {}) {
2738
2969
  prefix: pathParts.join("/")
2739
2970
  };
2740
2971
  }
2741
- const linodePath = host.match(/^([a-z0-9-]+)\.linodeobjects\.com$/);
2972
+ const linodePath = host.match(LINODE_PATH_RE);
2742
2973
  if (linodePath && pathParts.length > 0) {
2743
2974
  return {
2744
2975
  bucket: pathParts[0],
@@ -2748,7 +2979,7 @@ function parseStorageUrl(input, defaults = {}) {
2748
2979
  prefix: pathParts.slice(1).join("/")
2749
2980
  };
2750
2981
  }
2751
- const ovhMatch = host.match(/^s3\.([a-z0-9-]+)\.io\.cloud\.ovh\.(?:net|us)$/);
2982
+ const ovhMatch = host.match(OVH_RE);
2752
2983
  if (ovhMatch && pathParts.length > 0) {
2753
2984
  return {
2754
2985
  bucket: pathParts[0],
@@ -2758,8 +2989,7 @@ function parseStorageUrl(input, defaults = {}) {
2758
2989
  prefix: pathParts.slice(1).join("/")
2759
2990
  };
2760
2991
  }
2761
- const isMinioLike = host.includes("minio") || host === "localhost" || host === "127.0.0.1" || host.startsWith("192.168.") || host.startsWith("10.");
2762
- if (isMinioLike && pathParts.length > 0) {
2992
+ if (isMinioLikeHost(host) && pathParts.length > 0) {
2763
2993
  return {
2764
2994
  bucket: pathParts[0],
2765
2995
  region: defaults.region || "us-east-1",
@@ -2768,7 +2998,7 @@ function parseStorageUrl(input, defaults = {}) {
2768
2998
  prefix: pathParts.slice(1).join("/")
2769
2999
  };
2770
3000
  }
2771
- const azureBlob = host.match(/^([a-z0-9]+)\.blob\.core\.windows\.net$/);
3001
+ const azureBlob = host.match(AZURE_BLOB_RE);
2772
3002
  if (azureBlob && pathParts.length > 0) {
2773
3003
  return {
2774
3004
  bucket: pathParts[0],
@@ -2778,7 +3008,7 @@ function parseStorageUrl(input, defaults = {}) {
2778
3008
  prefix: pathParts.slice(1).join("/")
2779
3009
  };
2780
3010
  }
2781
- const storjGateway = host.match(/^gateway\.(?:([a-z0-9]+)\.)?storjshare\.io$/);
3011
+ const storjGateway = host.match(STORJ_GATEWAY_RE);
2782
3012
  if (storjGateway && pathParts.length > 0) {
2783
3013
  return {
2784
3014
  bucket: pathParts[0],
@@ -2788,7 +3018,7 @@ function parseStorageUrl(input, defaults = {}) {
2788
3018
  prefix: pathParts.slice(1).join("/")
2789
3019
  };
2790
3020
  }
2791
- const storjLink = host.match(/^link\.(?:([a-z0-9]+)\.)?storjshare\.io$/);
3021
+ const storjLink = host.match(STORJ_LINK_RE);
2792
3022
  if (storjLink && pathParts.length >= 3 && (pathParts[0] === "raw" || pathParts[0] === "s")) {
2793
3023
  return {
2794
3024
  bucket: pathParts[2],
@@ -2798,6 +3028,12 @@ function parseStorageUrl(input, defaults = {}) {
2798
3028
  prefix: pathParts.slice(3).join("/")
2799
3029
  };
2800
3030
  }
3031
+ if (STAC_API_PATH_RE.test(url.pathname)) {
3032
+ return {
3033
+ ...defaultResult(defaults),
3034
+ endpoint: `${url.protocol}//${url.host}`
3035
+ };
3036
+ }
2801
3037
  if (pathParts.length > 0) {
2802
3038
  const endpoint = `${url.protocol}//${url.host}`;
2803
3039
  return {
@@ -3000,7 +3236,10 @@ var GEO_TYPE_KEYWORDS = [
3000
3236
  "geometrycollection",
3001
3237
  "sdo_geometry"
3002
3238
  ];
3003
- var GEO_NAME_HINTS = ["geom", "geometry", "geo_", "_geo", "wkb", "wkt", "shape", "spatial"];
3239
+ var GEO_NAME_HINTS = ["geom", "geometry", "wkb", "wkt", "shape", "spatial", "geo"];
3240
+ function tokenizeColumnName(name) {
3241
+ return name.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/([a-z])([0-9])/g, "$1_$2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
3242
+ }
3004
3243
  var GEOJSON_TYPES = [
3005
3244
  "Point",
3006
3245
  "LineString",
@@ -3030,14 +3269,14 @@ function findGeoColumn(schema) {
3030
3269
  if (GEO_NAMES.includes(f.name.toLowerCase())) return f.name;
3031
3270
  }
3032
3271
  for (const f of schema) {
3033
- const n = f.name.toLowerCase();
3272
+ const tokens = tokenizeColumnName(f.name);
3034
3273
  const t = f.type.toLowerCase();
3035
3274
  const isBinary = t.includes("blob") || t.includes("binary") || t.includes("bytea");
3036
- if (isBinary && GEO_NAME_HINTS.some((hint) => n.includes(hint))) return f.name;
3275
+ if (isBinary && tokens.some((tok) => GEO_NAME_HINTS.includes(tok))) return f.name;
3037
3276
  }
3038
3277
  for (const f of schema) {
3039
- const n = f.name.toLowerCase();
3040
- if (GEO_NAME_HINTS.some((hint) => n.includes(hint))) return f.name;
3278
+ const tokens = tokenizeColumnName(f.name);
3279
+ if (tokens.some((tok) => GEO_NAME_HINTS.includes(tok))) return f.name;
3041
3280
  }
3042
3281
  return null;
3043
3282
  }
@@ -3104,6 +3343,7 @@ exports.PROVIDER_IDS = PROVIDER_IDS;
3104
3343
  exports.QueryCancelledError = QueryCancelledError;
3105
3344
  exports.SF_LABELS = SF_LABELS;
3106
3345
  exports.SQL_PREVIEW_LENGTH = SQL_PREVIEW_LENGTH;
3346
+ exports.STAC_GEOPARQUET_REQUIRED_COLUMNS = STAC_GEOPARQUET_REQUIRED_COLUMNS;
3107
3347
  exports.STORAGE_KEYS = STORAGE_KEYS;
3108
3348
  exports.UrlAdapter = UrlAdapter;
3109
3349
  exports.VIEWER_DIR_EXTENSIONS = VIEWER_DIR_EXTENSIONS;
@@ -3117,15 +3357,19 @@ exports.clampBounds = clampBounds;
3117
3357
  exports.classifyType = classifyType;
3118
3358
  exports.describeParseResult = describeParseResult;
3119
3359
  exports.escapeCsvField = escapeCsvField;
3360
+ exports.exportToCsv = exportToCsv;
3361
+ exports.exportToJson = exportToJson;
3120
3362
  exports.extractBounds = extractBounds;
3121
3363
  exports.extractEpsgFromGeoMeta = extractEpsgFromGeoMeta;
3122
3364
  exports.extractGeometryTypes = extractGeometryTypes;
3123
3365
  exports.findGeoColumn = findGeoColumn;
3124
3366
  exports.findGeoColumnFromRows = findGeoColumnFromRows;
3367
+ exports.flattenStacBbox = flattenStacBbox;
3125
3368
  exports.formatDate = formatDate;
3126
3369
  exports.formatFileSize = formatFileSize;
3127
3370
  exports.formatValue = formatValue;
3128
3371
  exports.generateHexDump = generateHexDump;
3372
+ exports.getAccessMode = getAccessMode;
3129
3373
  exports.getDuckDbReadFn = getDuckDbReadFn;
3130
3374
  exports.getFileExtension = getFileExtension;
3131
3375
  exports.getFileTypeInfo = getFileTypeInfo;
@@ -3137,7 +3381,9 @@ exports.handleLoadError = handleLoadError;
3137
3381
  exports.interpolateTemplates = interpolateTemplates;
3138
3382
  exports.isCloudNativeFormat = isCloudNativeFormat;
3139
3383
  exports.isGcsProvider = isGcsProvider;
3384
+ exports.isPubliclyStreamable = isPubliclyStreamable;
3140
3385
  exports.isQueryable = isQueryable;
3386
+ exports.isStacGeoparquetSchema = isStacGeoparquetSchema;
3141
3387
  exports.jsonReplacerBigInt = jsonReplacerBigInt;
3142
3388
  exports.loadFromStorage = loadFromStorage;
3143
3389
  exports.looksLikeUrl = looksLikeUrl;
@@ -3147,13 +3393,17 @@ exports.parseMarkdownDocument = parseMarkdownDocument;
3147
3393
  exports.parseStorageUrl = parseStorageUrl;
3148
3394
  exports.parseWKB = parseWKB;
3149
3395
  exports.persistToStorage = persistToStorage;
3396
+ exports.pickStacPrimaryAsset = pickStacPrimaryAsset;
3150
3397
  exports.readParquetMetadata = readParquetMetadata;
3151
3398
  exports.resolveCloudUrl = resolveCloudUrl;
3399
+ exports.resolveProviderEndpoint = resolveProviderEndpoint;
3400
+ exports.resolveStacAssetHref = resolveStacAssetHref;
3152
3401
  exports.safeClamp = safeClamp;
3153
3402
  exports.safeDecodeURIComponent = safeDecodeURIComponent;
3154
3403
  exports.serializeToCsv = serializeToCsv;
3155
3404
  exports.serializeToJson = serializeToJson;
3156
3405
  exports.sortFileEntries = sortFileEntries;
3406
+ exports.stacRowToItem = stacRowToItem;
3157
3407
  exports.toBinary = toBinary;
3158
3408
  exports.toggleSortField = toggleSortField;
3159
3409
  exports.typeBadgeClass = typeBadgeClass;