@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/dist/index.js CHANGED
@@ -1355,6 +1355,11 @@ function buildEndpointFromTemplate(id, region) {
1355
1355
  if (!def?.endpointTemplate) return "";
1356
1356
  return def.endpointTemplate.replace("{region}", region);
1357
1357
  }
1358
+ function resolveProviderEndpoint(provider, region) {
1359
+ const def = PROVIDERS[provider];
1360
+ if (!def?.endpointTemplate) return "";
1361
+ return buildEndpointFromTemplate(provider, region || def.defaultRegion);
1362
+ }
1358
1363
  function buildProviderBaseUrl(provider, endpoint, bucket, region) {
1359
1364
  if (endpoint) {
1360
1365
  return `${endpoint.replace(/\/$/, "")}/${bucket}`;
@@ -1369,6 +1374,15 @@ function buildProviderBaseUrl(provider, endpoint, bucket, region) {
1369
1374
  function isGcsProvider(provider, endpoint) {
1370
1375
  return provider === "gcs" || !!endpoint && /storage\.googleapis\.com/i.test(endpoint);
1371
1376
  }
1377
+ function getAccessMode(conn) {
1378
+ if (conn.provider === "azure") return "sas-https";
1379
+ if (conn.anonymous) return "public-https";
1380
+ return "signed-s3";
1381
+ }
1382
+ function isPubliclyStreamable(conn) {
1383
+ const mode = getAccessMode(conn);
1384
+ return mode === "public-https" || mode === "sas-https";
1385
+ }
1372
1386
 
1373
1387
  // ../../src/lib/storage/url-adapter.ts
1374
1388
  var UrlAdapter = class {
@@ -1638,6 +1652,17 @@ function formatValue(value) {
1638
1652
  }
1639
1653
 
1640
1654
  // ../../src/lib/utils/export.ts
1655
+ function triggerDownload(content, filename, mimeType) {
1656
+ const blob = new Blob([content], { type: mimeType });
1657
+ const url = URL.createObjectURL(blob);
1658
+ const a = document.createElement("a");
1659
+ a.href = url;
1660
+ a.download = filename;
1661
+ document.body.appendChild(a);
1662
+ a.click();
1663
+ document.body.removeChild(a);
1664
+ URL.revokeObjectURL(url);
1665
+ }
1641
1666
  function formatCellValue(value) {
1642
1667
  if (value === null || value === void 0) return "";
1643
1668
  if (value instanceof Date) return value.toISOString();
@@ -1672,6 +1697,20 @@ function serializeToJson(columns, rows) {
1672
1697
  });
1673
1698
  return JSON.stringify(data, jsonReplacerBigInt, 2);
1674
1699
  }
1700
+ function exportToCsv(columns, rows, filename) {
1701
+ triggerDownload(
1702
+ serializeToCsv(columns, rows),
1703
+ filename.endsWith(".csv") ? filename : `${filename}.csv`,
1704
+ "text/csv;charset=utf-8;"
1705
+ );
1706
+ }
1707
+ function exportToJson(columns, rows, filename) {
1708
+ triggerDownload(
1709
+ serializeToJson(columns, rows),
1710
+ filename.endsWith(".json") ? filename : `${filename}.json`,
1711
+ "application/json"
1712
+ );
1713
+ }
1675
1714
 
1676
1715
  // ../../src/lib/utils/file-sort.ts
1677
1716
  function sortFileEntries(entries, config) {
@@ -2436,6 +2475,15 @@ async function readParquetMetadata(url) {
2436
2475
  name: col.name,
2437
2476
  type: mapParquetType(col)
2438
2477
  }));
2478
+ const topLevelColumns = [];
2479
+ const root = metadata.schema[0];
2480
+ const rootChildren = root?.num_children ?? 0;
2481
+ let cursor = 1;
2482
+ for (let i = 0; i < rootChildren && cursor < metadata.schema.length; i++) {
2483
+ const el = metadata.schema[cursor];
2484
+ if (el?.name) topLevelColumns.push(el.name);
2485
+ cursor += countSubtree(metadata.schema, cursor);
2486
+ }
2439
2487
  let geo = null;
2440
2488
  let legacyGeoParquet = false;
2441
2489
  const geoKv = metadata.key_value_metadata?.find((kv) => kv.key === "geo");
@@ -2477,7 +2525,26 @@ async function readParquetMetadata(url) {
2477
2525
  compression = [...codecs].join(", ");
2478
2526
  }
2479
2527
  }
2480
- return { rowCount, schema, geo, legacyGeoParquet, createdBy, numRowGroups, compression };
2528
+ return {
2529
+ rowCount,
2530
+ schema,
2531
+ topLevelColumns,
2532
+ geo,
2533
+ legacyGeoParquet,
2534
+ createdBy,
2535
+ numRowGroups,
2536
+ compression
2537
+ };
2538
+ }
2539
+ function countSubtree(schema, start) {
2540
+ const el = schema[start];
2541
+ if (!el) return 0;
2542
+ const n = el.num_children ?? 0;
2543
+ let cursor = start + 1;
2544
+ for (let i = 0; i < n; i++) {
2545
+ cursor += countSubtree(schema, cursor);
2546
+ }
2547
+ return cursor - start;
2481
2548
  }
2482
2549
  function extractEpsgFromGeoMeta(geo) {
2483
2550
  const primaryCol = geo.columns[geo.primaryColumn];
@@ -2516,6 +2583,144 @@ function extractBounds(geo) {
2516
2583
  return [primaryCol.bbox[0], primaryCol.bbox[1], primaryCol.bbox[2], primaryCol.bbox[3]];
2517
2584
  }
2518
2585
 
2586
+ // ../../src/lib/utils/stac-geoparquet.ts
2587
+ var STAC_GEOPARQUET_REQUIRED_COLUMNS = [
2588
+ "stac_version",
2589
+ "type",
2590
+ "geometry",
2591
+ "assets"
2592
+ ];
2593
+ function isStacGeoparquetSchema(schema) {
2594
+ if (!Array.isArray(schema) || schema.length === 0) return false;
2595
+ const names = new Set(schema.map((c) => c.name));
2596
+ return STAC_GEOPARQUET_REQUIRED_COLUMNS.every((c) => names.has(c));
2597
+ }
2598
+ function flattenStacBbox(bbox) {
2599
+ if (!bbox) return null;
2600
+ if (Array.isArray(bbox)) {
2601
+ if (bbox.length < 4) return null;
2602
+ const [minX, minY, maxX, maxY] = bbox;
2603
+ if (![minX, minY, maxX, maxY].every((v) => Number.isFinite(v))) return null;
2604
+ return [minX, minY, maxX, maxY];
2605
+ }
2606
+ if (typeof bbox === "object") {
2607
+ const { xmin, ymin, xmax, ymax } = bbox;
2608
+ if (![xmin, ymin, xmax, ymax].every((v) => Number.isFinite(v))) return null;
2609
+ return [xmin, ymin, xmax, ymax];
2610
+ }
2611
+ return null;
2612
+ }
2613
+ function resolveStacAssetHref(href, baseUrl) {
2614
+ if (!href) return href;
2615
+ if (/^[a-z][a-z0-9+\-.]*:\/\//i.test(href)) return href;
2616
+ try {
2617
+ return new URL(href, baseUrl).toString();
2618
+ } catch {
2619
+ return href;
2620
+ }
2621
+ }
2622
+ function pickStacPrimaryAsset(assets, preferredKeys) {
2623
+ if (!assets || typeof assets !== "object") return null;
2624
+ const entries = Object.entries(assets).filter(
2625
+ ([, a]) => a && typeof a === "object" && typeof a.href === "string"
2626
+ );
2627
+ if (entries.length === 0) return null;
2628
+ if (preferredKeys) {
2629
+ for (const key of preferredKeys) {
2630
+ const match = entries.find(([k]) => k === key);
2631
+ if (match) return { key: match[0], asset: match[1] };
2632
+ }
2633
+ }
2634
+ const data = entries.find(([k]) => k === "data");
2635
+ if (data) return { key: data[0], asset: data[1] };
2636
+ const byRole = entries.find(([, a]) => Array.isArray(a.roles) && a.roles.includes("data"));
2637
+ if (byRole) return { key: byRole[0], asset: byRole[1] };
2638
+ return { key: entries[0][0], asset: entries[0][1] };
2639
+ }
2640
+ function normalizeAssetsField(value, baseUrl) {
2641
+ if (!value || typeof value !== "object") return void 0;
2642
+ const out = {};
2643
+ for (const [key, raw] of Object.entries(value)) {
2644
+ if (!raw || typeof raw !== "object") continue;
2645
+ const asset = raw;
2646
+ if (typeof asset.href !== "string" || !asset.href) continue;
2647
+ out[key] = {
2648
+ ...asset,
2649
+ href: resolveStacAssetHref(asset.href, baseUrl)
2650
+ };
2651
+ }
2652
+ return Object.keys(out).length > 0 ? out : void 0;
2653
+ }
2654
+ function normalizeLinksField(value, baseUrl) {
2655
+ if (!Array.isArray(value)) return void 0;
2656
+ const links = [];
2657
+ for (const raw of value) {
2658
+ if (!raw || typeof raw !== "object") continue;
2659
+ const link = raw;
2660
+ if (typeof link.href !== "string" || typeof link.rel !== "string") continue;
2661
+ links.push({ ...link, href: resolveStacAssetHref(link.href, baseUrl) });
2662
+ }
2663
+ return links.length > 0 ? links : void 0;
2664
+ }
2665
+ function stacRowToItem(row, baseUrl, opts = {}) {
2666
+ const { wkbParser, wkbColumn = "geom_wkb", geometryColumn = "geometry" } = opts;
2667
+ let geometry = row[geometryColumn];
2668
+ if (!geometry) {
2669
+ const wkb = row[wkbColumn];
2670
+ if (wkb && wkbParser) {
2671
+ const bytes = wkb instanceof Uint8Array ? wkb : toUint8Array(wkb);
2672
+ if (bytes) {
2673
+ try {
2674
+ geometry = wkbParser(bytes) ?? void 0;
2675
+ } catch {
2676
+ geometry = void 0;
2677
+ }
2678
+ }
2679
+ }
2680
+ }
2681
+ const bbox = flattenStacBbox(row.bbox) ?? void 0;
2682
+ const assets = normalizeAssetsField(row.assets, baseUrl);
2683
+ const links = normalizeLinksField(row.links, baseUrl);
2684
+ const properties = {};
2685
+ for (const [key, value] of Object.entries(row)) {
2686
+ if (value === null || value === void 0) continue;
2687
+ if (key.startsWith("proj:") || key.startsWith("raster:") || key.startsWith("eo:")) {
2688
+ properties[key] = value;
2689
+ }
2690
+ if (key === "datetime") {
2691
+ properties.datetime = value instanceof Date ? value.toISOString() : String(value);
2692
+ }
2693
+ if (key === "bands") {
2694
+ properties.bands = value;
2695
+ }
2696
+ }
2697
+ const item = {
2698
+ type: "Feature",
2699
+ stac_version: typeof row.stac_version === "string" ? row.stac_version : "1.0.0",
2700
+ id: typeof row.id === "string" ? row.id : String(row.id ?? ""),
2701
+ properties
2702
+ };
2703
+ if (typeof row.collection === "string") item.collection = row.collection;
2704
+ if (Array.isArray(row.stac_extensions)) {
2705
+ item.stac_extensions = row.stac_extensions;
2706
+ }
2707
+ if (bbox) item.bbox = bbox;
2708
+ if (geometry) item.geometry = geometry;
2709
+ if (assets) item.assets = assets;
2710
+ if (links) item.links = links;
2711
+ return item;
2712
+ }
2713
+ function toUint8Array(value) {
2714
+ if (value instanceof Uint8Array) return value;
2715
+ if (value instanceof ArrayBuffer) return new Uint8Array(value);
2716
+ if (ArrayBuffer.isView(value)) {
2717
+ const view = value;
2718
+ return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
2719
+ }
2720
+ if (Array.isArray(value)) return new Uint8Array(value);
2721
+ return null;
2722
+ }
2723
+
2519
2724
  // ../../src/lib/utils/storage-url.ts
2520
2725
  function buildSchemeMap() {
2521
2726
  const map = {};
@@ -2529,6 +2734,32 @@ function buildSchemeMap() {
2529
2734
  return map;
2530
2735
  }
2531
2736
  var SCHEME_MAP = buildSchemeMap();
2737
+ var AWS_VHOST_RE = /^(.+)\.s3[.-]([a-z0-9-]+)\.amazonaws\.com$/;
2738
+ var AWS_PATH_RE = /^s3[.-]([a-z0-9-]+)\.amazonaws\.com$/;
2739
+ var AWS_GLOBAL_HOST = "s3.amazonaws.com";
2740
+ var R2_RE = /^([a-z0-9]+)\.r2\.cloudflarestorage\.com$/;
2741
+ var GCS_GLOBAL_HOST = "storage.googleapis.com";
2742
+ var GCS_VHOST_RE = /^(.+)\.storage\.googleapis\.com$/;
2743
+ var DO_VHOST_RE = /^(.+)\.([a-z0-9-]+)\.digitaloceanspaces\.com$/;
2744
+ var DO_PATH_RE = /^([a-z0-9-]+)\.digitaloceanspaces\.com$/;
2745
+ var WASABI_RE = /^s3\.([a-z0-9-]+)\.wasabisys\.com$/;
2746
+ var B2_S3_RE = /^(.+)\.s3\.([a-z0-9-]+)\.backblazeb2\.com$/;
2747
+ var B2_NATIVE_RE = /^f[a-z0-9]+\.backblazeb2\.com$/;
2748
+ var OSS_RE = /^(.+)\.(oss-[a-z0-9-]+)\.aliyuncs\.com$/;
2749
+ var COS_RE = /^(.+)\.cos\.([a-z0-9-]+)\.myqcloud\.com$/;
2750
+ var YANDEX_HOST = "storage.yandexcloud.net";
2751
+ var CONTABO_RE = /^([a-z0-9]+)\.contabostorage\.com$/;
2752
+ var HETZNER_RE = /^([a-z0-9]+)\.your-objectstorage\.com$/;
2753
+ var LINODE_VHOST_RE = /^(.+)\.([a-z0-9-]+)\.linodeobjects\.com$/;
2754
+ var LINODE_PATH_RE = /^([a-z0-9-]+)\.linodeobjects\.com$/;
2755
+ var OVH_RE = /^s3\.([a-z0-9-]+)\.io\.cloud\.ovh\.(?:net|us)$/;
2756
+ var AZURE_BLOB_RE = /^([a-z0-9]+)\.blob\.core\.windows\.net$/;
2757
+ var STORJ_GATEWAY_RE = /^gateway\.(?:([a-z0-9]+)\.)?storjshare\.io$/;
2758
+ var STORJ_LINK_RE = /^link\.(?:([a-z0-9]+)\.)?storjshare\.io$/;
2759
+ function isMinioLikeHost(host) {
2760
+ return host.includes("minio") || host === "localhost" || host === "127.0.0.1" || host.startsWith("192.168.") || host.startsWith("10.");
2761
+ }
2762
+ var STAC_API_PATH_RE = /\/(collections|items|catalogs|search)(\/|\?|$)/i;
2532
2763
  function defaultResult(defaults) {
2533
2764
  return {
2534
2765
  bucket: "",
@@ -2569,7 +2800,7 @@ function parseStorageUrl(input, defaults = {}) {
2569
2800
  const url = new URL(trimmed);
2570
2801
  const host = url.hostname;
2571
2802
  const pathParts = url.pathname.replace(/^\//, "").split("/").filter(Boolean);
2572
- const awsVhost = host.match(/^(.+)\.s3[.-]([a-z0-9-]+)\.amazonaws\.com$/);
2803
+ const awsVhost = host.match(AWS_VHOST_RE);
2573
2804
  if (awsVhost) {
2574
2805
  return {
2575
2806
  bucket: awsVhost[1],
@@ -2579,7 +2810,7 @@ function parseStorageUrl(input, defaults = {}) {
2579
2810
  prefix: pathParts.join("/")
2580
2811
  };
2581
2812
  }
2582
- const awsPath = host.match(/^s3[.-]([a-z0-9-]+)\.amazonaws\.com$/);
2813
+ const awsPath = host.match(AWS_PATH_RE);
2583
2814
  if (awsPath && pathParts.length > 0) {
2584
2815
  return {
2585
2816
  bucket: pathParts[0],
@@ -2589,7 +2820,7 @@ function parseStorageUrl(input, defaults = {}) {
2589
2820
  prefix: pathParts.slice(1).join("/")
2590
2821
  };
2591
2822
  }
2592
- if (host === "s3.amazonaws.com" && pathParts.length > 0) {
2823
+ if (host === AWS_GLOBAL_HOST && pathParts.length > 0) {
2593
2824
  return {
2594
2825
  bucket: pathParts[0],
2595
2826
  region: defaults.region || "us-east-1",
@@ -2598,7 +2829,7 @@ function parseStorageUrl(input, defaults = {}) {
2598
2829
  prefix: pathParts.slice(1).join("/")
2599
2830
  };
2600
2831
  }
2601
- const r2Match = host.match(/^([a-z0-9]+)\.r2\.cloudflarestorage\.com$/);
2832
+ const r2Match = host.match(R2_RE);
2602
2833
  if (r2Match && pathParts.length > 0) {
2603
2834
  return {
2604
2835
  bucket: pathParts[0],
@@ -2608,7 +2839,7 @@ function parseStorageUrl(input, defaults = {}) {
2608
2839
  prefix: pathParts.slice(1).join("/")
2609
2840
  };
2610
2841
  }
2611
- if (host === "storage.googleapis.com" && pathParts.length > 0) {
2842
+ if (host === GCS_GLOBAL_HOST && pathParts.length > 0) {
2612
2843
  return {
2613
2844
  bucket: pathParts[0],
2614
2845
  region: defaults.region || "us",
@@ -2617,7 +2848,7 @@ function parseStorageUrl(input, defaults = {}) {
2617
2848
  prefix: pathParts.slice(1).join("/")
2618
2849
  };
2619
2850
  }
2620
- const gcsVhost = host.match(/^(.+)\.storage\.googleapis\.com$/);
2851
+ const gcsVhost = host.match(GCS_VHOST_RE);
2621
2852
  if (gcsVhost) {
2622
2853
  return {
2623
2854
  bucket: gcsVhost[1],
@@ -2627,7 +2858,7 @@ function parseStorageUrl(input, defaults = {}) {
2627
2858
  prefix: pathParts.join("/")
2628
2859
  };
2629
2860
  }
2630
- const doVhost = host.match(/^(.+)\.([a-z0-9-]+)\.digitaloceanspaces\.com$/);
2861
+ const doVhost = host.match(DO_VHOST_RE);
2631
2862
  if (doVhost) {
2632
2863
  return {
2633
2864
  bucket: doVhost[1],
@@ -2637,7 +2868,7 @@ function parseStorageUrl(input, defaults = {}) {
2637
2868
  prefix: pathParts.join("/")
2638
2869
  };
2639
2870
  }
2640
- const doPath = host.match(/^([a-z0-9-]+)\.digitaloceanspaces\.com$/);
2871
+ const doPath = host.match(DO_PATH_RE);
2641
2872
  if (doPath && pathParts.length > 0) {
2642
2873
  return {
2643
2874
  bucket: pathParts[0],
@@ -2647,7 +2878,7 @@ function parseStorageUrl(input, defaults = {}) {
2647
2878
  prefix: pathParts.slice(1).join("/")
2648
2879
  };
2649
2880
  }
2650
- const wasabiMatch = host.match(/^s3\.([a-z0-9-]+)\.wasabisys\.com$/);
2881
+ const wasabiMatch = host.match(WASABI_RE);
2651
2882
  if (wasabiMatch && pathParts.length > 0) {
2652
2883
  return {
2653
2884
  bucket: pathParts[0],
@@ -2657,7 +2888,7 @@ function parseStorageUrl(input, defaults = {}) {
2657
2888
  prefix: pathParts.slice(1).join("/")
2658
2889
  };
2659
2890
  }
2660
- const b2S3 = host.match(/^(.+)\.s3\.([a-z0-9-]+)\.backblazeb2\.com$/);
2891
+ const b2S3 = host.match(B2_S3_RE);
2661
2892
  if (b2S3) {
2662
2893
  return {
2663
2894
  bucket: b2S3[1],
@@ -2667,7 +2898,7 @@ function parseStorageUrl(input, defaults = {}) {
2667
2898
  prefix: pathParts.join("/")
2668
2899
  };
2669
2900
  }
2670
- const b2Native = host.match(/^f[a-z0-9]+\.backblazeb2\.com$/);
2901
+ const b2Native = host.match(B2_NATIVE_RE);
2671
2902
  if (b2Native && pathParts[0] === "file" && pathParts.length > 1) {
2672
2903
  return {
2673
2904
  bucket: pathParts[1],
@@ -2677,7 +2908,7 @@ function parseStorageUrl(input, defaults = {}) {
2677
2908
  prefix: pathParts.slice(2).join("/")
2678
2909
  };
2679
2910
  }
2680
- const ossMatch = host.match(/^(.+)\.(oss-[a-z0-9-]+)\.aliyuncs\.com$/);
2911
+ const ossMatch = host.match(OSS_RE);
2681
2912
  if (ossMatch) {
2682
2913
  return {
2683
2914
  bucket: ossMatch[1],
@@ -2687,7 +2918,7 @@ function parseStorageUrl(input, defaults = {}) {
2687
2918
  prefix: pathParts.join("/")
2688
2919
  };
2689
2920
  }
2690
- const cosMatch = host.match(/^(.+)\.cos\.([a-z0-9-]+)\.myqcloud\.com$/);
2921
+ const cosMatch = host.match(COS_RE);
2691
2922
  if (cosMatch) {
2692
2923
  return {
2693
2924
  bucket: cosMatch[1],
@@ -2697,7 +2928,7 @@ function parseStorageUrl(input, defaults = {}) {
2697
2928
  prefix: pathParts.join("/")
2698
2929
  };
2699
2930
  }
2700
- if (host === "storage.yandexcloud.net" && pathParts.length > 0) {
2931
+ if (host === YANDEX_HOST && pathParts.length > 0) {
2701
2932
  return {
2702
2933
  bucket: pathParts[0],
2703
2934
  region: defaults.region || "ru-central1",
@@ -2706,7 +2937,7 @@ function parseStorageUrl(input, defaults = {}) {
2706
2937
  prefix: pathParts.slice(1).join("/")
2707
2938
  };
2708
2939
  }
2709
- const contaboMatch = host.match(/^([a-z0-9]+)\.contabostorage\.com$/);
2940
+ const contaboMatch = host.match(CONTABO_RE);
2710
2941
  if (contaboMatch && pathParts.length > 0) {
2711
2942
  return {
2712
2943
  bucket: pathParts[0],
@@ -2716,7 +2947,7 @@ function parseStorageUrl(input, defaults = {}) {
2716
2947
  prefix: pathParts.slice(1).join("/")
2717
2948
  };
2718
2949
  }
2719
- const hetznerMatch = host.match(/^([a-z0-9]+)\.your-objectstorage\.com$/);
2950
+ const hetznerMatch = host.match(HETZNER_RE);
2720
2951
  if (hetznerMatch && pathParts.length > 0) {
2721
2952
  return {
2722
2953
  bucket: pathParts[0],
@@ -2726,7 +2957,7 @@ function parseStorageUrl(input, defaults = {}) {
2726
2957
  prefix: pathParts.slice(1).join("/")
2727
2958
  };
2728
2959
  }
2729
- const linodeVhost = host.match(/^(.+)\.([a-z0-9-]+)\.linodeobjects\.com$/);
2960
+ const linodeVhost = host.match(LINODE_VHOST_RE);
2730
2961
  if (linodeVhost) {
2731
2962
  return {
2732
2963
  bucket: linodeVhost[1],
@@ -2736,7 +2967,7 @@ function parseStorageUrl(input, defaults = {}) {
2736
2967
  prefix: pathParts.join("/")
2737
2968
  };
2738
2969
  }
2739
- const linodePath = host.match(/^([a-z0-9-]+)\.linodeobjects\.com$/);
2970
+ const linodePath = host.match(LINODE_PATH_RE);
2740
2971
  if (linodePath && pathParts.length > 0) {
2741
2972
  return {
2742
2973
  bucket: pathParts[0],
@@ -2746,7 +2977,7 @@ function parseStorageUrl(input, defaults = {}) {
2746
2977
  prefix: pathParts.slice(1).join("/")
2747
2978
  };
2748
2979
  }
2749
- const ovhMatch = host.match(/^s3\.([a-z0-9-]+)\.io\.cloud\.ovh\.(?:net|us)$/);
2980
+ const ovhMatch = host.match(OVH_RE);
2750
2981
  if (ovhMatch && pathParts.length > 0) {
2751
2982
  return {
2752
2983
  bucket: pathParts[0],
@@ -2756,8 +2987,7 @@ function parseStorageUrl(input, defaults = {}) {
2756
2987
  prefix: pathParts.slice(1).join("/")
2757
2988
  };
2758
2989
  }
2759
- const isMinioLike = host.includes("minio") || host === "localhost" || host === "127.0.0.1" || host.startsWith("192.168.") || host.startsWith("10.");
2760
- if (isMinioLike && pathParts.length > 0) {
2990
+ if (isMinioLikeHost(host) && pathParts.length > 0) {
2761
2991
  return {
2762
2992
  bucket: pathParts[0],
2763
2993
  region: defaults.region || "us-east-1",
@@ -2766,7 +2996,7 @@ function parseStorageUrl(input, defaults = {}) {
2766
2996
  prefix: pathParts.slice(1).join("/")
2767
2997
  };
2768
2998
  }
2769
- const azureBlob = host.match(/^([a-z0-9]+)\.blob\.core\.windows\.net$/);
2999
+ const azureBlob = host.match(AZURE_BLOB_RE);
2770
3000
  if (azureBlob && pathParts.length > 0) {
2771
3001
  return {
2772
3002
  bucket: pathParts[0],
@@ -2776,7 +3006,7 @@ function parseStorageUrl(input, defaults = {}) {
2776
3006
  prefix: pathParts.slice(1).join("/")
2777
3007
  };
2778
3008
  }
2779
- const storjGateway = host.match(/^gateway\.(?:([a-z0-9]+)\.)?storjshare\.io$/);
3009
+ const storjGateway = host.match(STORJ_GATEWAY_RE);
2780
3010
  if (storjGateway && pathParts.length > 0) {
2781
3011
  return {
2782
3012
  bucket: pathParts[0],
@@ -2786,7 +3016,7 @@ function parseStorageUrl(input, defaults = {}) {
2786
3016
  prefix: pathParts.slice(1).join("/")
2787
3017
  };
2788
3018
  }
2789
- const storjLink = host.match(/^link\.(?:([a-z0-9]+)\.)?storjshare\.io$/);
3019
+ const storjLink = host.match(STORJ_LINK_RE);
2790
3020
  if (storjLink && pathParts.length >= 3 && (pathParts[0] === "raw" || pathParts[0] === "s")) {
2791
3021
  return {
2792
3022
  bucket: pathParts[2],
@@ -2796,6 +3026,12 @@ function parseStorageUrl(input, defaults = {}) {
2796
3026
  prefix: pathParts.slice(3).join("/")
2797
3027
  };
2798
3028
  }
3029
+ if (STAC_API_PATH_RE.test(url.pathname)) {
3030
+ return {
3031
+ ...defaultResult(defaults),
3032
+ endpoint: `${url.protocol}//${url.host}`
3033
+ };
3034
+ }
2799
3035
  if (pathParts.length > 0) {
2800
3036
  const endpoint = `${url.protocol}//${url.host}`;
2801
3037
  return {
@@ -2998,7 +3234,10 @@ var GEO_TYPE_KEYWORDS = [
2998
3234
  "geometrycollection",
2999
3235
  "sdo_geometry"
3000
3236
  ];
3001
- var GEO_NAME_HINTS = ["geom", "geometry", "geo_", "_geo", "wkb", "wkt", "shape", "spatial"];
3237
+ var GEO_NAME_HINTS = ["geom", "geometry", "wkb", "wkt", "shape", "spatial", "geo"];
3238
+ function tokenizeColumnName(name) {
3239
+ 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);
3240
+ }
3002
3241
  var GEOJSON_TYPES = [
3003
3242
  "Point",
3004
3243
  "LineString",
@@ -3028,14 +3267,14 @@ function findGeoColumn(schema) {
3028
3267
  if (GEO_NAMES.includes(f.name.toLowerCase())) return f.name;
3029
3268
  }
3030
3269
  for (const f of schema) {
3031
- const n = f.name.toLowerCase();
3270
+ const tokens = tokenizeColumnName(f.name);
3032
3271
  const t = f.type.toLowerCase();
3033
3272
  const isBinary = t.includes("blob") || t.includes("binary") || t.includes("bytea");
3034
- if (isBinary && GEO_NAME_HINTS.some((hint) => n.includes(hint))) return f.name;
3273
+ if (isBinary && tokens.some((tok) => GEO_NAME_HINTS.includes(tok))) return f.name;
3035
3274
  }
3036
3275
  for (const f of schema) {
3037
- const n = f.name.toLowerCase();
3038
- if (GEO_NAME_HINTS.some((hint) => n.includes(hint))) return f.name;
3276
+ const tokens = tokenizeColumnName(f.name);
3277
+ if (tokens.some((tok) => GEO_NAME_HINTS.includes(tok))) return f.name;
3039
3278
  }
3040
3279
  return null;
3041
3280
  }
@@ -3092,6 +3331,6 @@ function isWKT(value) {
3092
3331
  return WKT_TYPES.some((t) => s.startsWith(t) || s.startsWith(`MULTI${t}`));
3093
3332
  }
3094
3333
 
3095
- export { COPY_FEEDBACK_MS, DEFAULT_TARGET_CRS, DUCKDB_INIT_TIMEOUT_MS, LAYER_HUE_MULTIPLIER, MAX_QUERY_HISTORY_ENTRIES, PROVIDERS, PROVIDER_IDS, QueryCancelledError, SF_LABELS, SQL_PREVIEW_LENGTH, STORAGE_KEYS, UrlAdapter, VIEWER_DIR_EXTENSIONS, WGS84_CODES, buildDataTypeLabel, buildDuckDbSource, buildEndpointFromTemplate, buildGeoArrowTables, buildProviderBaseUrl, clampBounds, classifyType, describeParseResult, escapeCsvField, extractBounds, extractEpsgFromGeoMeta, extractGeometryTypes, findGeoColumn, findGeoColumnFromRows, formatDate, formatFileSize, formatValue, generateHexDump, getDuckDbReadFn, getFileExtension, getFileTypeInfo, getMimeType, getNativeScheme, getProvider, getViewerKind, handleLoadError, interpolateTemplates, isCloudNativeFormat, isGcsProvider, isQueryable, jsonReplacerBigInt, loadFromStorage, looksLikeUrl, markSqlBlocks, normalizeGeomType, parseMarkdownDocument, parseStorageUrl, parseWKB, persistToStorage, readParquetMetadata, resolveCloudUrl, safeClamp, safeDecodeURIComponent, serializeToCsv, serializeToJson, sortFileEntries, toBinary, toggleSortField, typeBadgeClass, typeColor, typeLabel };
3334
+ export { COPY_FEEDBACK_MS, DEFAULT_TARGET_CRS, DUCKDB_INIT_TIMEOUT_MS, LAYER_HUE_MULTIPLIER, MAX_QUERY_HISTORY_ENTRIES, PROVIDERS, PROVIDER_IDS, QueryCancelledError, SF_LABELS, SQL_PREVIEW_LENGTH, STAC_GEOPARQUET_REQUIRED_COLUMNS, STORAGE_KEYS, UrlAdapter, VIEWER_DIR_EXTENSIONS, WGS84_CODES, buildDataTypeLabel, buildDuckDbSource, buildEndpointFromTemplate, buildGeoArrowTables, buildProviderBaseUrl, clampBounds, classifyType, describeParseResult, escapeCsvField, exportToCsv, exportToJson, extractBounds, extractEpsgFromGeoMeta, extractGeometryTypes, findGeoColumn, findGeoColumnFromRows, flattenStacBbox, formatDate, formatFileSize, formatValue, generateHexDump, getAccessMode, getDuckDbReadFn, getFileExtension, getFileTypeInfo, getMimeType, getNativeScheme, getProvider, getViewerKind, handleLoadError, interpolateTemplates, isCloudNativeFormat, isGcsProvider, isPubliclyStreamable, isQueryable, isStacGeoparquetSchema, jsonReplacerBigInt, loadFromStorage, looksLikeUrl, markSqlBlocks, normalizeGeomType, parseMarkdownDocument, parseStorageUrl, parseWKB, persistToStorage, pickStacPrimaryAsset, readParquetMetadata, resolveCloudUrl, resolveProviderEndpoint, resolveStacAssetHref, safeClamp, safeDecodeURIComponent, serializeToCsv, serializeToJson, sortFileEntries, stacRowToItem, toBinary, toggleSortField, typeBadgeClass, typeColor, typeLabel };
3096
3335
  //# sourceMappingURL=index.js.map
3097
3336
  //# sourceMappingURL=index.js.map