@walkthru-earth/objex-utils 1.2.1 → 1.3.1

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
@@ -1,8 +1,3 @@
1
- import '@developmentseed/epsg/all';
2
- import '@developmentseed/epsg/all.csv.gz?url';
3
- import '@developmentseed/geotiff';
4
- import '@developmentseed/proj';
5
- import 'proj4';
6
1
  import { Field, Float64, FixedSizeList, Schema, Struct, makeData, RecordBatch, Table, List, Utf8 } from 'apache-arrow';
7
2
 
8
3
  // ../../src/lib/constants.ts
@@ -1460,6 +1455,8 @@ function resolveCloudUrl(url) {
1460
1455
  }
1461
1456
  return url;
1462
1457
  }
1458
+
1459
+ // ../../src/lib/utils/cog-pure.ts
1463
1460
  var SF_LABELS = {
1464
1461
  1: "uint",
1465
1462
  2: "int",
@@ -2475,6 +2472,15 @@ async function readParquetMetadata(url) {
2475
2472
  name: col.name,
2476
2473
  type: mapParquetType(col)
2477
2474
  }));
2475
+ const topLevelColumns = [];
2476
+ const root = metadata.schema[0];
2477
+ const rootChildren = root?.num_children ?? 0;
2478
+ let cursor = 1;
2479
+ for (let i = 0; i < rootChildren && cursor < metadata.schema.length; i++) {
2480
+ const el = metadata.schema[cursor];
2481
+ if (el?.name) topLevelColumns.push(el.name);
2482
+ cursor += countSubtree(metadata.schema, cursor);
2483
+ }
2478
2484
  let geo = null;
2479
2485
  let legacyGeoParquet = false;
2480
2486
  const geoKv = metadata.key_value_metadata?.find((kv) => kv.key === "geo");
@@ -2516,7 +2522,26 @@ async function readParquetMetadata(url) {
2516
2522
  compression = [...codecs].join(", ");
2517
2523
  }
2518
2524
  }
2519
- return { rowCount, schema, geo, legacyGeoParquet, createdBy, numRowGroups, compression };
2525
+ return {
2526
+ rowCount,
2527
+ schema,
2528
+ topLevelColumns,
2529
+ geo,
2530
+ legacyGeoParquet,
2531
+ createdBy,
2532
+ numRowGroups,
2533
+ compression
2534
+ };
2535
+ }
2536
+ function countSubtree(schema, start) {
2537
+ const el = schema[start];
2538
+ if (!el) return 0;
2539
+ const n = el.num_children ?? 0;
2540
+ let cursor = start + 1;
2541
+ for (let i = 0; i < n; i++) {
2542
+ cursor += countSubtree(schema, cursor);
2543
+ }
2544
+ return cursor - start;
2520
2545
  }
2521
2546
  function extractEpsgFromGeoMeta(geo) {
2522
2547
  const primaryCol = geo.columns[geo.primaryColumn];
@@ -2555,6 +2580,144 @@ function extractBounds(geo) {
2555
2580
  return [primaryCol.bbox[0], primaryCol.bbox[1], primaryCol.bbox[2], primaryCol.bbox[3]];
2556
2581
  }
2557
2582
 
2583
+ // ../../src/lib/utils/stac-geoparquet.ts
2584
+ var STAC_GEOPARQUET_REQUIRED_COLUMNS = [
2585
+ "stac_version",
2586
+ "type",
2587
+ "geometry",
2588
+ "assets"
2589
+ ];
2590
+ function isStacGeoparquetSchema(schema) {
2591
+ if (!Array.isArray(schema) || schema.length === 0) return false;
2592
+ const names = new Set(schema.map((c) => c.name));
2593
+ return STAC_GEOPARQUET_REQUIRED_COLUMNS.every((c) => names.has(c));
2594
+ }
2595
+ function flattenStacBbox(bbox) {
2596
+ if (!bbox) return null;
2597
+ if (Array.isArray(bbox)) {
2598
+ if (bbox.length < 4) return null;
2599
+ const [minX, minY, maxX, maxY] = bbox;
2600
+ if (![minX, minY, maxX, maxY].every((v) => Number.isFinite(v))) return null;
2601
+ return [minX, minY, maxX, maxY];
2602
+ }
2603
+ if (typeof bbox === "object") {
2604
+ const { xmin, ymin, xmax, ymax } = bbox;
2605
+ if (![xmin, ymin, xmax, ymax].every((v) => Number.isFinite(v))) return null;
2606
+ return [xmin, ymin, xmax, ymax];
2607
+ }
2608
+ return null;
2609
+ }
2610
+ function resolveStacAssetHref(href, baseUrl) {
2611
+ if (!href) return href;
2612
+ if (/^[a-z][a-z0-9+\-.]*:\/\//i.test(href)) return href;
2613
+ try {
2614
+ return new URL(href, baseUrl).toString();
2615
+ } catch {
2616
+ return href;
2617
+ }
2618
+ }
2619
+ function pickStacPrimaryAsset(assets, preferredKeys) {
2620
+ if (!assets || typeof assets !== "object") return null;
2621
+ const entries = Object.entries(assets).filter(
2622
+ ([, a]) => a && typeof a === "object" && typeof a.href === "string"
2623
+ );
2624
+ if (entries.length === 0) return null;
2625
+ if (preferredKeys) {
2626
+ for (const key of preferredKeys) {
2627
+ const match = entries.find(([k]) => k === key);
2628
+ if (match) return { key: match[0], asset: match[1] };
2629
+ }
2630
+ }
2631
+ const data = entries.find(([k]) => k === "data");
2632
+ if (data) return { key: data[0], asset: data[1] };
2633
+ const byRole = entries.find(([, a]) => Array.isArray(a.roles) && a.roles.includes("data"));
2634
+ if (byRole) return { key: byRole[0], asset: byRole[1] };
2635
+ return { key: entries[0][0], asset: entries[0][1] };
2636
+ }
2637
+ function normalizeAssetsField(value, baseUrl) {
2638
+ if (!value || typeof value !== "object") return void 0;
2639
+ const out = {};
2640
+ for (const [key, raw] of Object.entries(value)) {
2641
+ if (!raw || typeof raw !== "object") continue;
2642
+ const asset = raw;
2643
+ if (typeof asset.href !== "string" || !asset.href) continue;
2644
+ out[key] = {
2645
+ ...asset,
2646
+ href: resolveStacAssetHref(asset.href, baseUrl)
2647
+ };
2648
+ }
2649
+ return Object.keys(out).length > 0 ? out : void 0;
2650
+ }
2651
+ function normalizeLinksField(value, baseUrl) {
2652
+ if (!Array.isArray(value)) return void 0;
2653
+ const links = [];
2654
+ for (const raw of value) {
2655
+ if (!raw || typeof raw !== "object") continue;
2656
+ const link = raw;
2657
+ if (typeof link.href !== "string" || typeof link.rel !== "string") continue;
2658
+ links.push({ ...link, href: resolveStacAssetHref(link.href, baseUrl) });
2659
+ }
2660
+ return links.length > 0 ? links : void 0;
2661
+ }
2662
+ function stacRowToItem(row, baseUrl, opts = {}) {
2663
+ const { wkbParser, wkbColumn = "geom_wkb", geometryColumn = "geometry" } = opts;
2664
+ let geometry = row[geometryColumn];
2665
+ if (!geometry) {
2666
+ const wkb = row[wkbColumn];
2667
+ if (wkb && wkbParser) {
2668
+ const bytes = wkb instanceof Uint8Array ? wkb : toUint8Array(wkb);
2669
+ if (bytes) {
2670
+ try {
2671
+ geometry = wkbParser(bytes) ?? void 0;
2672
+ } catch {
2673
+ geometry = void 0;
2674
+ }
2675
+ }
2676
+ }
2677
+ }
2678
+ const bbox = flattenStacBbox(row.bbox) ?? void 0;
2679
+ const assets = normalizeAssetsField(row.assets, baseUrl);
2680
+ const links = normalizeLinksField(row.links, baseUrl);
2681
+ const properties = {};
2682
+ for (const [key, value] of Object.entries(row)) {
2683
+ if (value === null || value === void 0) continue;
2684
+ if (key.startsWith("proj:") || key.startsWith("raster:") || key.startsWith("eo:")) {
2685
+ properties[key] = value;
2686
+ }
2687
+ if (key === "datetime") {
2688
+ properties.datetime = value instanceof Date ? value.toISOString() : String(value);
2689
+ }
2690
+ if (key === "bands") {
2691
+ properties.bands = value;
2692
+ }
2693
+ }
2694
+ const item = {
2695
+ type: "Feature",
2696
+ stac_version: typeof row.stac_version === "string" ? row.stac_version : "1.0.0",
2697
+ id: typeof row.id === "string" ? row.id : String(row.id ?? ""),
2698
+ properties
2699
+ };
2700
+ if (typeof row.collection === "string") item.collection = row.collection;
2701
+ if (Array.isArray(row.stac_extensions)) {
2702
+ item.stac_extensions = row.stac_extensions;
2703
+ }
2704
+ if (bbox) item.bbox = bbox;
2705
+ if (geometry) item.geometry = geometry;
2706
+ if (assets) item.assets = assets;
2707
+ if (links) item.links = links;
2708
+ return item;
2709
+ }
2710
+ function toUint8Array(value) {
2711
+ if (value instanceof Uint8Array) return value;
2712
+ if (value instanceof ArrayBuffer) return new Uint8Array(value);
2713
+ if (ArrayBuffer.isView(value)) {
2714
+ const view = value;
2715
+ return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
2716
+ }
2717
+ if (Array.isArray(value)) return new Uint8Array(value);
2718
+ return null;
2719
+ }
2720
+
2558
2721
  // ../../src/lib/utils/storage-url.ts
2559
2722
  function buildSchemeMap() {
2560
2723
  const map = {};
@@ -2568,6 +2731,32 @@ function buildSchemeMap() {
2568
2731
  return map;
2569
2732
  }
2570
2733
  var SCHEME_MAP = buildSchemeMap();
2734
+ var AWS_VHOST_RE = /^(.+)\.s3[.-]([a-z0-9-]+)\.amazonaws\.com$/;
2735
+ var AWS_PATH_RE = /^s3[.-]([a-z0-9-]+)\.amazonaws\.com$/;
2736
+ var AWS_GLOBAL_HOST = "s3.amazonaws.com";
2737
+ var R2_RE = /^([a-z0-9]+)\.r2\.cloudflarestorage\.com$/;
2738
+ var GCS_GLOBAL_HOST = "storage.googleapis.com";
2739
+ var GCS_VHOST_RE = /^(.+)\.storage\.googleapis\.com$/;
2740
+ var DO_VHOST_RE = /^(.+)\.([a-z0-9-]+)\.digitaloceanspaces\.com$/;
2741
+ var DO_PATH_RE = /^([a-z0-9-]+)\.digitaloceanspaces\.com$/;
2742
+ var WASABI_RE = /^s3\.([a-z0-9-]+)\.wasabisys\.com$/;
2743
+ var B2_S3_RE = /^(.+)\.s3\.([a-z0-9-]+)\.backblazeb2\.com$/;
2744
+ var B2_NATIVE_RE = /^f[a-z0-9]+\.backblazeb2\.com$/;
2745
+ var OSS_RE = /^(.+)\.(oss-[a-z0-9-]+)\.aliyuncs\.com$/;
2746
+ var COS_RE = /^(.+)\.cos\.([a-z0-9-]+)\.myqcloud\.com$/;
2747
+ var YANDEX_HOST = "storage.yandexcloud.net";
2748
+ var CONTABO_RE = /^([a-z0-9]+)\.contabostorage\.com$/;
2749
+ var HETZNER_RE = /^([a-z0-9]+)\.your-objectstorage\.com$/;
2750
+ var LINODE_VHOST_RE = /^(.+)\.([a-z0-9-]+)\.linodeobjects\.com$/;
2751
+ var LINODE_PATH_RE = /^([a-z0-9-]+)\.linodeobjects\.com$/;
2752
+ var OVH_RE = /^s3\.([a-z0-9-]+)\.io\.cloud\.ovh\.(?:net|us)$/;
2753
+ var AZURE_BLOB_RE = /^([a-z0-9]+)\.blob\.core\.windows\.net$/;
2754
+ var STORJ_GATEWAY_RE = /^gateway\.(?:([a-z0-9]+)\.)?storjshare\.io$/;
2755
+ var STORJ_LINK_RE = /^link\.(?:([a-z0-9]+)\.)?storjshare\.io$/;
2756
+ function isMinioLikeHost(host) {
2757
+ return host.includes("minio") || host === "localhost" || host === "127.0.0.1" || host.startsWith("192.168.") || host.startsWith("10.");
2758
+ }
2759
+ var STAC_API_PATH_RE = /\/(collections|items|catalogs|search)(\/|\?|$)/i;
2571
2760
  function defaultResult(defaults) {
2572
2761
  return {
2573
2762
  bucket: "",
@@ -2608,7 +2797,7 @@ function parseStorageUrl(input, defaults = {}) {
2608
2797
  const url = new URL(trimmed);
2609
2798
  const host = url.hostname;
2610
2799
  const pathParts = url.pathname.replace(/^\//, "").split("/").filter(Boolean);
2611
- const awsVhost = host.match(/^(.+)\.s3[.-]([a-z0-9-]+)\.amazonaws\.com$/);
2800
+ const awsVhost = host.match(AWS_VHOST_RE);
2612
2801
  if (awsVhost) {
2613
2802
  return {
2614
2803
  bucket: awsVhost[1],
@@ -2618,7 +2807,7 @@ function parseStorageUrl(input, defaults = {}) {
2618
2807
  prefix: pathParts.join("/")
2619
2808
  };
2620
2809
  }
2621
- const awsPath = host.match(/^s3[.-]([a-z0-9-]+)\.amazonaws\.com$/);
2810
+ const awsPath = host.match(AWS_PATH_RE);
2622
2811
  if (awsPath && pathParts.length > 0) {
2623
2812
  return {
2624
2813
  bucket: pathParts[0],
@@ -2628,7 +2817,7 @@ function parseStorageUrl(input, defaults = {}) {
2628
2817
  prefix: pathParts.slice(1).join("/")
2629
2818
  };
2630
2819
  }
2631
- if (host === "s3.amazonaws.com" && pathParts.length > 0) {
2820
+ if (host === AWS_GLOBAL_HOST && pathParts.length > 0) {
2632
2821
  return {
2633
2822
  bucket: pathParts[0],
2634
2823
  region: defaults.region || "us-east-1",
@@ -2637,7 +2826,7 @@ function parseStorageUrl(input, defaults = {}) {
2637
2826
  prefix: pathParts.slice(1).join("/")
2638
2827
  };
2639
2828
  }
2640
- const r2Match = host.match(/^([a-z0-9]+)\.r2\.cloudflarestorage\.com$/);
2829
+ const r2Match = host.match(R2_RE);
2641
2830
  if (r2Match && pathParts.length > 0) {
2642
2831
  return {
2643
2832
  bucket: pathParts[0],
@@ -2647,7 +2836,7 @@ function parseStorageUrl(input, defaults = {}) {
2647
2836
  prefix: pathParts.slice(1).join("/")
2648
2837
  };
2649
2838
  }
2650
- if (host === "storage.googleapis.com" && pathParts.length > 0) {
2839
+ if (host === GCS_GLOBAL_HOST && pathParts.length > 0) {
2651
2840
  return {
2652
2841
  bucket: pathParts[0],
2653
2842
  region: defaults.region || "us",
@@ -2656,7 +2845,7 @@ function parseStorageUrl(input, defaults = {}) {
2656
2845
  prefix: pathParts.slice(1).join("/")
2657
2846
  };
2658
2847
  }
2659
- const gcsVhost = host.match(/^(.+)\.storage\.googleapis\.com$/);
2848
+ const gcsVhost = host.match(GCS_VHOST_RE);
2660
2849
  if (gcsVhost) {
2661
2850
  return {
2662
2851
  bucket: gcsVhost[1],
@@ -2666,7 +2855,7 @@ function parseStorageUrl(input, defaults = {}) {
2666
2855
  prefix: pathParts.join("/")
2667
2856
  };
2668
2857
  }
2669
- const doVhost = host.match(/^(.+)\.([a-z0-9-]+)\.digitaloceanspaces\.com$/);
2858
+ const doVhost = host.match(DO_VHOST_RE);
2670
2859
  if (doVhost) {
2671
2860
  return {
2672
2861
  bucket: doVhost[1],
@@ -2676,7 +2865,7 @@ function parseStorageUrl(input, defaults = {}) {
2676
2865
  prefix: pathParts.join("/")
2677
2866
  };
2678
2867
  }
2679
- const doPath = host.match(/^([a-z0-9-]+)\.digitaloceanspaces\.com$/);
2868
+ const doPath = host.match(DO_PATH_RE);
2680
2869
  if (doPath && pathParts.length > 0) {
2681
2870
  return {
2682
2871
  bucket: pathParts[0],
@@ -2686,7 +2875,7 @@ function parseStorageUrl(input, defaults = {}) {
2686
2875
  prefix: pathParts.slice(1).join("/")
2687
2876
  };
2688
2877
  }
2689
- const wasabiMatch = host.match(/^s3\.([a-z0-9-]+)\.wasabisys\.com$/);
2878
+ const wasabiMatch = host.match(WASABI_RE);
2690
2879
  if (wasabiMatch && pathParts.length > 0) {
2691
2880
  return {
2692
2881
  bucket: pathParts[0],
@@ -2696,7 +2885,7 @@ function parseStorageUrl(input, defaults = {}) {
2696
2885
  prefix: pathParts.slice(1).join("/")
2697
2886
  };
2698
2887
  }
2699
- const b2S3 = host.match(/^(.+)\.s3\.([a-z0-9-]+)\.backblazeb2\.com$/);
2888
+ const b2S3 = host.match(B2_S3_RE);
2700
2889
  if (b2S3) {
2701
2890
  return {
2702
2891
  bucket: b2S3[1],
@@ -2706,7 +2895,7 @@ function parseStorageUrl(input, defaults = {}) {
2706
2895
  prefix: pathParts.join("/")
2707
2896
  };
2708
2897
  }
2709
- const b2Native = host.match(/^f[a-z0-9]+\.backblazeb2\.com$/);
2898
+ const b2Native = host.match(B2_NATIVE_RE);
2710
2899
  if (b2Native && pathParts[0] === "file" && pathParts.length > 1) {
2711
2900
  return {
2712
2901
  bucket: pathParts[1],
@@ -2716,7 +2905,7 @@ function parseStorageUrl(input, defaults = {}) {
2716
2905
  prefix: pathParts.slice(2).join("/")
2717
2906
  };
2718
2907
  }
2719
- const ossMatch = host.match(/^(.+)\.(oss-[a-z0-9-]+)\.aliyuncs\.com$/);
2908
+ const ossMatch = host.match(OSS_RE);
2720
2909
  if (ossMatch) {
2721
2910
  return {
2722
2911
  bucket: ossMatch[1],
@@ -2726,7 +2915,7 @@ function parseStorageUrl(input, defaults = {}) {
2726
2915
  prefix: pathParts.join("/")
2727
2916
  };
2728
2917
  }
2729
- const cosMatch = host.match(/^(.+)\.cos\.([a-z0-9-]+)\.myqcloud\.com$/);
2918
+ const cosMatch = host.match(COS_RE);
2730
2919
  if (cosMatch) {
2731
2920
  return {
2732
2921
  bucket: cosMatch[1],
@@ -2736,7 +2925,7 @@ function parseStorageUrl(input, defaults = {}) {
2736
2925
  prefix: pathParts.join("/")
2737
2926
  };
2738
2927
  }
2739
- if (host === "storage.yandexcloud.net" && pathParts.length > 0) {
2928
+ if (host === YANDEX_HOST && pathParts.length > 0) {
2740
2929
  return {
2741
2930
  bucket: pathParts[0],
2742
2931
  region: defaults.region || "ru-central1",
@@ -2745,7 +2934,7 @@ function parseStorageUrl(input, defaults = {}) {
2745
2934
  prefix: pathParts.slice(1).join("/")
2746
2935
  };
2747
2936
  }
2748
- const contaboMatch = host.match(/^([a-z0-9]+)\.contabostorage\.com$/);
2937
+ const contaboMatch = host.match(CONTABO_RE);
2749
2938
  if (contaboMatch && pathParts.length > 0) {
2750
2939
  return {
2751
2940
  bucket: pathParts[0],
@@ -2755,7 +2944,7 @@ function parseStorageUrl(input, defaults = {}) {
2755
2944
  prefix: pathParts.slice(1).join("/")
2756
2945
  };
2757
2946
  }
2758
- const hetznerMatch = host.match(/^([a-z0-9]+)\.your-objectstorage\.com$/);
2947
+ const hetznerMatch = host.match(HETZNER_RE);
2759
2948
  if (hetznerMatch && pathParts.length > 0) {
2760
2949
  return {
2761
2950
  bucket: pathParts[0],
@@ -2765,7 +2954,7 @@ function parseStorageUrl(input, defaults = {}) {
2765
2954
  prefix: pathParts.slice(1).join("/")
2766
2955
  };
2767
2956
  }
2768
- const linodeVhost = host.match(/^(.+)\.([a-z0-9-]+)\.linodeobjects\.com$/);
2957
+ const linodeVhost = host.match(LINODE_VHOST_RE);
2769
2958
  if (linodeVhost) {
2770
2959
  return {
2771
2960
  bucket: linodeVhost[1],
@@ -2775,7 +2964,7 @@ function parseStorageUrl(input, defaults = {}) {
2775
2964
  prefix: pathParts.join("/")
2776
2965
  };
2777
2966
  }
2778
- const linodePath = host.match(/^([a-z0-9-]+)\.linodeobjects\.com$/);
2967
+ const linodePath = host.match(LINODE_PATH_RE);
2779
2968
  if (linodePath && pathParts.length > 0) {
2780
2969
  return {
2781
2970
  bucket: pathParts[0],
@@ -2785,7 +2974,7 @@ function parseStorageUrl(input, defaults = {}) {
2785
2974
  prefix: pathParts.slice(1).join("/")
2786
2975
  };
2787
2976
  }
2788
- const ovhMatch = host.match(/^s3\.([a-z0-9-]+)\.io\.cloud\.ovh\.(?:net|us)$/);
2977
+ const ovhMatch = host.match(OVH_RE);
2789
2978
  if (ovhMatch && pathParts.length > 0) {
2790
2979
  return {
2791
2980
  bucket: pathParts[0],
@@ -2795,8 +2984,7 @@ function parseStorageUrl(input, defaults = {}) {
2795
2984
  prefix: pathParts.slice(1).join("/")
2796
2985
  };
2797
2986
  }
2798
- const isMinioLike = host.includes("minio") || host === "localhost" || host === "127.0.0.1" || host.startsWith("192.168.") || host.startsWith("10.");
2799
- if (isMinioLike && pathParts.length > 0) {
2987
+ if (isMinioLikeHost(host) && pathParts.length > 0) {
2800
2988
  return {
2801
2989
  bucket: pathParts[0],
2802
2990
  region: defaults.region || "us-east-1",
@@ -2805,7 +2993,7 @@ function parseStorageUrl(input, defaults = {}) {
2805
2993
  prefix: pathParts.slice(1).join("/")
2806
2994
  };
2807
2995
  }
2808
- const azureBlob = host.match(/^([a-z0-9]+)\.blob\.core\.windows\.net$/);
2996
+ const azureBlob = host.match(AZURE_BLOB_RE);
2809
2997
  if (azureBlob && pathParts.length > 0) {
2810
2998
  return {
2811
2999
  bucket: pathParts[0],
@@ -2815,7 +3003,7 @@ function parseStorageUrl(input, defaults = {}) {
2815
3003
  prefix: pathParts.slice(1).join("/")
2816
3004
  };
2817
3005
  }
2818
- const storjGateway = host.match(/^gateway\.(?:([a-z0-9]+)\.)?storjshare\.io$/);
3006
+ const storjGateway = host.match(STORJ_GATEWAY_RE);
2819
3007
  if (storjGateway && pathParts.length > 0) {
2820
3008
  return {
2821
3009
  bucket: pathParts[0],
@@ -2825,7 +3013,7 @@ function parseStorageUrl(input, defaults = {}) {
2825
3013
  prefix: pathParts.slice(1).join("/")
2826
3014
  };
2827
3015
  }
2828
- const storjLink = host.match(/^link\.(?:([a-z0-9]+)\.)?storjshare\.io$/);
3016
+ const storjLink = host.match(STORJ_LINK_RE);
2829
3017
  if (storjLink && pathParts.length >= 3 && (pathParts[0] === "raw" || pathParts[0] === "s")) {
2830
3018
  return {
2831
3019
  bucket: pathParts[2],
@@ -2835,6 +3023,12 @@ function parseStorageUrl(input, defaults = {}) {
2835
3023
  prefix: pathParts.slice(3).join("/")
2836
3024
  };
2837
3025
  }
3026
+ if (STAC_API_PATH_RE.test(url.pathname)) {
3027
+ return {
3028
+ ...defaultResult(defaults),
3029
+ endpoint: `${url.protocol}//${url.host}`
3030
+ };
3031
+ }
2838
3032
  if (pathParts.length > 0) {
2839
3033
  const endpoint = `${url.protocol}//${url.host}`;
2840
3034
  return {
@@ -3134,6 +3328,6 @@ function isWKT(value) {
3134
3328
  return WKT_TYPES.some((t) => s.startsWith(t) || s.startsWith(`MULTI${t}`));
3135
3329
  }
3136
3330
 
3137
- 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, exportToCsv, exportToJson, extractBounds, extractEpsgFromGeoMeta, extractGeometryTypes, findGeoColumn, findGeoColumnFromRows, formatDate, formatFileSize, formatValue, generateHexDump, getAccessMode, getDuckDbReadFn, getFileExtension, getFileTypeInfo, getMimeType, getNativeScheme, getProvider, getViewerKind, handleLoadError, interpolateTemplates, isCloudNativeFormat, isGcsProvider, isPubliclyStreamable, isQueryable, jsonReplacerBigInt, loadFromStorage, looksLikeUrl, markSqlBlocks, normalizeGeomType, parseMarkdownDocument, parseStorageUrl, parseWKB, persistToStorage, readParquetMetadata, resolveCloudUrl, resolveProviderEndpoint, safeClamp, safeDecodeURIComponent, serializeToCsv, serializeToJson, sortFileEntries, toBinary, toggleSortField, typeBadgeClass, typeColor, typeLabel };
3331
+ 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 };
3138
3332
  //# sourceMappingURL=index.js.map
3139
3333
  //# sourceMappingURL=index.js.map