@walkthru-earth/objex-utils 1.3.0 → 1.4.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
@@ -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
@@ -1153,15 +1148,15 @@ var PROVIDERS = {
1153
1148
  schemes: ["azure", "az", "abfs", "abfss", "wasbs", "adl"]
1154
1149
  },
1155
1150
  minio: {
1156
- label: "MinIO",
1157
- description: "Self-hosted MinIO or S3-compatible",
1151
+ label: "MinIO / RustFS / Custom",
1152
+ description: "MinIO, RustFS, or any custom S3-compatible endpoint",
1158
1153
  authMethod: "sigv4",
1159
1154
  needsRegion: false,
1160
1155
  needsEndpoint: true,
1161
1156
  defaultRegion: "us-east-1",
1162
1157
  endpointTemplate: null,
1163
1158
  regions: [],
1164
- endpointPlaceholder: "https://minio.example.com or http://localhost:9000",
1159
+ endpointPlaceholder: "https://s3.example.com or http://localhost:9000",
1165
1160
  schemes: []
1166
1161
  },
1167
1162
  storj: {
@@ -1429,7 +1424,460 @@ var UrlAdapter = class {
1429
1424
  }
1430
1425
  };
1431
1426
 
1432
- // ../../src/lib/utils/cloud-url.ts
1427
+ // src/app-config.ts
1428
+ var DEFAULT_APP_CONFIG = {
1429
+ defaults: { theme: "system", locale: "en", featureLimit: 1e3, mosaicItemLimit: 2e3 },
1430
+ ui: { showConnectionRail: true, showFileTree: true, showSettings: true },
1431
+ basemaps: [],
1432
+ defaultBasemap: {},
1433
+ connections: []
1434
+ };
1435
+ function asObject(v) {
1436
+ return v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
1437
+ }
1438
+ function coerceTheme(v) {
1439
+ return v === "light" || v === "dark" || v === "system" ? v : void 0;
1440
+ }
1441
+ function coerceString(v) {
1442
+ return typeof v === "string" && v.trim().length > 0 ? v.trim() : void 0;
1443
+ }
1444
+ function coercePositiveInt(v) {
1445
+ return typeof v === "number" && Number.isFinite(v) && v >= 1 ? Math.floor(v) : void 0;
1446
+ }
1447
+ function coerceBool(v) {
1448
+ return typeof v === "boolean" ? v : void 0;
1449
+ }
1450
+ function resolveSetting(...candidates) {
1451
+ for (const c of candidates) {
1452
+ if (c !== null && c !== void 0) return c;
1453
+ }
1454
+ return void 0;
1455
+ }
1456
+ function parseVisibilityParam(value) {
1457
+ if (value === "hide") return false;
1458
+ if (value === "show") return true;
1459
+ return void 0;
1460
+ }
1461
+ function coerceBasemaps(v) {
1462
+ if (!Array.isArray(v)) return void 0;
1463
+ const out = [];
1464
+ for (const raw of v) {
1465
+ const o = asObject(raw);
1466
+ if (!o) continue;
1467
+ const id = coerceString(o.id);
1468
+ const label = coerceString(o.label);
1469
+ const url = coerceString(o.url);
1470
+ const type = o.type === "vector" || o.type === "raster" ? o.type : void 0;
1471
+ if (!id || !label || !url || !type) continue;
1472
+ const variant = o.variant === "light" || o.variant === "dark" ? o.variant : void 0;
1473
+ out.push({ id, label, url, type, ...variant ? { variant } : {} });
1474
+ }
1475
+ return out;
1476
+ }
1477
+ function coerceConnections(v) {
1478
+ if (!Array.isArray(v)) return void 0;
1479
+ const out = [];
1480
+ for (const raw of v) {
1481
+ const o = asObject(raw);
1482
+ if (!o) continue;
1483
+ const name = coerceString(o.name);
1484
+ const bucket = coerceString(o.bucket);
1485
+ if (!name || !bucket) continue;
1486
+ const region = coerceString(o.region);
1487
+ const endpoint = coerceString(o.endpoint);
1488
+ const anonymous = coerceBool(o.anonymous);
1489
+ const authMethod = o.authMethod === "sigv4" || o.authMethod === "sas-token" ? o.authMethod : void 0;
1490
+ const rootPrefix = coerceString(o.rootPrefix);
1491
+ out.push({
1492
+ name,
1493
+ bucket,
1494
+ provider: coerceString(o.provider) ?? "s3",
1495
+ ...region !== void 0 ? { region } : {},
1496
+ ...endpoint !== void 0 ? { endpoint } : {},
1497
+ ...anonymous !== void 0 ? { anonymous } : {},
1498
+ ...authMethod !== void 0 ? { authMethod } : {},
1499
+ ...rootPrefix !== void 0 ? { rootPrefix } : {}
1500
+ });
1501
+ }
1502
+ return out;
1503
+ }
1504
+ function resolveBasemap(config, variant, userId) {
1505
+ const list = config.basemaps;
1506
+ if (list.length === 0) return void 0;
1507
+ if (userId) {
1508
+ const picked = list.find((b) => b.id === userId);
1509
+ if (picked) return picked;
1510
+ }
1511
+ const defaultId = config.defaultBasemap[variant];
1512
+ if (defaultId) {
1513
+ const byDefault = list.find((b) => b.id === defaultId);
1514
+ if (byDefault) return byDefault;
1515
+ }
1516
+ return list.find((b) => b.variant === variant) ?? list[0];
1517
+ }
1518
+ function mergeAppConfig(base, override) {
1519
+ const o = asObject(override);
1520
+ if (!o) return base;
1521
+ const d = asObject(o.defaults) ?? {};
1522
+ const u = asObject(o.ui) ?? {};
1523
+ const db = asObject(o.defaultBasemap) ?? {};
1524
+ return {
1525
+ defaults: {
1526
+ theme: coerceTheme(d.theme) ?? base.defaults.theme,
1527
+ locale: coerceString(d.locale) ?? base.defaults.locale,
1528
+ featureLimit: coercePositiveInt(d.featureLimit) ?? base.defaults.featureLimit,
1529
+ mosaicItemLimit: coercePositiveInt(d.mosaicItemLimit) ?? base.defaults.mosaicItemLimit
1530
+ },
1531
+ ui: {
1532
+ showConnectionRail: coerceBool(u.showConnectionRail) ?? base.ui.showConnectionRail,
1533
+ showFileTree: coerceBool(u.showFileTree) ?? base.ui.showFileTree,
1534
+ showSettings: coerceBool(u.showSettings) ?? base.ui.showSettings
1535
+ },
1536
+ basemaps: coerceBasemaps(o.basemaps) ?? base.basemaps,
1537
+ defaultBasemap: (() => {
1538
+ const light = coerceString(db.light) ?? base.defaultBasemap.light;
1539
+ const dark = coerceString(db.dark) ?? base.defaultBasemap.dark;
1540
+ return { ...light !== void 0 ? { light } : {}, ...dark !== void 0 ? { dark } : {} };
1541
+ })(),
1542
+ connections: coerceConnections(o.connections) ?? base.connections
1543
+ };
1544
+ }
1545
+
1546
+ // src/stac.ts
1547
+ var STAC_COG_ASSET_KEYS = ["visual", "image", "data", "rendered_preview"];
1548
+ function isStacItem(json) {
1549
+ if (!json || typeof json !== "object") return false;
1550
+ const obj = json;
1551
+ return obj.type === "Feature" && typeof obj.stac_version === "string";
1552
+ }
1553
+ function isStacFeatureCollection(json) {
1554
+ if (!json || typeof json !== "object") return false;
1555
+ const obj = json;
1556
+ if (obj.type !== "FeatureCollection") return false;
1557
+ if (!Array.isArray(obj.features) || obj.features.length === 0) return false;
1558
+ if (typeof obj.stac_version === "string") return true;
1559
+ return isStacItem(obj.features[0]);
1560
+ }
1561
+ function isStacCollection(json) {
1562
+ if (!json || typeof json !== "object") return false;
1563
+ const obj = json;
1564
+ return obj.type === "Collection" && typeof obj.stac_version === "string" && Array.isArray(obj.links);
1565
+ }
1566
+ function isStacCatalog(json) {
1567
+ if (!json || typeof json !== "object") return false;
1568
+ const obj = json;
1569
+ return obj.type === "Catalog" && typeof obj.stac_version === "string" && Array.isArray(obj.links);
1570
+ }
1571
+ function classifyStac(json) {
1572
+ if (isStacItem(json)) return { kind: "item", item: json };
1573
+ if (isStacFeatureCollection(json)) return { kind: "item-collection", fc: json };
1574
+ if (isStacCollection(json)) return { kind: "collection", payload: json };
1575
+ if (isStacCatalog(json)) return { kind: "catalog", payload: json };
1576
+ return { kind: "none" };
1577
+ }
1578
+ function pickCogAssetHref(item, preferred) {
1579
+ const assets = item.assets ?? {};
1580
+ if (preferred && assets[preferred]?.href) return assets[preferred].href;
1581
+ for (const key of STAC_COG_ASSET_KEYS) {
1582
+ if (assets[key]?.href) return assets[key].href;
1583
+ }
1584
+ for (const asset of Object.values(assets)) {
1585
+ const t = typeof asset?.type === "string" ? asset.type.toLowerCase() : "";
1586
+ if (asset?.href && t.includes("tiff")) return asset.href;
1587
+ }
1588
+ return null;
1589
+ }
1590
+ function detectMosaicCapable(item) {
1591
+ return stacItemBbox(item) !== null && pickCogAssetHref(item) !== null;
1592
+ }
1593
+ function detectMultiCogCapable(item) {
1594
+ if (hasRgbBands(extractSentinelBandAssets(item))) return true;
1595
+ return hasCompositableBands(extractRasterBandAssets(item));
1596
+ }
1597
+ function stacItemBbox(item) {
1598
+ if (Array.isArray(item.bbox) && item.bbox.length >= 4) {
1599
+ return [Number(item.bbox[0]), Number(item.bbox[1]), Number(item.bbox[2]), Number(item.bbox[3])];
1600
+ }
1601
+ return null;
1602
+ }
1603
+ function buildMosaicSourceMeta(input, assetKey) {
1604
+ if (!input || typeof input !== "object") return null;
1605
+ if (isStacItem(input)) {
1606
+ const bbox = stacItemBbox(input);
1607
+ if (!bbox) return null;
1608
+ const href = pickCogAssetHref(input, assetKey);
1609
+ if (!href) return null;
1610
+ return {
1611
+ id: String(input.id ?? href),
1612
+ bbox,
1613
+ href
1614
+ };
1615
+ }
1616
+ const raw = input;
1617
+ if (Array.isArray(raw.bbox) && raw.bbox.length >= 4 && typeof raw.href === "string") {
1618
+ return {
1619
+ id: String(raw.id ?? raw.href),
1620
+ bbox: [Number(raw.bbox[0]), Number(raw.bbox[1]), Number(raw.bbox[2]), Number(raw.bbox[3])],
1621
+ href: raw.href
1622
+ };
1623
+ }
1624
+ return null;
1625
+ }
1626
+ function spatialCellKey(item, bbox) {
1627
+ const props = item.properties ?? {};
1628
+ const grid = props["grid:code"];
1629
+ if (typeof grid === "string" && grid) return `g:${grid}`;
1630
+ const utm = props["mgrs:utm_zone"];
1631
+ const band = props["mgrs:latitude_band"];
1632
+ const sq = props["mgrs:grid_square"];
1633
+ if (utm != null && band != null && sq != null) return `m:${utm}${band}${sq}`;
1634
+ const s2 = props["s2:mgrs_tile"];
1635
+ if (typeof s2 === "string" && s2) return `m:${s2}`;
1636
+ return `b:${bbox[0].toFixed(3)},${bbox[1].toFixed(3)},${bbox[2].toFixed(3)},${bbox[3].toFixed(3)}`;
1637
+ }
1638
+ var BAND_KEY_FALLBACKS = {
1639
+ red: ["red", "B04", "B4", "visual-red"],
1640
+ green: ["green", "B03", "B3", "visual-green"],
1641
+ blue: ["blue", "B02", "B2", "visual-blue"],
1642
+ nir: ["nir", "nir08", "B08", "B8", "B8A"],
1643
+ swir1: ["swir16", "swir1", "B11"],
1644
+ swir2: ["swir22", "swir2", "B12"],
1645
+ rededge: ["rededge1", "rededge", "B05", "B5"]
1646
+ };
1647
+ function extractSentinelBandAssets(item) {
1648
+ const out = {};
1649
+ const assets = item.assets ?? {};
1650
+ for (const [key, asset] of Object.entries(assets)) {
1651
+ if (!asset?.href) continue;
1652
+ const bands = asset["eo:bands"];
1653
+ if (Array.isArray(bands) && bands.length >= 1) {
1654
+ const common = bands[0]?.common_name?.toLowerCase();
1655
+ if (common && isBandSlot(common)) {
1656
+ if (!out[common]) out[common] = asset.href;
1657
+ continue;
1658
+ }
1659
+ }
1660
+ for (const slot of Object.keys(BAND_KEY_FALLBACKS)) {
1661
+ if (BAND_KEY_FALLBACKS[slot].includes(key) && !out[slot]) {
1662
+ out[slot] = asset.href;
1663
+ break;
1664
+ }
1665
+ }
1666
+ }
1667
+ return out;
1668
+ }
1669
+ function isBandSlot(value) {
1670
+ return value === "red" || value === "green" || value === "blue" || value === "nir" || value === "swir1" || value === "swir2" || value === "rededge";
1671
+ }
1672
+ function hasRgbBands(map) {
1673
+ return Boolean(map.red && map.green && map.blue);
1674
+ }
1675
+ function extractRasterBandAssets(item) {
1676
+ const out = [];
1677
+ const assets = item.assets ?? {};
1678
+ for (const [key, asset] of Object.entries(assets)) {
1679
+ if (!asset?.href) continue;
1680
+ const mediaType = typeof asset.type === "string" ? asset.type : void 0;
1681
+ if (mediaType && !/^image\/(tiff|geotiff)\b/i.test(mediaType)) continue;
1682
+ const roles = Array.isArray(asset.roles) ? asset.roles : void 0;
1683
+ if (roles && (roles.includes("thumbnail") || roles.includes("overview") || roles.includes("metadata"))) {
1684
+ continue;
1685
+ }
1686
+ const eoBands = Array.isArray(asset["eo:bands"]) ? asset["eo:bands"] : void 0;
1687
+ const assetExt = asset;
1688
+ const rasterBands = Array.isArray(assetExt["raster:bands"]) ? assetExt["raster:bands"] : void 0;
1689
+ const bandCount = rasterBands?.length ?? eoBands?.length;
1690
+ if (typeof bandCount === "number" && bandCount > 1) continue;
1691
+ const commonName = eoBands?.[0]?.common_name?.toLowerCase();
1692
+ out.push({
1693
+ key,
1694
+ href: asset.href,
1695
+ commonName,
1696
+ bandCount: typeof bandCount === "number" ? bandCount : 1,
1697
+ roles,
1698
+ mediaType,
1699
+ title: typeof asset.title === "string" ? asset.title : void 0
1700
+ });
1701
+ }
1702
+ return out;
1703
+ }
1704
+ function resolveBandSlotAssetKey(assets, slot) {
1705
+ const byCommon = assets.find((a) => a.commonName === slot);
1706
+ if (byCommon) return byCommon.key;
1707
+ const fallbacks = BAND_KEY_FALLBACKS[slot] ?? [];
1708
+ const byKey = assets.find((a) => fallbacks.includes(a.key));
1709
+ return byKey?.key;
1710
+ }
1711
+ function resolvePresetComposite(assets, composite) {
1712
+ const r = resolveBandSlotAssetKey(assets, composite.r);
1713
+ const g = resolveBandSlotAssetKey(assets, composite.g);
1714
+ const b = resolveBandSlotAssetKey(assets, composite.b);
1715
+ if (!r || !g || !b) return null;
1716
+ return { r, g, b };
1717
+ }
1718
+ function hasCompositableBands(assets) {
1719
+ return assets.length >= 3;
1720
+ }
1721
+ function extractMosaicAssets(item) {
1722
+ const out = [];
1723
+ const assets = item.assets ?? {};
1724
+ for (const [key, asset] of Object.entries(assets)) {
1725
+ if (!asset?.href) continue;
1726
+ const mediaType = typeof asset.type === "string" ? asset.type : void 0;
1727
+ if (mediaType && !/^image\/(tiff|geotiff)\b/i.test(mediaType)) continue;
1728
+ const roles = Array.isArray(asset.roles) ? asset.roles : void 0;
1729
+ if (roles && (roles.includes("thumbnail") || roles.includes("overview") || roles.includes("metadata"))) {
1730
+ continue;
1731
+ }
1732
+ const eoBands = Array.isArray(asset["eo:bands"]) ? asset["eo:bands"] : void 0;
1733
+ const assetExt = asset;
1734
+ const rasterBands = Array.isArray(assetExt["raster:bands"]) ? assetExt["raster:bands"] : void 0;
1735
+ const bandCount = rasterBands?.length ?? eoBands?.length;
1736
+ const commonName = eoBands?.[0]?.common_name?.toLowerCase();
1737
+ out.push({
1738
+ key,
1739
+ href: asset.href,
1740
+ commonName,
1741
+ bandCount: typeof bandCount === "number" ? bandCount : void 0,
1742
+ roles,
1743
+ mediaType,
1744
+ title: typeof asset.title === "string" ? asset.title : void 0
1745
+ });
1746
+ }
1747
+ return out;
1748
+ }
1749
+
1750
+ // src/channel-composite.ts
1751
+ var PRESETS = [
1752
+ {
1753
+ id: "natural-color",
1754
+ labelKey: "map.multiCogPreset.trueColor",
1755
+ slots: { r: "red", g: "green", b: "blue" }
1756
+ },
1757
+ {
1758
+ id: "false-color-ir",
1759
+ labelKey: "map.multiCogPreset.falseColorIR",
1760
+ slots: { r: "nir", g: "red", b: "green" }
1761
+ },
1762
+ {
1763
+ id: "swir",
1764
+ labelKey: "map.multiCogPreset.swir",
1765
+ slots: { r: "swir2", g: "swir1", b: "red" }
1766
+ },
1767
+ {
1768
+ id: "vegetation",
1769
+ labelKey: "map.multiCogPreset.vegetation",
1770
+ slots: { r: "nir", g: "swir1", b: "red" }
1771
+ },
1772
+ {
1773
+ id: "agriculture",
1774
+ labelKey: "map.multiCogPreset.agriculture",
1775
+ slots: { r: "swir1", g: "nir", b: "blue" }
1776
+ }
1777
+ ];
1778
+ function toRasterBandAssets(assets) {
1779
+ return assets.map((a) => ({
1780
+ key: a.key,
1781
+ href: a.href,
1782
+ commonName: a.eoCommon[0],
1783
+ bandCount: a.bandCount,
1784
+ roles: a.roles,
1785
+ mediaType: a.mediaType,
1786
+ title: a.title
1787
+ }));
1788
+ }
1789
+ function availablePresets(assets) {
1790
+ const rba = toRasterBandAssets(assets);
1791
+ return PRESETS.filter((p) => resolvePresetComposite(rba, p.slots) !== null);
1792
+ }
1793
+ function applyPreset(assets, preset) {
1794
+ const rba = toRasterBandAssets(assets);
1795
+ const r = resolvePresetComposite(rba, preset.slots);
1796
+ if (!r) return null;
1797
+ return {
1798
+ r: { assetKey: r.r, bandIndex: 0 },
1799
+ g: { assetKey: r.g, bandIndex: 0 },
1800
+ b: { assetKey: r.b, bandIndex: 0 }
1801
+ };
1802
+ }
1803
+ function presetMatchesComposite(preset, c, assets) {
1804
+ const resolved = applyPreset(assets, preset);
1805
+ if (!resolved) return false;
1806
+ return resolved.r.assetKey === c.r.assetKey && resolved.g.assetKey === c.g.assetKey && resolved.b.assetKey === c.b.assetKey && c.r.bandIndex === 0 && c.g.bandIndex === 0 && c.b.bandIndex === 0;
1807
+ }
1808
+ function compositeFromUrl(params, assets) {
1809
+ const r = params.get("r");
1810
+ const g = params.get("g");
1811
+ const b = params.get("b");
1812
+ if (!r || !g || !b) return null;
1813
+ const known = new Map(assets.map((a2) => [a2.key, a2]));
1814
+ const ra = known.get(r);
1815
+ const ga = known.get(g);
1816
+ const ba = known.get(b);
1817
+ if (!ra || !ga || !ba) return null;
1818
+ const out = {
1819
+ r: { assetKey: r, bandIndex: clampBand(params.get("band_r"), ra.bandCount) },
1820
+ g: { assetKey: g, bandIndex: clampBand(params.get("band_g"), ga.bandCount) },
1821
+ b: { assetKey: b, bandIndex: clampBand(params.get("band_b"), ba.bandCount) }
1822
+ };
1823
+ const a = params.get("a");
1824
+ if (a) {
1825
+ const aa = known.get(a);
1826
+ if (aa) out.a = { assetKey: a, bandIndex: clampBand(params.get("band_a"), aa.bandCount) };
1827
+ }
1828
+ return out;
1829
+ }
1830
+ function compositeToUrl(c, presetId) {
1831
+ const p = new URLSearchParams();
1832
+ p.set("r", c.r.assetKey);
1833
+ p.set("g", c.g.assetKey);
1834
+ p.set("b", c.b.assetKey);
1835
+ if (c.r.bandIndex !== 0) p.set("band_r", String(c.r.bandIndex));
1836
+ if (c.g.bandIndex !== 0) p.set("band_g", String(c.g.bandIndex));
1837
+ if (c.b.bandIndex !== 0) p.set("band_b", String(c.b.bandIndex));
1838
+ if (c.a) {
1839
+ p.set("a", c.a.assetKey);
1840
+ if (c.a.bandIndex !== 0) p.set("band_a", String(c.a.bandIndex));
1841
+ }
1842
+ if (presetId) p.set("preset", presetId);
1843
+ return p;
1844
+ }
1845
+ function clampBand(raw, bandCount) {
1846
+ if (!raw) return 0;
1847
+ const n = Number(raw);
1848
+ if (!Number.isFinite(n)) return 0;
1849
+ const i = Math.floor(n);
1850
+ if (i < 0) return 0;
1851
+ if (i >= bandCount) return Math.max(0, bandCount - 1);
1852
+ return i;
1853
+ }
1854
+
1855
+ // src/clipboard.ts
1856
+ async function copyToClipboard(text, onFeedback) {
1857
+ try {
1858
+ await navigator.clipboard.writeText(text);
1859
+ onFeedback?.(true);
1860
+ setTimeout(() => onFeedback?.(false), COPY_FEEDBACK_MS);
1861
+ return true;
1862
+ } catch {
1863
+ return false;
1864
+ }
1865
+ }
1866
+ function wireCodeCopyButtons(root, selector) {
1867
+ for (const btn of root.querySelectorAll(selector)) {
1868
+ btn.addEventListener("click", async () => {
1869
+ const code = decodeURIComponent(btn.dataset.code ?? "");
1870
+ try {
1871
+ await navigator.clipboard.writeText(code);
1872
+ btn.classList.add("copied");
1873
+ setTimeout(() => btn.classList.remove("copied"), COPY_FEEDBACK_MS);
1874
+ } catch {
1875
+ }
1876
+ });
1877
+ }
1878
+ }
1879
+
1880
+ // src/cloud-url.ts
1433
1881
  var AWS_REGION_RE = /^(us|eu|ap|sa|ca|me|af|il)-(north|south|east|west|central|northeast|southeast|northwest|southwest)-\d+/;
1434
1882
  function getNativeScheme(provider) {
1435
1883
  const def = PROVIDERS[provider];
@@ -1444,7 +1892,7 @@ function safeDecodeURIComponent(s) {
1444
1892
  }
1445
1893
  }
1446
1894
  function resolveCloudUrl(url) {
1447
- const s3Match = url.match(/^s3[an]?:\/\/([^/]+)\/?(.*)$/);
1895
+ const s3Match = url.match(/^s3[an]?:\/\/([^/]+)(?:\/(.*))?$/);
1448
1896
  if (s3Match) {
1449
1897
  const [, bucket, key] = s3Match;
1450
1898
  const regionMatch = bucket.match(AWS_REGION_RE);
@@ -1452,7 +1900,7 @@ function resolveCloudUrl(url) {
1452
1900
  const base = buildProviderBaseUrl("s3", "", bucket, region);
1453
1901
  return key ? `${base}/${key}` : base;
1454
1902
  }
1455
- const gcsMatch = url.match(/^gcs?:\/\/([^/]+)\/?(.*)$/);
1903
+ const gcsMatch = url.match(/^gcs?:\/\/([^/]+)(?:\/(.*))?$/);
1456
1904
  if (gcsMatch) {
1457
1905
  const [, bucket, key] = gcsMatch;
1458
1906
  const base = buildProviderBaseUrl("gcs", "", bucket, "");
@@ -1460,6 +1908,122 @@ function resolveCloudUrl(url) {
1460
1908
  }
1461
1909
  return url;
1462
1910
  }
1911
+
1912
+ // src/cog-asset.ts
1913
+ var TIFF_MEDIA = /^image\/(tiff|geotiff)\b/i;
1914
+ var NON_DATA_ROLES = /* @__PURE__ */ new Set(["thumbnail", "overview", "metadata"]);
1915
+ function extractCogAssets(item) {
1916
+ const out = [];
1917
+ const assets = item.assets ?? {};
1918
+ const props = item.properties ?? {};
1919
+ const itemBands = Array.isArray(props.bands) ? props.bands : void 0;
1920
+ for (const [key, asset] of Object.entries(assets)) {
1921
+ if (!asset?.href) continue;
1922
+ const mediaType = typeof asset.type === "string" ? asset.type : void 0;
1923
+ if (mediaType && !TIFF_MEDIA.test(mediaType)) continue;
1924
+ const roles = Array.isArray(asset.roles) ? asset.roles : [];
1925
+ if (roles.some((r) => NON_DATA_ROLES.has(r))) continue;
1926
+ const eoBands = Array.isArray(asset["eo:bands"]) ? asset["eo:bands"] : void 0;
1927
+ const assetExt = asset;
1928
+ const rasterBands = Array.isArray(assetExt["raster:bands"]) ? assetExt["raster:bands"] : void 0;
1929
+ const assetBands11 = Array.isArray(assetExt.bands) ? assetExt.bands : void 0;
1930
+ const bandCount = assetBands11?.length ?? rasterBands?.length ?? eoBands?.length ?? itemBands?.length;
1931
+ const bandCountKnown = typeof bandCount === "number" && bandCount > 0;
1932
+ const commonSource = eoBands ?? assetBands11 ?? itemBands;
1933
+ const eoCommon = commonSource ? commonSource.map((b) => {
1934
+ const c = b?.common_name;
1935
+ return typeof c === "string" ? c.toLowerCase() : "";
1936
+ }) : [];
1937
+ const dtypeSource = rasterBands ?? assetBands11 ?? itemBands;
1938
+ const dtype = dtypeSource?.[0]?.data_type;
1939
+ out.push({
1940
+ key,
1941
+ href: asset.href,
1942
+ bandCount: bandCountKnown ? bandCount : 1,
1943
+ bandCountKnown,
1944
+ dtype: typeof dtype === "string" ? dtype : void 0,
1945
+ eoCommon,
1946
+ roles,
1947
+ title: typeof asset.title === "string" ? asset.title : void 0,
1948
+ mediaType
1949
+ });
1950
+ }
1951
+ return out;
1952
+ }
1953
+ function syntheticSelfAsset(href, probedBandCount) {
1954
+ const known = typeof probedBandCount === "number" && probedBandCount > 0;
1955
+ return {
1956
+ key: "self",
1957
+ href,
1958
+ bandCount: known ? probedBandCount : 1,
1959
+ bandCountKnown: known,
1960
+ eoCommon: [],
1961
+ roles: []
1962
+ };
1963
+ }
1964
+ function pickNaturalColorComposite(assets) {
1965
+ if (assets.length === 0) return null;
1966
+ for (const a of assets) {
1967
+ if (a.bandCount === 3 && (a.roles.includes("visual") || hasRgbInEoCommon(a.eoCommon))) {
1968
+ return {
1969
+ composite: {
1970
+ r: { assetKey: a.key, bandIndex: indexOfCommon(a.eoCommon, "red", 0) },
1971
+ g: { assetKey: a.key, bandIndex: indexOfCommon(a.eoCommon, "green", 1) },
1972
+ b: { assetKey: a.key, bandIndex: indexOfCommon(a.eoCommon, "blue", 2) }
1973
+ },
1974
+ source: "visual-asset"
1975
+ };
1976
+ }
1977
+ }
1978
+ const red = assets.find((a) => a.eoCommon[0] === "red");
1979
+ const green = assets.find((a) => a.eoCommon[0] === "green");
1980
+ const blue = assets.find((a) => a.eoCommon[0] === "blue");
1981
+ if (red && green && blue) {
1982
+ return {
1983
+ composite: {
1984
+ r: { assetKey: red.key, bandIndex: 0 },
1985
+ g: { assetKey: green.key, bandIndex: 0 },
1986
+ b: { assetKey: blue.key, bandIndex: 0 }
1987
+ },
1988
+ source: "rgb-bands"
1989
+ };
1990
+ }
1991
+ if (assets.length >= 3) {
1992
+ return {
1993
+ composite: {
1994
+ r: { assetKey: assets[0].key, bandIndex: 0 },
1995
+ g: { assetKey: assets[1].key, bandIndex: 0 },
1996
+ b: { assetKey: assets[2].key, bandIndex: 0 }
1997
+ },
1998
+ source: "fallback"
1999
+ };
2000
+ }
2001
+ const only = assets[0];
2002
+ const last = Math.max(0, only.bandCount - 1);
2003
+ return {
2004
+ composite: {
2005
+ r: { assetKey: only.key, bandIndex: 0 },
2006
+ g: { assetKey: only.key, bandIndex: Math.min(1, last) },
2007
+ b: { assetKey: only.key, bandIndex: Math.min(2, last) }
2008
+ },
2009
+ source: "fallback"
2010
+ };
2011
+ }
2012
+ function hasRgbInEoCommon(eo) {
2013
+ return eo.includes("red") && eo.includes("green") && eo.includes("blue");
2014
+ }
2015
+ function indexOfCommon(eo, name, fallback) {
2016
+ const i = eo.indexOf(name);
2017
+ return i >= 0 ? i : fallback;
2018
+ }
2019
+ function isSingleAssetComposite(c) {
2020
+ return c.r.assetKey === c.g.assetKey && c.g.assetKey === c.b.assetKey;
2021
+ }
2022
+ function allChannelsBand0(c) {
2023
+ return c.r.bandIndex === 0 && c.g.bandIndex === 0 && c.b.bandIndex === 0;
2024
+ }
2025
+
2026
+ // src/cog-info.ts
1463
2027
  var SF_LABELS = {
1464
2028
  1: "uint",
1465
2029
  2: "int",
@@ -1483,7 +2047,7 @@ function buildDataTypeLabel(sampleFormat, bitsPerSample) {
1483
2047
  return `${SF_LABELS[sampleFormat] ?? `sf${sampleFormat}`}${bitsPerSample ?? ""}`;
1484
2048
  }
1485
2049
 
1486
- // ../../src/lib/utils/column-types.ts
2050
+ // src/column-types.ts
1487
2051
  var NUMBER_TYPES = [
1488
2052
  "TINYINT",
1489
2053
  "SMALLINT",
@@ -1537,7 +2101,9 @@ var BINARY_TYPES = ["BLOB", "BYTEA", "BINARY", "VARBINARY"];
1537
2101
  var JSON_TYPES = ["JSON", "JSONB"];
1538
2102
  function classifyType(duckdbType) {
1539
2103
  const upper = duckdbType.toUpperCase().trim();
1540
- const base = upper.replace(/\(.*\)/, "").trim();
2104
+ const openIdx = upper.indexOf("(");
2105
+ const closeIdx = upper.lastIndexOf(")");
2106
+ const base = (openIdx >= 0 && closeIdx > openIdx ? upper.slice(0, openIdx) + upper.slice(closeIdx + 1) : upper).trim();
1541
2107
  if (NUMBER_TYPES.includes(base)) return "number";
1542
2108
  if (STRING_TYPES.includes(base)) return "string";
1543
2109
  if (DATE_TYPES.includes(base)) return "date";
@@ -1599,13 +2165,84 @@ function typeLabel(category) {
1599
2165
  return TYPE_LABELS[category];
1600
2166
  }
1601
2167
 
1602
- // ../../src/lib/utils/error.ts
2168
+ // src/connection-identity.ts
2169
+ var DEFAULT_PORTS = {
2170
+ "https:": "443",
2171
+ "http:": "80"
2172
+ };
2173
+ var SLASH = 47;
2174
+ function stripTrailingSlashes(s) {
2175
+ let end = s.length;
2176
+ while (end > 0 && s.charCodeAt(end - 1) === SLASH) end--;
2177
+ return s.slice(0, end);
2178
+ }
2179
+ function stripEdgeSlashes(s) {
2180
+ let start = 0;
2181
+ let end = s.length;
2182
+ while (start < end && s.charCodeAt(start) === SLASH) start++;
2183
+ while (end > start && s.charCodeAt(end - 1) === SLASH) end--;
2184
+ return s.slice(start, end);
2185
+ }
2186
+ function normalizeEndpoint(raw) {
2187
+ if (!raw) return "";
2188
+ const trimmed = raw.trim();
2189
+ if (!trimmed) return "";
2190
+ try {
2191
+ const url = new URL(trimmed);
2192
+ const scheme = url.protocol.toLowerCase();
2193
+ const host = url.hostname.toLowerCase();
2194
+ const defaultPort = DEFAULT_PORTS[scheme] ?? "";
2195
+ const port = url.port && url.port !== defaultPort ? `:${url.port}` : "";
2196
+ const path = stripTrailingSlashes(url.pathname);
2197
+ return `${scheme}//${host}${port}${path}`;
2198
+ } catch {
2199
+ return stripTrailingSlashes(trimmed.toLowerCase());
2200
+ }
2201
+ }
2202
+ function normalizeProvider(provider) {
2203
+ if (!provider) return "s3";
2204
+ const p = provider.trim().toLowerCase();
2205
+ if (!p || p === "unknown") return "s3";
2206
+ return p;
2207
+ }
2208
+ function normalizeBucket(bucket) {
2209
+ return stripEdgeSlashes((bucket ?? "").trim());
2210
+ }
2211
+ function normalizeRegion(region) {
2212
+ return (region ?? "").trim().toLowerCase();
2213
+ }
2214
+ function connectionIdentityKey(input) {
2215
+ const provider = normalizeProvider(input.provider);
2216
+ const bucket = normalizeBucket(input.bucket);
2217
+ if (!bucket) return "";
2218
+ const endpoint = normalizeEndpoint(input.endpoint);
2219
+ const region = normalizeRegion(input.region);
2220
+ if (provider === "azure") return `azure|${endpoint}|${bucket}`;
2221
+ if (provider === "gcs") return `gcs|${bucket}`;
2222
+ if (provider === "s3" && !endpoint) return `s3|${bucket}|${region}`;
2223
+ return `${provider}|${endpoint}|${bucket}`;
2224
+ }
2225
+ function isSameConnectionIdentity(a, b) {
2226
+ const key = connectionIdentityKey(a);
2227
+ return key !== "" && key === connectionIdentityKey(b);
2228
+ }
2229
+
2230
+ // src/error.ts
2231
+ function isAbortError(err) {
2232
+ if (!err) return false;
2233
+ if (err instanceof DOMException && err.name === "AbortError") return true;
2234
+ const e = err;
2235
+ if (e.name === "AbortError") return true;
2236
+ if (typeof e.message === "string" && /\baborted?\b/i.test(e.message)) return true;
2237
+ if (e.cause && isAbortError(e.cause)) return true;
2238
+ return false;
2239
+ }
1603
2240
  function handleLoadError(err) {
1604
- if (err instanceof DOMException && err.name === "AbortError") return null;
2241
+ if (isAbortError(err)) return null;
1605
2242
  return err instanceof Error ? err.message : String(err);
1606
2243
  }
1607
2244
 
1608
- // ../../src/lib/utils/format.ts
2245
+ // src/format.ts
1609
2246
  function formatFileSize(bytes) {
1610
2247
  if (bytes < 0) return "0 B";
1611
2248
  if (bytes === 0) return "0 B";
@@ -1651,7 +2288,7 @@ function formatValue(value) {
1651
2288
  return String(value);
1652
2289
  }
1653
2290
 
1654
- // ../../src/lib/utils/export.ts
2291
+ // src/export.ts
1655
2292
  function triggerDownload(content, filename, mimeType) {
1656
2293
  const blob = new Blob([content], { type: mimeType });
1657
2294
  const url = URL.createObjectURL(blob);
@@ -1712,7 +2349,7 @@ function exportToJson(columns, rows, filename) {
1712
2349
  );
1713
2350
  }
1714
2351
 
1715
- // ../../src/lib/utils/file-sort.ts
2352
+ // src/file-sort.ts
1716
2353
  function sortFileEntries(entries, config) {
1717
2354
  const sorted = [...entries];
1718
2355
  const dir = config.direction === "asc" ? 1 : -1;
@@ -2317,7 +2954,42 @@ function buildGeoArrowTables(wkbArrays, attributes, knownGeomType) {
2317
2954
  return results;
2318
2955
  }
2319
2956
 
2320
- // ../../src/lib/utils/hex.ts
2957
+ // src/geometry-type.ts
2958
+ var GEOMETRY_PREFIX = /^GEOMETRY(\s*\(\s*'?([^')]+)'?\s*\))?/i;
2959
+ function parseGeometryTypeCrs(typeStr) {
2960
+ if (!typeStr) return { isGeometry: false, hasCrs: false, rawCrs: null, nonWgs84Crs: null };
2961
+ const match = typeStr.match(GEOMETRY_PREFIX);
2962
+ if (!match) return { isGeometry: false, hasCrs: false, rawCrs: null, nonWgs84Crs: null };
2963
+ const rawCrs = match[2]?.trim() ?? null;
2964
+ if (!rawCrs) return { isGeometry: true, hasCrs: false, rawCrs: null, nonWgs84Crs: null };
2965
+ return {
2966
+ isGeometry: true,
2967
+ hasCrs: true,
2968
+ rawCrs,
2969
+ nonWgs84Crs: isWgs84Crs(rawCrs) ? null : rawCrs
2970
+ };
2971
+ }
2972
+ function isWgs84Crs(crs) {
2973
+ if (!crs) return true;
2974
+ const trimmed = crs.trim();
2975
+ if (trimmed === "OGC:CRS84" || trimmed === "OGC:CRS83") return true;
2976
+ const epsgMatch = trimmed.match(/^EPSG:(\d+)$/i);
2977
+ if (epsgMatch && WGS84_CODES.has(Number(epsgMatch[1]))) return true;
2978
+ return false;
2979
+ }
2980
+ function buildTransformExpr(innerExpr, sourceType, sourceCrs, targetCrs) {
2981
+ const info = parseGeometryTypeCrs(sourceType);
2982
+ if (info.hasCrs) {
2983
+ return `ST_Transform(${innerExpr}, '${targetCrs}')`;
2984
+ }
2985
+ return `ST_Transform(${innerExpr}, '${sourceCrs}', '${targetCrs}')`;
2986
+ }
2987
+ function wrapWkbWithCrs(wkbExpr, sourceCrs) {
2988
+ if (!sourceCrs) return `ST_GeomFromWKB(${wkbExpr})`;
2989
+ return `ST_SetCRS(ST_GeomFromWKB(${wkbExpr}), '${sourceCrs}')`;
2990
+ }
2991
+
2992
+ // src/hex.ts
2321
2993
  function generateHexDump(data, bytesPerRow = 16) {
2322
2994
  const rows = [];
2323
2995
  for (let i = 0; i < data.length; i += bytesPerRow) {
@@ -2341,458 +3013,233 @@ function generateHexDump(data, bytesPerRow = 16) {
2341
3013
  return rows;
2342
3014
  }
2343
3015
 
2344
- // ../../src/lib/utils/local-storage.ts
2345
- function loadFromStorage(key, defaultValue) {
2346
- if (typeof window === "undefined") return defaultValue;
2347
- try {
2348
- const raw = localStorage.getItem(key);
2349
- if (raw) return JSON.parse(raw);
2350
- } catch {
2351
- }
2352
- return defaultValue;
3016
+ // src/stac-storage-extension.ts
3017
+ function emptyStorageHints() {
3018
+ return { platform: null, region: null, requesterPays: false, endpoint: null };
2353
3019
  }
2354
- function persistToStorage(key, value) {
2355
- if (typeof window === "undefined") return;
2356
- try {
2357
- localStorage.setItem(key, JSON.stringify(value));
2358
- } catch {
3020
+ function detectStorageExtensionVersion(item) {
3021
+ const exts = item.stac_extensions;
3022
+ if (!Array.isArray(exts)) return null;
3023
+ for (const raw of exts) {
3024
+ if (typeof raw !== "string") continue;
3025
+ if (!raw.includes("stac-extensions.github.io/storage")) continue;
3026
+ const trimmed = raw.endsWith("/schema.json") ? raw.slice(0, -"/schema.json".length) : raw;
3027
+ const last = trimmed.slice(trimmed.lastIndexOf("/") + 1);
3028
+ const version = last.startsWith("v") ? last.slice(1) : last;
3029
+ if (version === "1.0.0" || version === "2.0.0") return version;
3030
+ const major = version.split(".")[0];
3031
+ if (major === "1") return "1.0.0";
3032
+ if (major === "2") return "2.0.0";
2359
3033
  }
3034
+ return null;
2360
3035
  }
2361
-
2362
- // ../../src/lib/utils/markdown-sql.ts
2363
- async function parseMarkdownDocument(markdown) {
2364
- let frontmatter = {};
2365
- let content = markdown;
2366
- const fmMatch = markdown.match(/^---\n([\s\S]*?)\n---\n/);
2367
- if (fmMatch) {
2368
- try {
2369
- const { default: YAML } = await import('yaml');
2370
- frontmatter = YAML.parse(fmMatch[1]) || {};
2371
- } catch {
3036
+ function extractStorageHints(item, assetKey) {
3037
+ const version = detectStorageExtensionVersion(item);
3038
+ if (version === "1.0.0") return extractV1Hints(item, assetKey);
3039
+ if (version === "2.0.0") return extractV2Hints(item, assetKey);
3040
+ return emptyStorageHints();
3041
+ }
3042
+ function extractV1Hints(item, assetKey) {
3043
+ const props = item.properties ?? {};
3044
+ const asset = assetKey && item.assets && item.assets[assetKey] ? item.assets[assetKey] : null;
3045
+ const platformRaw = pickFirstString([asset?.["storage:platform"], props["storage:platform"]]);
3046
+ const region = pickFirstString([asset?.["storage:region"], props["storage:region"]]);
3047
+ const requesterPays = pickFirstBoolean([
3048
+ asset?.["storage:requester_pays"],
3049
+ props["storage:requester_pays"]
3050
+ ]);
3051
+ return {
3052
+ platform: platformRaw ? platformRaw.toUpperCase() : null,
3053
+ region: region ?? null,
3054
+ requesterPays: requesterPays === true,
3055
+ endpoint: null
3056
+ };
3057
+ }
3058
+ function extractV2Hints(item, assetKey) {
3059
+ const props = item.properties ?? {};
3060
+ const schemes = props["storage:schemes"] ?? {};
3061
+ if (!schemes || typeof schemes !== "object") return emptyStorageHints();
3062
+ const refs = collectAssetRefs(item, assetKey);
3063
+ const scheme = pickFirstScheme(schemes, refs);
3064
+ if (!scheme) return emptyStorageHints();
3065
+ const storeType = typeof scheme.type === "string" ? scheme.type : "";
3066
+ const platform = typeof scheme.platform === "string" ? scheme.platform : null;
3067
+ const region = typeof scheme.region === "string" ? scheme.region : null;
3068
+ const requesterPays = scheme.requester_pays === true;
3069
+ let endpoint = null;
3070
+ if (storeType === "custom-s3") {
3071
+ if (typeof scheme.endpoint === "string" && scheme.endpoint && !scheme.endpoint.includes("{")) {
3072
+ endpoint = scheme.endpoint;
3073
+ } else if (platform && !platform.includes("{")) {
3074
+ endpoint = platform;
2372
3075
  }
2373
- content = markdown.slice(fmMatch[0].length);
2374
3076
  }
2375
- const sqlBlocks = [];
2376
- const lines = content.split("\n");
2377
- let i = 0;
2378
- while (i < lines.length) {
2379
- const line = lines[i];
2380
- const match = line.match(/^```sql\s+(\w[\w-]*)\s*$/);
2381
- if (match) {
2382
- const name = match[1];
2383
- const startLine = i;
2384
- const sqlLines = [];
2385
- i++;
2386
- while (i < lines.length && lines[i] !== "```") {
2387
- sqlLines.push(lines[i]);
2388
- i++;
2389
- }
2390
- sqlBlocks.push({
2391
- name,
2392
- sql: sqlLines.join("\n"),
2393
- startLine,
2394
- endLine: i
2395
- });
2396
- }
2397
- i++;
3077
+ return {
3078
+ platform: platform ? platform.toUpperCase() : null,
3079
+ region,
3080
+ requesterPays,
3081
+ endpoint
3082
+ };
3083
+ }
3084
+ function collectAssetRefs(item, assetKey) {
3085
+ const assets = item.assets ?? {};
3086
+ if (assetKey) {
3087
+ const a = assets[assetKey];
3088
+ const refs = a?.["storage:refs"];
3089
+ if (Array.isArray(refs)) return refs.filter((r) => typeof r === "string");
3090
+ return [];
2398
3091
  }
2399
- return { frontmatter, content, sqlBlocks };
3092
+ const out = [];
3093
+ for (const a of Object.values(assets)) {
3094
+ if (!a || typeof a !== "object") continue;
3095
+ const refs = a["storage:refs"];
3096
+ if (!Array.isArray(refs)) continue;
3097
+ for (const r of refs) if (typeof r === "string") out.push(r);
3098
+ }
3099
+ return out;
2400
3100
  }
2401
- function interpolateTemplates(text, queryResults) {
2402
- return text.replace(/\{(\w+)\.rows\[(\d+)\]\.(\w+)\}/g, (match, queryName, rowIdx, colName) => {
2403
- const rows = queryResults.get(queryName);
2404
- if (!rows) return match;
2405
- const row = rows[parseInt(rowIdx, 10)];
2406
- if (!row) return match;
2407
- const value = row[colName];
2408
- return value !== void 0 ? String(value) : match;
2409
- });
3101
+ function pickFirstScheme(schemes, refs) {
3102
+ for (const r of refs) {
3103
+ const s = schemes[r];
3104
+ if (s && typeof s === "object") return s;
3105
+ }
3106
+ return null;
2410
3107
  }
2411
- function markSqlBlocks(content) {
2412
- return content.replace(
2413
- /```sql\s+(\w[\w-]*)\s*\n([\s\S]*?)```/g,
2414
- (_, name) => `<div data-sql-block="${name}"></div>`
2415
- );
3108
+ function pickFirstString(candidates) {
3109
+ for (const c of candidates) {
3110
+ if (typeof c === "string" && c.length > 0) return c;
3111
+ }
3112
+ return null;
3113
+ }
3114
+ function pickFirstBoolean(candidates) {
3115
+ for (const c of candidates) {
3116
+ if (typeof c === "boolean") return c;
3117
+ }
3118
+ return null;
3119
+ }
3120
+ function applyStorageHintsToConnection(conn, hints) {
3121
+ const out = { ...conn };
3122
+ if (hints.region && !out.region) {
3123
+ out.region = hints.region;
3124
+ }
3125
+ if (hints.endpoint && !out.endpoint) {
3126
+ out.endpoint = hints.endpoint;
3127
+ }
3128
+ return out;
2416
3129
  }
2417
3130
 
2418
- // ../../src/lib/utils/parquet-metadata.ts
2419
- function mapParquetType(col) {
2420
- const lt = col.logical_type;
2421
- if (lt) {
2422
- if (lt.type === "GEOMETRY" || lt.type === "GEOGRAPHY") return "GEOMETRY";
2423
- if (lt.type === "STRING" || lt.type === "UTF8") return "VARCHAR";
2424
- if (lt.type === "JSON") return "JSON";
2425
- if (lt.type === "UUID") return "UUID";
2426
- if (lt.type === "ENUM") return "VARCHAR";
2427
- if (lt.type === "INT" || lt.type === "INTEGER") {
2428
- const bits = lt.bitWidth ?? 32;
2429
- const signed = lt.isSigned !== false;
2430
- if (bits <= 8) return signed ? "TINYINT" : "UTINYINT";
2431
- if (bits <= 16) return signed ? "SMALLINT" : "USMALLINT";
2432
- if (bits <= 32) return signed ? "INTEGER" : "UINTEGER";
2433
- return signed ? "BIGINT" : "UBIGINT";
3131
+ // src/storage-url.ts
3132
+ function buildSchemeMap() {
3133
+ const map = {};
3134
+ for (const [id, def] of Object.entries(PROVIDERS)) {
3135
+ for (const scheme of def.schemes) {
3136
+ const key = `${scheme}://`;
3137
+ map[key] = { provider: id, strip: key.length };
2434
3138
  }
2435
- if (lt.type === "DECIMAL") return `DECIMAL(${lt.precision ?? 18},${lt.scale ?? 0})`;
2436
- if (lt.type === "DATE") return "DATE";
2437
- if (lt.type === "TIME") return "TIME";
2438
- if (lt.type === "TIMESTAMP") return "TIMESTAMP";
2439
- if (lt.type === "BSON") return "BLOB";
2440
3139
  }
2441
- const ct = col.converted_type;
2442
- if (ct === "UTF8") return "VARCHAR";
2443
- if (ct === "JSON") return "JSON";
2444
- if (ct === "DATE") return "DATE";
2445
- if (ct === "TIMESTAMP_MILLIS" || ct === "TIMESTAMP_MICROS") return "TIMESTAMP";
2446
- if (ct === "DECIMAL") return `DECIMAL(${col.precision ?? 18},${col.scale ?? 0})`;
2447
- if (ct === "INT_8") return "TINYINT";
2448
- if (ct === "INT_16") return "SMALLINT";
2449
- if (ct === "INT_32") return "INTEGER";
2450
- if (ct === "INT_64") return "BIGINT";
2451
- if (ct === "UINT_8") return "UTINYINT";
2452
- if (ct === "UINT_16") return "USMALLINT";
2453
- if (ct === "UINT_32") return "UINTEGER";
2454
- if (ct === "UINT_64") return "UBIGINT";
2455
- const pt = col.type;
2456
- if (pt === "BOOLEAN") return "BOOLEAN";
2457
- if (pt === "INT32") return "INTEGER";
2458
- if (pt === "INT64") return "BIGINT";
2459
- if (pt === "INT96") return "TIMESTAMP";
2460
- if (pt === "FLOAT") return "FLOAT";
2461
- if (pt === "DOUBLE") return "DOUBLE";
2462
- if (pt === "BYTE_ARRAY") return "BLOB";
2463
- if (pt === "FIXED_LEN_BYTE_ARRAY") return "BLOB";
2464
- return "VARCHAR";
2465
- }
2466
- async function readParquetMetadata(url) {
2467
- const { parquetMetadataAsync, asyncBufferFromUrl } = await import('hyparquet');
2468
- const file = await asyncBufferFromUrl({ url });
2469
- const metadata = await parquetMetadataAsync(file);
2470
- const rowCount = metadata.row_groups.reduce(
2471
- (sum, rg) => sum + Number(rg.num_rows),
2472
- 0
2473
- );
2474
- const schema = metadata.schema.slice(1).filter((col) => col.num_children === void 0).map((col) => ({
2475
- name: col.name,
2476
- type: mapParquetType(col)
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
- }
2487
- let geo = null;
2488
- let legacyGeoParquet = false;
2489
- const geoKv = metadata.key_value_metadata?.find((kv) => kv.key === "geo");
2490
- if (geoKv) {
2491
- try {
2492
- const geoJson = JSON.parse(geoKv.value ?? "");
2493
- if (geoJson.schema_version && !geoJson.version) {
2494
- legacyGeoParquet = true;
2495
- }
2496
- geo = {
2497
- primaryColumn: geoJson.primary_column ?? "geometry",
2498
- columns: {}
2499
- };
2500
- if (geoJson.columns) {
2501
- for (const [colName, colMeta] of Object.entries(geoJson.columns)) {
2502
- geo.columns[colName] = {
2503
- encoding: colMeta.encoding ?? "WKB",
2504
- geometryTypes: colMeta.geometry_types ?? [],
2505
- crs: colMeta.crs ?? null,
2506
- bbox: colMeta.bbox
2507
- };
2508
- }
2509
- }
2510
- } catch {
2511
- }
2512
- }
2513
- const createdBy = metadata.created_by ?? null;
2514
- const numRowGroups = metadata.row_groups.length;
2515
- let compression = null;
2516
- if (numRowGroups > 0 && metadata.row_groups[0].columns) {
2517
- const codecs = /* @__PURE__ */ new Set();
2518
- for (const col of metadata.row_groups[0].columns) {
2519
- const codec = col.meta_data?.codec;
2520
- if (codec) codecs.add(codec);
2521
- }
2522
- if (codecs.size === 1) {
2523
- compression = [...codecs][0];
2524
- } else if (codecs.size > 1) {
2525
- compression = [...codecs].join(", ");
2526
- }
2527
- }
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;
3140
+ map["swift://"] = { provider: "unknown", strip: 8 };
3141
+ return map;
2548
3142
  }
2549
- function extractEpsgFromGeoMeta(geo) {
2550
- const primaryCol = geo.columns[geo.primaryColumn];
2551
- if (!primaryCol?.crs) return null;
2552
- const crs = primaryCol.crs;
2553
- if (crs.type === "name" && crs.properties?.name?.includes("CRS84")) return null;
2554
- if (crs.id?.authority === "EPSG") {
2555
- const code = crs.id.code;
2556
- if (WGS84_CODES.has(code)) return null;
2557
- return `EPSG:${code}`;
2558
- }
2559
- return null;
3143
+ var SCHEME_MAP = buildSchemeMap();
3144
+ var SLASH2 = 47;
3145
+ function stripTrailingSlashes2(s) {
3146
+ let end = s.length;
3147
+ while (end > 0 && s.charCodeAt(end - 1) === SLASH2) end--;
3148
+ return s.slice(0, end);
2560
3149
  }
2561
- function extractGeometryTypes(geo) {
2562
- const primaryCol = geo.columns[geo.primaryColumn];
2563
- if (!primaryCol?.geometryTypes?.length) return [];
2564
- const typeMap = {
2565
- Point: "point",
2566
- LineString: "linestring",
2567
- Polygon: "polygon",
2568
- MultiPoint: "multipoint",
2569
- MultiLineString: "multilinestring",
2570
- MultiPolygon: "multipolygon"
2571
- };
2572
- const types = [];
2573
- for (const raw of primaryCol.geometryTypes) {
2574
- const base = raw.split(" ")[0];
2575
- const mapped = typeMap[base];
2576
- if (mapped && !types.includes(mapped)) types.push(mapped);
2577
- }
2578
- return types;
3150
+ function stripEdgeSlashes2(s) {
3151
+ let start = 0;
3152
+ let end = s.length;
3153
+ while (start < end && s.charCodeAt(start) === SLASH2) start++;
3154
+ while (end > start && s.charCodeAt(end - 1) === SLASH2) end--;
3155
+ return s.slice(start, end);
2579
3156
  }
2580
- function extractBounds(geo) {
2581
- const primaryCol = geo.columns[geo.primaryColumn];
2582
- if (!primaryCol?.bbox || primaryCol.bbox.length < 4) return null;
2583
- return [primaryCol.bbox[0], primaryCol.bbox[1], primaryCol.bbox[2], primaryCol.bbox[3]];
3157
+ var AWS_VHOST_RE = /^(.+)\.s3[.-]([a-z0-9-]+)\.amazonaws\.com$/;
3158
+ var AWS_PATH_RE = /^s3[.-]([a-z0-9-]+)\.amazonaws\.com$/;
3159
+ var AWS_GLOBAL_HOST = "s3.amazonaws.com";
3160
+ var R2_RE = /^([a-z0-9]+)\.r2\.cloudflarestorage\.com$/;
3161
+ var GCS_GLOBAL_HOST = "storage.googleapis.com";
3162
+ var GCS_VHOST_RE = /^(.+)\.storage\.googleapis\.com$/;
3163
+ var DO_VHOST_RE = /^(.+)\.([a-z0-9-]+)\.digitaloceanspaces\.com$/;
3164
+ var DO_PATH_RE = /^([a-z0-9-]+)\.digitaloceanspaces\.com$/;
3165
+ var WASABI_RE = /^s3\.([a-z0-9-]+)\.wasabisys\.com$/;
3166
+ var B2_S3_RE = /^(.+)\.s3\.([a-z0-9-]+)\.backblazeb2\.com$/;
3167
+ var B2_NATIVE_RE = /^f[a-z0-9]+\.backblazeb2\.com$/;
3168
+ var OSS_RE = /^(.+)\.(oss-[a-z0-9-]+)\.aliyuncs\.com$/;
3169
+ var COS_RE = /^(.+)\.cos\.([a-z0-9-]+)\.myqcloud\.com$/;
3170
+ var YANDEX_HOST = "storage.yandexcloud.net";
3171
+ var CONTABO_RE = /^([a-z0-9]+)\.contabostorage\.com$/;
3172
+ var HETZNER_RE = /^([a-z0-9]+)\.your-objectstorage\.com$/;
3173
+ var LINODE_VHOST_RE = /^(.+)\.([a-z0-9-]+)\.linodeobjects\.com$/;
3174
+ var LINODE_PATH_RE = /^([a-z0-9-]+)\.linodeobjects\.com$/;
3175
+ var OVH_RE = /^s3\.([a-z0-9-]+)\.io\.cloud\.ovh\.(?:net|us)$/;
3176
+ var AZURE_BLOB_RE = /^([a-z0-9]+)\.blob\.core\.windows\.net$/;
3177
+ var STORJ_GATEWAY_RE = /^gateway\.(?:([a-z0-9]+)\.)?storjshare\.io$/;
3178
+ var STORJ_LINK_RE = /^link\.(?:([a-z0-9]+)\.)?storjshare\.io$/;
3179
+ function isMinioLikeHost(host) {
3180
+ return host.includes("minio") || host === "localhost" || host === "127.0.0.1" || host.startsWith("192.168.") || host.startsWith("10.");
2584
3181
  }
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));
3182
+ var STAC_API_PATH_RE = /\/(collections|items|catalogs|search)(\/|\?|$)/i;
3183
+ function isKnownBucketHost(host) {
3184
+ if (!host) return false;
3185
+ if (AWS_VHOST_RE.test(host)) return true;
3186
+ if (AWS_PATH_RE.test(host)) return true;
3187
+ if (host === AWS_GLOBAL_HOST) return true;
3188
+ if (R2_RE.test(host)) return true;
3189
+ if (host === GCS_GLOBAL_HOST) return true;
3190
+ if (GCS_VHOST_RE.test(host)) return true;
3191
+ if (DO_VHOST_RE.test(host)) return true;
3192
+ if (DO_PATH_RE.test(host)) return true;
3193
+ if (WASABI_RE.test(host)) return true;
3194
+ if (B2_S3_RE.test(host)) return true;
3195
+ if (B2_NATIVE_RE.test(host)) return true;
3196
+ if (OSS_RE.test(host)) return true;
3197
+ if (COS_RE.test(host)) return true;
3198
+ if (host === YANDEX_HOST) return true;
3199
+ if (CONTABO_RE.test(host)) return true;
3200
+ if (HETZNER_RE.test(host)) return true;
3201
+ if (LINODE_VHOST_RE.test(host)) return true;
3202
+ if (LINODE_PATH_RE.test(host)) return true;
3203
+ if (OVH_RE.test(host)) return true;
3204
+ if (AZURE_BLOB_RE.test(host)) return true;
3205
+ if (STORJ_GATEWAY_RE.test(host)) return true;
3206
+ if (STORJ_LINK_RE.test(host)) return true;
3207
+ if (isMinioLikeHost(host)) return true;
3208
+ return false;
2597
3209
  }
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;
3210
+ function defaultResult(defaults) {
3211
+ return {
3212
+ bucket: "",
3213
+ region: defaults.region || "us-east-1",
3214
+ endpoint: defaults.endpoint || "",
3215
+ provider: defaults.provider || "s3",
3216
+ prefix: ""
3217
+ };
2612
3218
  }
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;
3219
+ function splitBucketPrefix(rest) {
3220
+ const slashIdx = rest.indexOf("/");
3221
+ if (slashIdx >= 0) {
3222
+ return {
3223
+ bucket: rest.slice(0, slashIdx),
3224
+ prefix: stripTrailingSlashes2(rest.slice(slashIdx + 1))
3225
+ };
2620
3226
  }
3227
+ return { bucket: rest, prefix: "" };
2621
3228
  }
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
-
2724
- // ../../src/lib/utils/storage-url.ts
2725
- function buildSchemeMap() {
2726
- const map = {};
2727
- for (const [id, def] of Object.entries(PROVIDERS)) {
2728
- for (const scheme of def.schemes) {
2729
- const key = `${scheme}://`;
2730
- map[key] = { provider: id, strip: key.length };
2731
- }
2732
- }
2733
- map["swift://"] = { provider: "unknown", strip: 8 };
2734
- return map;
2735
- }
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;
2763
- function defaultResult(defaults) {
2764
- return {
2765
- bucket: "",
2766
- region: defaults.region || "us-east-1",
2767
- endpoint: defaults.endpoint || "",
2768
- provider: defaults.provider || "s3",
2769
- prefix: ""
2770
- };
2771
- }
2772
- function splitBucketPrefix(rest) {
2773
- const slashIdx = rest.indexOf("/");
2774
- if (slashIdx >= 0) {
2775
- return {
2776
- bucket: rest.slice(0, slashIdx),
2777
- prefix: rest.slice(slashIdx + 1).replace(/\/+$/, "")
2778
- };
2779
- }
2780
- return { bucket: rest, prefix: "" };
2781
- }
2782
- function parseStorageUrl(input, defaults = {}) {
2783
- const trimmed = input.trim();
2784
- const lower = trimmed.toLowerCase();
2785
- for (const [scheme, { provider, strip }] of Object.entries(SCHEME_MAP)) {
2786
- if (lower.startsWith(scheme)) {
2787
- const rest = trimmed.slice(strip);
2788
- const { bucket, prefix } = splitBucketPrefix(rest);
2789
- return {
2790
- bucket,
2791
- region: defaults.region || "us-east-1",
2792
- endpoint: defaults.endpoint || "",
2793
- provider,
2794
- prefix
2795
- };
3229
+ function parseStorageUrl(input, defaults = {}) {
3230
+ const trimmed = input.trim();
3231
+ const lower = trimmed.toLowerCase();
3232
+ for (const [scheme, { provider, strip }] of Object.entries(SCHEME_MAP)) {
3233
+ if (lower.startsWith(scheme)) {
3234
+ const rest = trimmed.slice(strip);
3235
+ const { bucket, prefix } = splitBucketPrefix(rest);
3236
+ return {
3237
+ bucket,
3238
+ region: defaults.region || "us-east-1",
3239
+ endpoint: defaults.endpoint || "",
3240
+ provider,
3241
+ prefix
3242
+ };
2796
3243
  }
2797
3244
  }
2798
3245
  if (lower.startsWith("http://") || lower.startsWith("https://")) {
@@ -2987,96 +3434,2062 @@ function parseStorageUrl(input, defaults = {}) {
2987
3434
  prefix: pathParts.slice(1).join("/")
2988
3435
  };
2989
3436
  }
2990
- if (isMinioLikeHost(host) && pathParts.length > 0) {
2991
- return {
2992
- bucket: pathParts[0],
2993
- region: defaults.region || "us-east-1",
2994
- endpoint: `${url.protocol}//${url.host}`,
2995
- provider: "minio",
2996
- prefix: pathParts.slice(1).join("/")
2997
- };
3437
+ if (isMinioLikeHost(host) && pathParts.length > 0) {
3438
+ return {
3439
+ bucket: pathParts[0],
3440
+ region: defaults.region || "us-east-1",
3441
+ endpoint: `${url.protocol}//${url.host}`,
3442
+ provider: "minio",
3443
+ prefix: pathParts.slice(1).join("/")
3444
+ };
3445
+ }
3446
+ const azureBlob = host.match(AZURE_BLOB_RE);
3447
+ if (azureBlob && pathParts.length > 0) {
3448
+ return {
3449
+ bucket: pathParts[0],
3450
+ region: defaults.region || "",
3451
+ endpoint: `${url.protocol}//${url.host}`,
3452
+ provider: "azure",
3453
+ prefix: pathParts.slice(1).join("/")
3454
+ };
3455
+ }
3456
+ const storjGateway = host.match(STORJ_GATEWAY_RE);
3457
+ if (storjGateway && pathParts.length > 0) {
3458
+ return {
3459
+ bucket: pathParts[0],
3460
+ region: storjGateway[1] || defaults.region || "us1",
3461
+ endpoint: `${url.protocol}//${url.host}`,
3462
+ provider: "storj",
3463
+ prefix: pathParts.slice(1).join("/")
3464
+ };
3465
+ }
3466
+ const storjLink = host.match(STORJ_LINK_RE);
3467
+ if (storjLink && pathParts.length >= 3 && (pathParts[0] === "raw" || pathParts[0] === "s")) {
3468
+ return {
3469
+ bucket: pathParts[2],
3470
+ region: storjLink[1] || defaults.region || "us1",
3471
+ endpoint: `${url.protocol}//${url.host}/${pathParts[0]}/${pathParts[1]}`,
3472
+ provider: "storj",
3473
+ prefix: pathParts.slice(3).join("/")
3474
+ };
3475
+ }
3476
+ if (STAC_API_PATH_RE.test(url.pathname)) {
3477
+ return {
3478
+ ...defaultResult(defaults),
3479
+ endpoint: `${url.protocol}//${url.host}`
3480
+ };
3481
+ }
3482
+ if (pathParts.length > 0) {
3483
+ const endpoint = `${url.protocol}//${url.host}`;
3484
+ return {
3485
+ bucket: pathParts[0],
3486
+ region: defaults.region || "us-east-1",
3487
+ endpoint,
3488
+ provider: defaults.provider || "s3",
3489
+ prefix: pathParts.slice(1).join("/")
3490
+ };
3491
+ }
3492
+ return {
3493
+ ...defaultResult(defaults),
3494
+ endpoint: `${url.protocol}//${url.host}`
3495
+ };
3496
+ } catch {
3497
+ }
3498
+ }
3499
+ const cleaned = stripEdgeSlashes2(trimmed);
3500
+ return {
3501
+ bucket: cleaned,
3502
+ region: defaults.region || "us-east-1",
3503
+ endpoint: defaults.endpoint || "",
3504
+ provider: defaults.provider || "s3",
3505
+ prefix: ""
3506
+ };
3507
+ }
3508
+ function looksLikeUrl(input) {
3509
+ const lower = input.trim().toLowerCase();
3510
+ if (lower.startsWith("http://") || lower.startsWith("https://")) return true;
3511
+ for (const scheme of Object.keys(SCHEME_MAP)) {
3512
+ if (lower.startsWith(scheme)) return true;
3513
+ }
3514
+ return false;
3515
+ }
3516
+ function describeParseResult(parsed) {
3517
+ const parts = [];
3518
+ if (parsed.bucket) parts.push(`bucket="${parsed.bucket}"`);
3519
+ if (parsed.endpoint) parts.push(`endpoint="${parsed.endpoint}"`);
3520
+ if (parsed.region && parsed.region !== "us-east-1") parts.push(`region="${parsed.region}"`);
3521
+ if (parsed.provider !== "s3") parts.push(`provider=${parsed.provider}`);
3522
+ if (parsed.prefix) parts.push(`prefix="${parsed.prefix}"`);
3523
+ return parts.length > 0 ? `Detected: ${parts.join(", ")}` : "";
3524
+ }
3525
+ function classifyUrl(input) {
3526
+ const trimmed = input.trim();
3527
+ const lower = trimmed.toLowerCase();
3528
+ for (const scheme of Object.keys(SCHEME_MAP)) {
3529
+ if (lower.startsWith(scheme)) {
3530
+ return { kind: "scheme", parsed: parseStorageUrl(trimmed) };
3531
+ }
3532
+ }
3533
+ if (lower.startsWith("http://") || lower.startsWith("https://")) {
3534
+ let url;
3535
+ try {
3536
+ url = new URL(trimmed);
3537
+ } catch {
3538
+ return {
3539
+ kind: "remote-file",
3540
+ url: new URL(`https://${trimmed.replace(/^https?:\/\//i, "")}`)
3541
+ };
3542
+ }
3543
+ const parsed = parseStorageUrl(trimmed);
3544
+ if (parsed.bucket && isKnownBucketHost(url.hostname)) {
3545
+ return { kind: "object-storage", parsed };
3546
+ }
3547
+ if (STAC_API_PATH_RE.test(url.pathname)) {
3548
+ return { kind: "stac-api", url };
3549
+ }
3550
+ return { kind: "remote-file", url };
3551
+ }
3552
+ try {
3553
+ return { kind: "remote-file", url: new URL(trimmed) };
3554
+ } catch {
3555
+ return { kind: "remote-file", url: new URL(`https://${trimmed}`) };
3556
+ }
3557
+ }
3558
+
3559
+ // src/host-detection.ts
3560
+ function extractRootPrefix(pathname) {
3561
+ let clean = pathname.replace(/\/[^/]*\.[^/]*$/, "/");
3562
+ clean = clean.replace(/^\//, "");
3563
+ if (!clean || clean === "/") return "";
3564
+ if (!clean.endsWith("/")) clean += "/";
3565
+ return clean;
3566
+ }
3567
+ function buildBucketUrl(provider, endpoint, bucket, region) {
3568
+ return buildProviderBaseUrl(
3569
+ provider === "unknown" ? "s3" : provider,
3570
+ endpoint,
3571
+ bucket,
3572
+ region || ""
3573
+ );
3574
+ }
3575
+ function parsedToHost(parsed, host, rootPrefix) {
3576
+ if (!parsed.bucket || !isKnownBucketHost(host)) return null;
3577
+ const provider = parsed.provider === "unknown" ? "s3" : parsed.provider;
3578
+ return {
3579
+ provider,
3580
+ bucket: parsed.bucket,
3581
+ region: parsed.region,
3582
+ endpoint: parsed.endpoint,
3583
+ rootPrefix,
3584
+ bucketUrl: buildBucketUrl(parsed.provider, parsed.endpoint, parsed.bucket, parsed.region)
3585
+ };
3586
+ }
3587
+ function detectHostBucket() {
3588
+ if (typeof window === "undefined") return null;
3589
+ const url = new URL(window.location.href);
3590
+ const urlParam = url.searchParams.get("url");
3591
+ if (urlParam) {
3592
+ try {
3593
+ const paramUrl = new URL(urlParam);
3594
+ const parsed2 = parseStorageUrl(urlParam);
3595
+ const host = parsedToHost(parsed2, paramUrl.hostname, "");
3596
+ if (host) return host;
3597
+ } catch {
3598
+ }
3599
+ }
3600
+ const parsed = parseStorageUrl(window.location.href);
3601
+ if (!parsed.bucket || !isKnownBucketHost(url.hostname)) return null;
3602
+ const prefixPath = parsed.prefix ? `/${parsed.prefix}` : "";
3603
+ const rootPrefix = extractRootPrefix(prefixPath || "/");
3604
+ const provider = parsed.provider === "unknown" ? "s3" : parsed.provider;
3605
+ return {
3606
+ provider,
3607
+ bucket: parsed.bucket,
3608
+ region: parsed.region,
3609
+ endpoint: parsed.endpoint,
3610
+ rootPrefix,
3611
+ bucketUrl: buildBucketUrl(parsed.provider, parsed.endpoint, parsed.bucket, parsed.region)
3612
+ };
3613
+ }
3614
+ function applyStacItemStorageHints(input, item) {
3615
+ const hints = extractStorageHints(item);
3616
+ const merged = applyStorageHintsToConnection(input, hints);
3617
+ if (import.meta.env?.DEV && (hints.region || hints.endpoint || hints.requesterPays)) {
3618
+ console.debug("[host-detection] applied STAC storage hints", hints);
3619
+ }
3620
+ return merged;
3621
+ }
3622
+
3623
+ // src/local-storage.ts
3624
+ function loadFromStorage(key, defaultValue) {
3625
+ if (typeof window === "undefined") return defaultValue;
3626
+ try {
3627
+ const raw = localStorage.getItem(key);
3628
+ if (raw) return JSON.parse(raw);
3629
+ } catch {
3630
+ }
3631
+ return defaultValue;
3632
+ }
3633
+ function persistToStorage(key, value) {
3634
+ if (typeof window === "undefined") return;
3635
+ try {
3636
+ localStorage.setItem(key, JSON.stringify(value));
3637
+ } catch {
3638
+ }
3639
+ }
3640
+
3641
+ // src/lru.ts
3642
+ var LruCache = class {
3643
+ map = /* @__PURE__ */ new Map();
3644
+ max;
3645
+ onEvict;
3646
+ constructor(opts) {
3647
+ if (opts.max <= 0) throw new Error("LruCache: max must be > 0");
3648
+ this.max = opts.max;
3649
+ this.onEvict = opts.onEvict;
3650
+ }
3651
+ get size() {
3652
+ return this.map.size;
3653
+ }
3654
+ has(key) {
3655
+ return this.map.has(key);
3656
+ }
3657
+ get(key) {
3658
+ const v = this.map.get(key);
3659
+ if (v === void 0) return void 0;
3660
+ this.map.delete(key);
3661
+ this.map.set(key, v);
3662
+ return v;
3663
+ }
3664
+ set(key, value) {
3665
+ if (this.map.has(key)) {
3666
+ this.map.delete(key);
3667
+ }
3668
+ this.map.set(key, value);
3669
+ while (this.map.size > this.max) {
3670
+ const oldest = this.map.keys().next().value;
3671
+ if (oldest === void 0) break;
3672
+ const evicted = this.map.get(oldest);
3673
+ this.map.delete(oldest);
3674
+ this.onEvict?.(oldest, evicted);
3675
+ }
3676
+ }
3677
+ delete(key) {
3678
+ const v = this.map.get(key);
3679
+ if (v === void 0) return false;
3680
+ this.map.delete(key);
3681
+ this.onEvict?.(key, v);
3682
+ return true;
3683
+ }
3684
+ clear() {
3685
+ if (this.onEvict) {
3686
+ for (const [k, v] of this.map) this.onEvict(k, v);
3687
+ }
3688
+ this.map.clear();
3689
+ }
3690
+ };
3691
+
3692
+ // src/map-pixel-inspect.ts
3693
+ function attachPixelInspector(map, { probe, onStart, onResult }) {
3694
+ let active = null;
3695
+ let detached = false;
3696
+ const handler = (event) => {
3697
+ if (detached) return;
3698
+ if (active) active.abort();
3699
+ const ctrl = new AbortController();
3700
+ active = ctrl;
3701
+ onStart();
3702
+ void (async () => {
3703
+ let payload = null;
3704
+ try {
3705
+ payload = await probe({
3706
+ lng: event.lngLat.lng,
3707
+ lat: event.lngLat.lat,
3708
+ signal: ctrl.signal
3709
+ });
3710
+ } catch (err) {
3711
+ if (isHelperAbort(err, ctrl.signal)) return;
3712
+ payload = null;
3713
+ }
3714
+ if (ctrl.signal.aborted && active === ctrl) {
3715
+ return;
3716
+ }
3717
+ if (active === ctrl) active = null;
3718
+ onResult(payload);
3719
+ })();
3720
+ };
3721
+ map.on("click", handler);
3722
+ return function detach() {
3723
+ if (detached) return;
3724
+ detached = true;
3725
+ map.off("click", handler);
3726
+ if (active) {
3727
+ active.abort();
3728
+ active = null;
3729
+ }
3730
+ };
3731
+ }
3732
+ function isHelperAbort(err, signal) {
3733
+ if (!signal.aborted) return false;
3734
+ if (err instanceof DOMException && err.name === "AbortError") return true;
3735
+ if (err && typeof err === "object" && err.name === "AbortError") {
3736
+ return true;
3737
+ }
3738
+ return false;
3739
+ }
3740
+
3741
+ // src/markdown-sql.ts
3742
+ async function parseMarkdownDocument(markdown) {
3743
+ let frontmatter = {};
3744
+ let content = markdown;
3745
+ const fmMatch = markdown.match(/^---\n([\s\S]*?)\n---\n/);
3746
+ if (fmMatch) {
3747
+ try {
3748
+ const { default: YAML } = await import('yaml');
3749
+ frontmatter = YAML.parse(fmMatch[1]) || {};
3750
+ } catch {
3751
+ }
3752
+ content = markdown.slice(fmMatch[0].length);
3753
+ }
3754
+ const sqlBlocks = [];
3755
+ const lines = content.split("\n");
3756
+ let i = 0;
3757
+ while (i < lines.length) {
3758
+ const line = lines[i];
3759
+ const match = line.match(/^```sql\s+(\w[\w-]*)\s*$/);
3760
+ if (match) {
3761
+ const name = match[1];
3762
+ const startLine = i;
3763
+ const sqlLines = [];
3764
+ i++;
3765
+ while (i < lines.length && lines[i] !== "```") {
3766
+ sqlLines.push(lines[i]);
3767
+ i++;
3768
+ }
3769
+ sqlBlocks.push({
3770
+ name,
3771
+ sql: sqlLines.join("\n"),
3772
+ startLine,
3773
+ endLine: i
3774
+ });
3775
+ }
3776
+ i++;
3777
+ }
3778
+ return { frontmatter, content, sqlBlocks };
3779
+ }
3780
+ function interpolateTemplates(text, queryResults) {
3781
+ return text.replace(/\{(\w+)\.rows\[(\d+)\]\.(\w+)\}/g, (match, queryName, rowIdx, colName) => {
3782
+ const rows = queryResults.get(queryName);
3783
+ if (!rows) return match;
3784
+ const row = rows[parseInt(rowIdx, 10)];
3785
+ if (!row) return match;
3786
+ const value = row[colName];
3787
+ return value !== void 0 ? String(value) : match;
3788
+ });
3789
+ }
3790
+ function markSqlBlocks(content) {
3791
+ return content.replace(
3792
+ /```sql[ \t]+(\w[\w-]*)[ \t]*\n([\s\S]*?)```/g,
3793
+ (_, name) => `<div data-sql-block="${name}"></div>`
3794
+ );
3795
+ }
3796
+
3797
+ // src/markdown-sql-context.ts
3798
+ var MarkdownSqlContext = class {
3799
+ engine;
3800
+ connId;
3801
+ prefix;
3802
+ results = /* @__PURE__ */ new Map();
3803
+ constructor(engine, connId, prefix = "") {
3804
+ this.engine = engine;
3805
+ this.connId = connId;
3806
+ this.prefix = prefix;
3807
+ }
3808
+ /** Execute a SQL query and store the result under the given name. */
3809
+ async executeSql(sql, queryName) {
3810
+ const transformedSql = this.transformPaths(sql);
3811
+ const result = await this.engine.query(this.connId, transformedSql);
3812
+ const rows = result.rows ?? [];
3813
+ this.results.set(queryName, { result, rows });
3814
+ return rows;
3815
+ }
3816
+ /**
3817
+ * Transform relative file paths in SQL to full S3 URLs.
3818
+ * e.g. read_parquet('data.parquet') becomes read_parquet('s3://bucket/prefix/data.parquet').
3819
+ */
3820
+ transformPaths(sql) {
3821
+ if (!this.connId || !this.prefix) return sql;
3822
+ return sql.replace(/(read_(?:parquet|csv|json|csv_auto))\('([^']+)'\)/g, (match, fn, path) => {
3823
+ if (path.startsWith("s3://") || path.startsWith("http") || path.startsWith("/")) {
3824
+ return match;
3825
+ }
3826
+ const fullPath = `s3://${this.prefix}/${path}`;
3827
+ return `${fn}('${fullPath}')`;
3828
+ });
3829
+ }
3830
+ getResult(queryName) {
3831
+ return this.results.get(queryName);
3832
+ }
3833
+ getAllResults() {
3834
+ const map = /* @__PURE__ */ new Map();
3835
+ for (const [name, { rows }] of this.results) {
3836
+ map.set(name, rows);
3837
+ }
3838
+ return map;
3839
+ }
3840
+ getColumns(queryName) {
3841
+ const entry = this.results.get(queryName);
3842
+ if (!entry) return [];
3843
+ return entry.result.columns;
3844
+ }
3845
+ };
3846
+
3847
+ // src/notebook.ts
3848
+ var PREFIX = "nb-";
3849
+ function el(tag, classNames = []) {
3850
+ const e = document.createElement(tag);
3851
+ if (classNames.length) e.className = classNames.map((c) => PREFIX + c).join(" ");
3852
+ return e;
3853
+ }
3854
+ function escapeHTML(raw) {
3855
+ return raw.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3856
+ }
3857
+ function joinText(text) {
3858
+ if (Array.isArray(text)) return text.join("");
3859
+ return text ?? "";
3860
+ }
3861
+ var IMAGE_FORMATS = ["image/png", "image/jpeg", "image/gif", "image/webp"];
3862
+ function renderImage(format, data) {
3863
+ const img = el("img", ["image-output"]);
3864
+ img.src = `data:${format};base64,${joinText(data).replace(/\n/g, "")}`;
3865
+ return img;
3866
+ }
3867
+ function renderDisplayData(output, config) {
3868
+ const getData = (mime) => output.data?.[mime] ?? output[mime];
3869
+ for (const fmt of IMAGE_FORMATS) {
3870
+ const d = getData(fmt);
3871
+ if (d) return renderImage(fmt, d);
3872
+ }
3873
+ const svg = getData("image/svg+xml") ?? getData("text/svg+xml");
3874
+ if (svg) {
3875
+ const wrapper = el("div", ["svg-output"]);
3876
+ wrapper.innerHTML = joinText(svg);
3877
+ return wrapper;
3878
+ }
3879
+ const html = getData("text/html");
3880
+ if (html) {
3881
+ const wrapper = el("div", ["html-output"]);
3882
+ wrapper.innerHTML = joinText(html);
3883
+ return wrapper;
3884
+ }
3885
+ const md = getData("text/markdown");
3886
+ if (md) {
3887
+ const wrapper = el("div", ["html-output"]);
3888
+ wrapper.innerHTML = config.markdown(joinText(md));
3889
+ return wrapper;
3890
+ }
3891
+ const latex = getData("text/latex");
3892
+ if (latex) {
3893
+ const wrapper = el("div", ["latex-output"]);
3894
+ wrapper.textContent = joinText(latex);
3895
+ return wrapper;
3896
+ }
3897
+ const plain = getData("text/plain");
3898
+ if (plain) {
3899
+ const pre = el("pre", ["text-output"]);
3900
+ pre.innerHTML = config.ansi(escapeHTML(joinText(plain)));
3901
+ return pre;
3902
+ }
3903
+ return el("div", ["empty-output"]);
3904
+ }
3905
+ function renderStream(output, config) {
3906
+ const streamName = output.stream ?? output.name ?? "stdout";
3907
+ const pre = el("pre", [streamName]);
3908
+ pre.innerHTML = config.ansi(escapeHTML(joinText(output.text ?? "")));
3909
+ return pre;
3910
+ }
3911
+ function renderError(output, config) {
3912
+ const pre = el("pre", ["pyerr"]);
3913
+ const raw = (output.traceback ?? []).join("\n");
3914
+ pre.innerHTML = config.ansi(escapeHTML(raw));
3915
+ return pre;
3916
+ }
3917
+ function coalesceStreams(outputs) {
3918
+ if (!outputs.length) return outputs;
3919
+ const result = [{ ...outputs[0] }];
3920
+ for (let i = 1; i < outputs.length; i++) {
3921
+ const o = outputs[i];
3922
+ const last = result[result.length - 1];
3923
+ if (o.output_type === "stream" && last.output_type === "stream" && (o.stream ?? o.name) === (last.stream ?? last.name)) {
3924
+ const lastText = Array.isArray(last.text) ? last.text : [last.text ?? ""];
3925
+ const oText = Array.isArray(o.text) ? o.text : [o.text ?? ""];
3926
+ last.text = lastText.concat(oText);
3927
+ } else {
3928
+ result.push({ ...o });
3929
+ }
3930
+ }
3931
+ return result;
3932
+ }
3933
+ function renderCodeCell(cell, lang, config) {
3934
+ const cellEl = el("div", ["cell", "code-cell"]);
3935
+ const source = joinText(cell.source ?? cell.input ?? "");
3936
+ if (source) {
3937
+ const holder = el("div", ["input"]);
3938
+ const num = cell.prompt_number ?? cell.execution_count;
3939
+ if (typeof num === "number" && num > -1) {
3940
+ holder.setAttribute("data-prompt-number", String(num));
3941
+ }
3942
+ const pre = el("pre");
3943
+ const code = document.createElement("code");
3944
+ code.setAttribute("data-language", lang);
3945
+ code.className = `lang-${lang}`;
3946
+ code.innerHTML = config.highlighter(escapeHTML(source), lang);
3947
+ pre.appendChild(code);
3948
+ holder.appendChild(pre);
3949
+ cellEl.appendChild(holder);
3950
+ }
3951
+ const rawOutputs = coalesceStreams(cell.outputs ?? []);
3952
+ for (const output of rawOutputs) {
3953
+ const outer = el("div", ["output"]);
3954
+ let inner;
3955
+ switch (output.output_type) {
3956
+ case "display_data":
3957
+ case "execute_result":
3958
+ case "pyout":
3959
+ inner = renderDisplayData(output, config);
3960
+ break;
3961
+ case "stream":
3962
+ inner = renderStream(output, config);
3963
+ break;
3964
+ case "error":
3965
+ case "pyerr":
3966
+ inner = renderError(output, config);
3967
+ break;
3968
+ default:
3969
+ inner = el("div", ["empty-output"]);
3970
+ }
3971
+ outer.appendChild(inner);
3972
+ cellEl.appendChild(outer);
3973
+ }
3974
+ return cellEl;
3975
+ }
3976
+ function renderMarkdownCell(cell, config) {
3977
+ const cellEl = el("div", ["cell", "markdown-cell"]);
3978
+ const source = joinText(cell.source ?? cell.input ?? "");
3979
+ cellEl.innerHTML = config.markdown(source);
3980
+ return cellEl;
3981
+ }
3982
+ function renderHeadingCell(cell) {
3983
+ const level = Math.min(Math.max(cell.level ?? 1, 1), 6);
3984
+ const heading = el(`h${level}`, ["cell", "heading-cell"]);
3985
+ heading.innerHTML = escapeHTML(joinText(cell.source ?? cell.input ?? ""));
3986
+ return heading;
3987
+ }
3988
+ function renderRawCell(cell) {
3989
+ const cellEl = el("div", ["cell", "raw-cell"]);
3990
+ cellEl.innerHTML = escapeHTML(joinText(cell.source ?? cell.input ?? ""));
3991
+ return cellEl;
3992
+ }
3993
+ function renderNotebook(raw, config) {
3994
+ const meta = raw.metadata ?? {};
3995
+ const lang = meta.kernelspec?.language ?? meta.language_info?.name ?? meta.language ?? "python";
3996
+ const kernelName = meta.kernelspec?.display_name ?? meta.language_info?.name ?? "";
3997
+ let allCells = [];
3998
+ if (Array.isArray(raw.cells)) {
3999
+ allCells = raw.cells;
4000
+ } else if (Array.isArray(raw.worksheets)) {
4001
+ for (const ws of raw.worksheets) {
4002
+ if (Array.isArray(ws.cells)) allCells.push(...ws.cells);
4003
+ }
4004
+ }
4005
+ const notebook = el("div", ["notebook"]);
4006
+ for (const cell of allCells) {
4007
+ let rendered;
4008
+ switch (cell.cell_type) {
4009
+ case "code":
4010
+ rendered = renderCodeCell(cell, cell.language ?? lang, config);
4011
+ break;
4012
+ case "markdown":
4013
+ rendered = renderMarkdownCell(cell, config);
4014
+ break;
4015
+ case "heading":
4016
+ rendered = renderHeadingCell(cell);
4017
+ break;
4018
+ case "raw":
4019
+ rendered = renderRawCell(cell);
4020
+ break;
4021
+ default:
4022
+ rendered = renderRawCell(cell);
4023
+ }
4024
+ notebook.appendChild(rendered);
4025
+ }
4026
+ return {
4027
+ element: notebook,
4028
+ meta: { kernelName, language: lang, cellCount: allCells.length }
4029
+ };
4030
+ }
4031
+
4032
+ // src/parquet-metadata.ts
4033
+ function mapParquetType(col) {
4034
+ const lt = col.logical_type;
4035
+ if (lt) {
4036
+ if (lt.type === "GEOMETRY" || lt.type === "GEOGRAPHY") return "GEOMETRY";
4037
+ if (lt.type === "STRING" || lt.type === "UTF8") return "VARCHAR";
4038
+ if (lt.type === "JSON") return "JSON";
4039
+ if (lt.type === "UUID") return "UUID";
4040
+ if (lt.type === "ENUM") return "VARCHAR";
4041
+ if (lt.type === "INT" || lt.type === "INTEGER") {
4042
+ const bits = lt.bitWidth ?? 32;
4043
+ const signed = lt.isSigned !== false;
4044
+ if (bits <= 8) return signed ? "TINYINT" : "UTINYINT";
4045
+ if (bits <= 16) return signed ? "SMALLINT" : "USMALLINT";
4046
+ if (bits <= 32) return signed ? "INTEGER" : "UINTEGER";
4047
+ return signed ? "BIGINT" : "UBIGINT";
4048
+ }
4049
+ if (lt.type === "DECIMAL") return `DECIMAL(${lt.precision ?? 18},${lt.scale ?? 0})`;
4050
+ if (lt.type === "DATE") return "DATE";
4051
+ if (lt.type === "TIME") return "TIME";
4052
+ if (lt.type === "TIMESTAMP") return "TIMESTAMP";
4053
+ if (lt.type === "BSON") return "BLOB";
4054
+ }
4055
+ const ct = col.converted_type;
4056
+ if (ct === "UTF8") return "VARCHAR";
4057
+ if (ct === "JSON") return "JSON";
4058
+ if (ct === "DATE") return "DATE";
4059
+ if (ct === "TIMESTAMP_MILLIS" || ct === "TIMESTAMP_MICROS") return "TIMESTAMP";
4060
+ if (ct === "DECIMAL") return `DECIMAL(${col.precision ?? 18},${col.scale ?? 0})`;
4061
+ if (ct === "INT_8") return "TINYINT";
4062
+ if (ct === "INT_16") return "SMALLINT";
4063
+ if (ct === "INT_32") return "INTEGER";
4064
+ if (ct === "INT_64") return "BIGINT";
4065
+ if (ct === "UINT_8") return "UTINYINT";
4066
+ if (ct === "UINT_16") return "USMALLINT";
4067
+ if (ct === "UINT_32") return "UINTEGER";
4068
+ if (ct === "UINT_64") return "UBIGINT";
4069
+ const pt = col.type;
4070
+ if (pt === "BOOLEAN") return "BOOLEAN";
4071
+ if (pt === "INT32") return "INTEGER";
4072
+ if (pt === "INT64") return "BIGINT";
4073
+ if (pt === "INT96") return "TIMESTAMP";
4074
+ if (pt === "FLOAT") return "FLOAT";
4075
+ if (pt === "DOUBLE") return "DOUBLE";
4076
+ if (pt === "BYTE_ARRAY") return "BLOB";
4077
+ if (pt === "FIXED_LEN_BYTE_ARRAY") return "BLOB";
4078
+ return "VARCHAR";
4079
+ }
4080
+ async function readParquetMetadata(url) {
4081
+ const { parquetMetadataAsync, asyncBufferFromUrl } = await import('hyparquet');
4082
+ const file = await asyncBufferFromUrl({ url });
4083
+ const metadata = await parquetMetadataAsync(file);
4084
+ const rowCount = metadata.row_groups.reduce(
4085
+ (sum, rg) => sum + Number(rg.num_rows),
4086
+ 0
4087
+ );
4088
+ const schema = metadata.schema.slice(1).filter((col) => col.num_children === void 0).map((col) => ({
4089
+ name: col.name,
4090
+ type: mapParquetType(col)
4091
+ }));
4092
+ const topLevelColumns = [];
4093
+ const root = metadata.schema[0];
4094
+ const rootChildren = root?.num_children ?? 0;
4095
+ let cursor = 1;
4096
+ for (let i = 0; i < rootChildren && cursor < metadata.schema.length; i++) {
4097
+ const el2 = metadata.schema[cursor];
4098
+ if (el2?.name) topLevelColumns.push(el2.name);
4099
+ cursor += countSubtree(metadata.schema, cursor);
4100
+ }
4101
+ let geo = null;
4102
+ let legacyGeoParquet = false;
4103
+ const geoKv = metadata.key_value_metadata?.find((kv) => kv.key === "geo");
4104
+ if (geoKv) {
4105
+ try {
4106
+ const geoJson = JSON.parse(geoKv.value ?? "");
4107
+ if (geoJson.schema_version && !geoJson.version) {
4108
+ legacyGeoParquet = true;
4109
+ }
4110
+ geo = {
4111
+ primaryColumn: geoJson.primary_column ?? "geometry",
4112
+ columns: {}
4113
+ };
4114
+ if (geoJson.columns) {
4115
+ for (const [colName, colMeta] of Object.entries(geoJson.columns)) {
4116
+ geo.columns[colName] = {
4117
+ encoding: colMeta.encoding ?? "WKB",
4118
+ geometryTypes: colMeta.geometry_types ?? [],
4119
+ crs: colMeta.crs ?? null,
4120
+ bbox: colMeta.bbox
4121
+ };
4122
+ }
4123
+ }
4124
+ } catch {
4125
+ }
4126
+ }
4127
+ const createdBy = metadata.created_by ?? null;
4128
+ const numRowGroups = metadata.row_groups.length;
4129
+ let compression = null;
4130
+ if (numRowGroups > 0 && metadata.row_groups[0].columns) {
4131
+ const codecs = /* @__PURE__ */ new Set();
4132
+ for (const col of metadata.row_groups[0].columns) {
4133
+ const codec = col.meta_data?.codec;
4134
+ if (codec) codecs.add(codec);
4135
+ }
4136
+ if (codecs.size === 1) {
4137
+ compression = [...codecs][0];
4138
+ } else if (codecs.size > 1) {
4139
+ compression = [...codecs].join(", ");
4140
+ }
4141
+ }
4142
+ return {
4143
+ rowCount,
4144
+ schema,
4145
+ topLevelColumns,
4146
+ geo,
4147
+ legacyGeoParquet,
4148
+ createdBy,
4149
+ numRowGroups,
4150
+ compression
4151
+ };
4152
+ }
4153
+ function countSubtree(schema, start) {
4154
+ const el2 = schema[start];
4155
+ if (!el2) return 0;
4156
+ const n = el2.num_children ?? 0;
4157
+ let cursor = start + 1;
4158
+ for (let i = 0; i < n; i++) {
4159
+ cursor += countSubtree(schema, cursor);
4160
+ }
4161
+ return cursor - start;
4162
+ }
4163
+ function extractEpsgFromGeoMeta(geo) {
4164
+ const primaryCol = geo.columns[geo.primaryColumn];
4165
+ if (!primaryCol?.crs) return null;
4166
+ const crs = primaryCol.crs;
4167
+ if (crs.type === "name" && crs.properties?.name?.includes("CRS84")) return null;
4168
+ if (crs.id?.authority === "EPSG") {
4169
+ const code = crs.id.code;
4170
+ if (WGS84_CODES.has(code)) return null;
4171
+ return `EPSG:${code}`;
4172
+ }
4173
+ return null;
4174
+ }
4175
+ function extractGeometryTypes(geo) {
4176
+ const primaryCol = geo.columns[geo.primaryColumn];
4177
+ if (!primaryCol?.geometryTypes?.length) return [];
4178
+ const typeMap = {
4179
+ Point: "point",
4180
+ LineString: "linestring",
4181
+ Polygon: "polygon",
4182
+ MultiPoint: "multipoint",
4183
+ MultiLineString: "multilinestring",
4184
+ MultiPolygon: "multipolygon"
4185
+ };
4186
+ const types = [];
4187
+ for (const raw of primaryCol.geometryTypes) {
4188
+ const base = raw.split(" ")[0];
4189
+ const mapped = typeMap[base];
4190
+ if (mapped && !types.includes(mapped)) types.push(mapped);
4191
+ }
4192
+ return types;
4193
+ }
4194
+ function extractBounds(geo) {
4195
+ const primaryCol = geo.columns[geo.primaryColumn];
4196
+ if (!primaryCol?.bbox || primaryCol.bbox.length < 4) return null;
4197
+ return [primaryCol.bbox[0], primaryCol.bbox[1], primaryCol.bbox[2], primaryCol.bbox[3]];
4198
+ }
4199
+
4200
+ // src/stac-facets.ts
4201
+ var THUMBNAIL_ROLES = ["thumbnail", "overview", "visual"];
4202
+ var THUMBNAIL_KEYS = ["thumbnail", "preview", "overview", "rendered_preview", "visual"];
4203
+ var BROWSER_IMAGE_MIME_RE = /^image\/(png|jpe?g|webp|gif|avif|svg\+xml|svg)\b/i;
4204
+ function isBrowserRenderableAsset(asset) {
4205
+ if (!asset.type) return true;
4206
+ return BROWSER_IMAGE_MIME_RE.test(asset.type);
4207
+ }
4208
+ function extractThumbnailHref(item) {
4209
+ const assets = item.assets ?? {};
4210
+ for (const role of THUMBNAIL_ROLES) {
4211
+ for (const asset of Object.values(assets)) {
4212
+ if (Array.isArray(asset?.roles) && asset.roles.includes(role) && asset.href && isBrowserRenderableAsset(asset)) {
4213
+ return asset.href;
4214
+ }
4215
+ }
4216
+ }
4217
+ for (const key of THUMBNAIL_KEYS) {
4218
+ const asset = assets[key];
4219
+ if (asset?.href && isBrowserRenderableAsset(asset)) return asset.href;
4220
+ }
4221
+ return null;
4222
+ }
4223
+ function collectAssetRoles(item) {
4224
+ const out = /* @__PURE__ */ new Set();
4225
+ for (const asset of Object.values(item.assets ?? {})) {
4226
+ if (Array.isArray(asset?.roles)) {
4227
+ for (const role of asset.roles) {
4228
+ if (typeof role === "string") out.add(role);
4229
+ }
4230
+ }
4231
+ }
4232
+ return [...out].sort();
4233
+ }
4234
+ function readNumber(props, key) {
4235
+ const v = props[key];
4236
+ if (typeof v === "number" && Number.isFinite(v)) return v;
4237
+ if (typeof v === "string") {
4238
+ const n = Number(v);
4239
+ return Number.isFinite(n) ? n : null;
4240
+ }
4241
+ return null;
4242
+ }
4243
+ function readString(props, key) {
4244
+ const v = props[key];
4245
+ return typeof v === "string" && v.length > 0 ? v : null;
4246
+ }
4247
+ function readStringArray(props, key) {
4248
+ const v = props[key];
4249
+ if (!Array.isArray(v)) return [];
4250
+ return v.filter((x) => typeof x === "string");
4251
+ }
4252
+ function extractItemView(item) {
4253
+ const props = item.properties ?? {};
4254
+ const bbox = Array.isArray(item.bbox) && item.bbox.length >= 4 ? [
4255
+ Number(item.bbox[0]),
4256
+ Number(item.bbox[1]),
4257
+ Number(item.bbox[2]),
4258
+ Number(item.bbox[3])
4259
+ ] : null;
4260
+ return {
4261
+ id: String(item.id ?? ""),
4262
+ collection: typeof item.collection === "string" ? item.collection : null,
4263
+ bbox,
4264
+ datetime: readString(props, "datetime") ?? readString(props, "start_datetime"),
4265
+ endDatetime: readString(props, "end_datetime"),
4266
+ cloudCover: readNumber(props, "eo:cloud_cover"),
4267
+ gsd: readNumber(props, "gsd"),
4268
+ platform: readString(props, "platform"),
4269
+ constellation: readString(props, "constellation"),
4270
+ instruments: readStringArray(props, "instruments"),
4271
+ epsg: readNumber(props, "proj:epsg"),
4272
+ thumbnailHref: extractThumbnailHref(item),
4273
+ assetRoles: collectAssetRoles(item),
4274
+ raw: item
4275
+ };
4276
+ }
4277
+ var DATETIME_HISTOGRAM_BINS_MAX = 64;
4278
+ var DATETIME_HISTOGRAM_BINS = 32;
4279
+ var MS_PER_DAY = 864e5;
4280
+ var MS_PER_YEAR = MS_PER_DAY * 365.25;
4281
+ var NUMERIC_FIELDS = ["cloudCover", "gsd"];
4282
+ var ENUM_FIELDS = [
4283
+ "collection",
4284
+ "platform",
4285
+ "constellation",
4286
+ "instruments",
4287
+ "assetRoles"
4288
+ ];
4289
+ function buildNumericFacet(views, field) {
4290
+ let min = Number.POSITIVE_INFINITY;
4291
+ let max = Number.NEGATIVE_INFINITY;
4292
+ let count = 0;
4293
+ for (const v of views) {
4294
+ const value = v[field];
4295
+ if (value == null || !Number.isFinite(value)) continue;
4296
+ if (value < min) min = value;
4297
+ if (value > max) max = value;
4298
+ count++;
4299
+ }
4300
+ if (count === 0 || min === max) return null;
4301
+ return { kind: "numeric", field, min, max, count };
4302
+ }
4303
+ function buildEnumFacet(views, field) {
4304
+ const counts = /* @__PURE__ */ new Map();
4305
+ for (const v of views) {
4306
+ const raw = v[field];
4307
+ if (raw == null) continue;
4308
+ if (Array.isArray(raw)) {
4309
+ for (const item of raw) counts.set(item, (counts.get(item) ?? 0) + 1);
4310
+ } else if (typeof raw === "string" && raw.length > 0) {
4311
+ counts.set(raw, (counts.get(raw) ?? 0) + 1);
4312
+ }
4313
+ }
4314
+ if (counts.size < 2) return null;
4315
+ const values = [...counts.entries()].map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
4316
+ return { kind: "enum", field, values };
4317
+ }
4318
+ function pickGranularity(spanMs) {
4319
+ if (spanMs <= 90 * MS_PER_DAY) return "day";
4320
+ if (spanMs <= 2 * MS_PER_YEAR) return "week";
4321
+ if (spanMs <= 20 * MS_PER_YEAR) return "month";
4322
+ return "year";
4323
+ }
4324
+ function floorUtcDay(t) {
4325
+ const d = new Date(t);
4326
+ return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
4327
+ }
4328
+ function floorIsoWeek(t) {
4329
+ const dayStart = floorUtcDay(t);
4330
+ const dow = new Date(dayStart).getUTCDay();
4331
+ const offsetDays = (dow + 6) % 7;
4332
+ return dayStart - offsetDays * MS_PER_DAY;
4333
+ }
4334
+ function floorUtcMonth(t) {
4335
+ const d = new Date(t);
4336
+ return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1);
4337
+ }
4338
+ function floorUtcYear(t) {
4339
+ const d = new Date(t);
4340
+ return Date.UTC(d.getUTCFullYear(), 0, 1);
4341
+ }
4342
+ function floorBin(t, g) {
4343
+ switch (g) {
4344
+ case "day":
4345
+ return floorUtcDay(t);
4346
+ case "week":
4347
+ return floorIsoWeek(t);
4348
+ case "month":
4349
+ return floorUtcMonth(t);
4350
+ case "year":
4351
+ return floorUtcYear(t);
4352
+ }
4353
+ }
4354
+ function nextBin(t, g) {
4355
+ const d = new Date(t);
4356
+ switch (g) {
4357
+ case "day":
4358
+ return t + MS_PER_DAY;
4359
+ case "week":
4360
+ return t + 7 * MS_PER_DAY;
4361
+ case "month":
4362
+ return Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + 1, 1);
4363
+ case "year":
4364
+ return Date.UTC(d.getUTCFullYear() + 1, 0, 1);
4365
+ }
4366
+ }
4367
+ function buildDatetimeFacet(views) {
4368
+ const timestamps = [];
4369
+ for (const v of views) {
4370
+ if (!v.datetime) continue;
4371
+ const t = Date.parse(v.datetime);
4372
+ if (Number.isFinite(t)) timestamps.push(t);
4373
+ }
4374
+ if (timestamps.length < 2) return null;
4375
+ let min = timestamps[0];
4376
+ let max = timestamps[0];
4377
+ for (const t of timestamps) {
4378
+ if (t < min) min = t;
4379
+ if (t > max) max = t;
4380
+ }
4381
+ if (min === max) return null;
4382
+ let granularity = pickGranularity(max - min);
4383
+ let binEdges = computeBinEdges(min, max, granularity);
4384
+ while (binEdges.length > DATETIME_HISTOGRAM_BINS_MAX && granularity !== "year") {
4385
+ granularity = coarsenGranularity(granularity);
4386
+ binEdges = computeBinEdges(min, max, granularity);
4387
+ }
4388
+ const bins = new Array(binEdges.length).fill(0);
4389
+ for (const t of timestamps) {
4390
+ const idx = findBinIndex(binEdges, t);
4391
+ bins[idx]++;
4392
+ }
4393
+ return {
4394
+ kind: "datetime",
4395
+ field: "datetime",
4396
+ min: new Date(min).toISOString(),
4397
+ max: new Date(max).toISOString(),
4398
+ count: timestamps.length,
4399
+ bins,
4400
+ granularity,
4401
+ binEdges
4402
+ };
4403
+ }
4404
+ function coarsenGranularity(g) {
4405
+ switch (g) {
4406
+ case "day":
4407
+ return "week";
4408
+ case "week":
4409
+ return "month";
4410
+ case "month":
4411
+ case "year":
4412
+ return "year";
4413
+ }
4414
+ }
4415
+ function computeBinEdges(min, max, g) {
4416
+ const edges = [];
4417
+ let cursor = floorBin(min, g);
4418
+ const stop = max;
4419
+ for (let i = 0; i < DATETIME_HISTOGRAM_BINS_MAX * 4 && cursor <= stop; i++) {
4420
+ edges.push(cursor);
4421
+ cursor = nextBin(cursor, g);
4422
+ }
4423
+ return edges;
4424
+ }
4425
+ function findBinIndex(edges, t) {
4426
+ if (t <= edges[0]) return 0;
4427
+ if (t >= edges[edges.length - 1]) return edges.length - 1;
4428
+ let lo = 0;
4429
+ let hi = edges.length - 1;
4430
+ while (lo < hi) {
4431
+ const mid = lo + hi + 1 >>> 1;
4432
+ if (edges[mid] <= t) lo = mid;
4433
+ else hi = mid - 1;
4434
+ }
4435
+ return lo;
4436
+ }
4437
+ function buildFacets(views) {
4438
+ const numeric = [];
4439
+ for (const field of NUMERIC_FIELDS) {
4440
+ const f = buildNumericFacet(views, field);
4441
+ if (f) numeric.push(f);
4442
+ }
4443
+ const enums = [];
4444
+ for (const field of ENUM_FIELDS) {
4445
+ const f = buildEnumFacet(views, field);
4446
+ if (f) enums.push(f);
4447
+ }
4448
+ return {
4449
+ datetime: buildDatetimeFacet(views),
4450
+ numeric,
4451
+ enums,
4452
+ total: views.length
4453
+ };
4454
+ }
4455
+ function inNumericRange(value, range) {
4456
+ if (value == null || !Number.isFinite(value)) return false;
4457
+ if (range.min != null && value < range.min) return false;
4458
+ if (range.max != null && value > range.max) return false;
4459
+ return true;
4460
+ }
4461
+ function inEnumSet(value, allow) {
4462
+ if (allow.length === 0) return true;
4463
+ if (value == null) return false;
4464
+ if (Array.isArray(value)) return value.some((v) => allow.includes(v));
4465
+ return allow.includes(value);
4466
+ }
4467
+ function inDatetimeRange(value, range) {
4468
+ if (!value) return false;
4469
+ const t = Date.parse(value);
4470
+ if (!Number.isFinite(t)) return false;
4471
+ if (range.min) {
4472
+ const m = Date.parse(range.min);
4473
+ if (Number.isFinite(m) && t < m) return false;
4474
+ }
4475
+ if (range.max) {
4476
+ const m = Date.parse(range.max);
4477
+ if (Number.isFinite(m) && t > m) return false;
4478
+ }
4479
+ return true;
4480
+ }
4481
+ function applyFacets(views, state) {
4482
+ if (!state) return views;
4483
+ const dt = state.datetime;
4484
+ const num = state.numeric;
4485
+ const enums = state.enums;
4486
+ const hasDatetime = dt && (dt.min || dt.max);
4487
+ const hasNumeric = num && Object.values(num).some((r) => r && (r.min != null || r.max != null));
4488
+ const hasEnums = enums && Object.values(enums).some((a) => Array.isArray(a) && a.length > 0);
4489
+ if (!hasDatetime && !hasNumeric && !hasEnums) return views;
4490
+ return views.filter((view) => {
4491
+ if (hasDatetime && dt && !inDatetimeRange(view.datetime, dt)) return false;
4492
+ if (hasNumeric && num) {
4493
+ for (const field of NUMERIC_FIELDS) {
4494
+ const range = num[field];
4495
+ if (!range || range.min == null && range.max == null) continue;
4496
+ if (!inNumericRange(view[field], range)) return false;
4497
+ }
4498
+ }
4499
+ if (hasEnums && enums) {
4500
+ for (const field of ENUM_FIELDS) {
4501
+ const allow = enums[field];
4502
+ if (!Array.isArray(allow) || allow.length === 0) continue;
4503
+ if (!inEnumSet(view[field], allow)) return false;
4504
+ }
4505
+ }
4506
+ return true;
4507
+ });
4508
+ }
4509
+ function sortViews(views, sort) {
4510
+ const arr = views.slice();
4511
+ const cmp = getComparator(sort);
4512
+ arr.sort(cmp);
4513
+ return arr;
4514
+ }
4515
+ function getComparator(sort) {
4516
+ switch (sort) {
4517
+ case "datetime-desc":
4518
+ return (a, b) => compareNullableNumber(parseTime(b.datetime), parseTime(a.datetime));
4519
+ case "datetime-asc":
4520
+ return (a, b) => compareNullableNumber(parseTime(a.datetime), parseTime(b.datetime));
4521
+ case "cloud-asc":
4522
+ return (a, b) => compareNullableNumber(a.cloudCover, b.cloudCover);
4523
+ case "cloud-desc":
4524
+ return (a, b) => compareNullableNumber(b.cloudCover, a.cloudCover);
4525
+ case "gsd-asc":
4526
+ return (a, b) => compareNullableNumber(a.gsd, b.gsd);
4527
+ case "gsd-desc":
4528
+ return (a, b) => compareNullableNumber(b.gsd, a.gsd);
4529
+ case "id-asc":
4530
+ return (a, b) => a.id.localeCompare(b.id);
4531
+ }
4532
+ }
4533
+ function parseTime(s) {
4534
+ if (!s) return null;
4535
+ const t = Date.parse(s);
4536
+ return Number.isFinite(t) ? t : null;
4537
+ }
4538
+ function compareNullableNumber(a, b) {
4539
+ const aMissing = a == null || !Number.isFinite(a);
4540
+ const bMissing = b == null || !Number.isFinite(b);
4541
+ if (aMissing && bMissing) return 0;
4542
+ if (aMissing) return 1;
4543
+ if (bMissing) return -1;
4544
+ return a - b;
4545
+ }
4546
+ function hasActiveFilters(state) {
4547
+ if (!state) return false;
4548
+ if (state.datetime && (state.datetime.min || state.datetime.max)) return true;
4549
+ if (state.numeric) {
4550
+ for (const r of Object.values(state.numeric)) {
4551
+ if (r && (r.min != null || r.max != null)) return true;
4552
+ }
4553
+ }
4554
+ if (state.enums) {
4555
+ for (const a of Object.values(state.enums)) {
4556
+ if (Array.isArray(a) && a.length > 0) return true;
4557
+ }
4558
+ }
4559
+ return false;
4560
+ }
4561
+ function emptyFacetState() {
4562
+ return {};
4563
+ }
4564
+
4565
+ // src/stac-geoparquet.ts
4566
+ function plainifyArrowValue(value) {
4567
+ if (value === null || value === void 0) return value;
4568
+ const t = typeof value;
4569
+ if (t === "string" || t === "number" || t === "boolean" || t === "bigint") return value;
4570
+ if (value instanceof Uint8Array || value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
4571
+ return value;
4572
+ }
4573
+ if (Array.isArray(value)) return value.map(plainifyArrowValue);
4574
+ const obj = value;
4575
+ if (typeof obj.toJSON === "function") {
4576
+ return plainifyArrowValue(obj.toJSON());
4577
+ }
4578
+ if (typeof obj.toArray === "function") {
4579
+ return obj.toArray().map(plainifyArrowValue);
4580
+ }
4581
+ if (t === "object") {
4582
+ const out = {};
4583
+ for (const [k, v] of Object.entries(value)) {
4584
+ out[k] = plainifyArrowValue(v);
4585
+ }
4586
+ return out;
4587
+ }
4588
+ return value;
4589
+ }
4590
+ var STAC_GEOPARQUET_REQUIRED_COLUMNS = [
4591
+ "stac_version",
4592
+ "type",
4593
+ "geometry",
4594
+ "assets"
4595
+ ];
4596
+ function isStacGeoparquetSchema(schema) {
4597
+ if (!Array.isArray(schema) || schema.length === 0) return false;
4598
+ const names = new Set(schema.map((c) => c.name));
4599
+ return STAC_GEOPARQUET_REQUIRED_COLUMNS.every((c) => names.has(c));
4600
+ }
4601
+ function flattenStacBbox(bbox) {
4602
+ if (!bbox) return null;
4603
+ if (Array.isArray(bbox)) {
4604
+ if (bbox.length < 4) return null;
4605
+ const [minX, minY, maxX, maxY] = bbox;
4606
+ if (![minX, minY, maxX, maxY].every((v) => Number.isFinite(v))) return null;
4607
+ return [minX, minY, maxX, maxY];
4608
+ }
4609
+ if (typeof bbox === "object") {
4610
+ const { xmin, ymin, xmax, ymax } = bbox;
4611
+ if (![xmin, ymin, xmax, ymax].every((v) => Number.isFinite(v))) return null;
4612
+ return [xmin, ymin, xmax, ymax];
4613
+ }
4614
+ return null;
4615
+ }
4616
+ function resolveStacAssetHref(href, baseUrl) {
4617
+ if (!href) return href;
4618
+ if (/^[a-z][a-z0-9+\-.]*:\/\//i.test(href)) return href;
4619
+ try {
4620
+ return new URL(href, baseUrl).toString();
4621
+ } catch {
4622
+ return href;
4623
+ }
4624
+ }
4625
+ function pickStacPrimaryAsset(assets, preferredKeys) {
4626
+ if (!assets || typeof assets !== "object") return null;
4627
+ const entries = Object.entries(assets).filter(
4628
+ ([, a]) => a && typeof a === "object" && typeof a.href === "string"
4629
+ );
4630
+ if (entries.length === 0) return null;
4631
+ if (preferredKeys) {
4632
+ for (const key of preferredKeys) {
4633
+ const match = entries.find(([k]) => k === key);
4634
+ if (match) return { key: match[0], asset: match[1] };
4635
+ }
4636
+ }
4637
+ const data = entries.find(([k]) => k === "data");
4638
+ if (data) return { key: data[0], asset: data[1] };
4639
+ const byRole = entries.find(([, a]) => Array.isArray(a.roles) && a.roles.includes("data"));
4640
+ if (byRole) return { key: byRole[0], asset: byRole[1] };
4641
+ return { key: entries[0][0], asset: entries[0][1] };
4642
+ }
4643
+ function normalizeAssetsField(value, baseUrl) {
4644
+ if (!value || typeof value !== "object") return void 0;
4645
+ const out = {};
4646
+ for (const [key, raw] of Object.entries(value)) {
4647
+ if (!raw || typeof raw !== "object") continue;
4648
+ const asset = raw;
4649
+ if (typeof asset.href !== "string" || !asset.href) continue;
4650
+ out[key] = {
4651
+ ...asset,
4652
+ href: resolveStacAssetHref(asset.href, baseUrl)
4653
+ };
4654
+ }
4655
+ return Object.keys(out).length > 0 ? out : void 0;
4656
+ }
4657
+ function coerceDatetimeToIso(value) {
4658
+ if (value == null) return void 0;
4659
+ if (value instanceof Date) {
4660
+ const ms = value.getTime();
4661
+ return Number.isFinite(ms) ? new Date(ms).toISOString() : void 0;
4662
+ }
4663
+ if (typeof value === "bigint") {
4664
+ const ms = Number(value / 1000n);
4665
+ return Number.isFinite(ms) ? new Date(ms).toISOString() : void 0;
4666
+ }
4667
+ if (typeof value === "number" && Number.isFinite(value)) {
4668
+ const ms = Math.abs(value) > 1e14 ? Math.floor(value / 1e3) : value;
4669
+ return new Date(ms).toISOString();
4670
+ }
4671
+ if (typeof value === "string") {
4672
+ const t = Date.parse(value);
4673
+ return Number.isFinite(t) ? new Date(t).toISOString() : void 0;
4674
+ }
4675
+ return void 0;
4676
+ }
4677
+ function normalizeLinksField(value, baseUrl) {
4678
+ if (!Array.isArray(value)) return void 0;
4679
+ const links = [];
4680
+ for (const raw of value) {
4681
+ if (!raw || typeof raw !== "object") continue;
4682
+ const link = raw;
4683
+ if (typeof link.href !== "string" || typeof link.rel !== "string") continue;
4684
+ links.push({ ...link, href: resolveStacAssetHref(link.href, baseUrl) });
4685
+ }
4686
+ return links.length > 0 ? links : void 0;
4687
+ }
4688
+ function stacRowToItem(row, baseUrl, opts = {}) {
4689
+ const { wkbParser, wkbColumn = "geom_wkb", geometryColumn = "geometry" } = opts;
4690
+ for (const key of ["bbox", "assets", "links", "bands", "stac_extensions"]) {
4691
+ if (row[key] !== void 0 && row[key] !== null) {
4692
+ row[key] = plainifyArrowValue(row[key]);
4693
+ }
4694
+ }
4695
+ let geometry = row[geometryColumn];
4696
+ if (!geometry) {
4697
+ const wkb = row[wkbColumn];
4698
+ if (wkb && wkbParser) {
4699
+ const bytes = wkb instanceof Uint8Array ? wkb : toUint8Array(wkb);
4700
+ if (bytes) {
4701
+ try {
4702
+ geometry = wkbParser(bytes) ?? void 0;
4703
+ } catch {
4704
+ geometry = void 0;
4705
+ }
4706
+ }
4707
+ }
4708
+ }
4709
+ const bbox = flattenStacBbox(row.bbox) ?? void 0;
4710
+ const assets = normalizeAssetsField(row.assets, baseUrl);
4711
+ const links = normalizeLinksField(row.links, baseUrl);
4712
+ const properties = {};
4713
+ const SCALAR_PROP_KEYS = /* @__PURE__ */ new Set([
4714
+ "gsd",
4715
+ "platform",
4716
+ "constellation",
4717
+ "instruments",
4718
+ "mission",
4719
+ "license"
4720
+ ]);
4721
+ const TIMESTAMP_PROP_KEYS = /* @__PURE__ */ new Set([
4722
+ "datetime",
4723
+ "start_datetime",
4724
+ "end_datetime",
4725
+ "created",
4726
+ "updated"
4727
+ ]);
4728
+ for (const [key, value] of Object.entries(row)) {
4729
+ if (value === null || value === void 0) continue;
4730
+ if (key.startsWith("proj:") || key.startsWith("raster:") || key.startsWith("eo:")) {
4731
+ properties[key] = value;
4732
+ continue;
4733
+ }
4734
+ if (TIMESTAMP_PROP_KEYS.has(key)) {
4735
+ const iso = coerceDatetimeToIso(value);
4736
+ if (iso) properties[key] = iso;
4737
+ continue;
4738
+ }
4739
+ if (SCALAR_PROP_KEYS.has(key)) {
4740
+ properties[key] = value;
4741
+ continue;
4742
+ }
4743
+ if (key === "bands") {
4744
+ properties.bands = value;
4745
+ }
4746
+ }
4747
+ const item = {
4748
+ type: "Feature",
4749
+ stac_version: typeof row.stac_version === "string" ? row.stac_version : "1.0.0",
4750
+ id: typeof row.id === "string" ? row.id : String(row.id ?? ""),
4751
+ properties
4752
+ };
4753
+ if (typeof row.collection === "string") item.collection = row.collection;
4754
+ if (Array.isArray(row.stac_extensions)) {
4755
+ item.stac_extensions = row.stac_extensions;
4756
+ }
4757
+ if (bbox) item.bbox = bbox;
4758
+ if (geometry) item.geometry = geometry;
4759
+ if (assets) item.assets = assets;
4760
+ if (links) item.links = links;
4761
+ return item;
4762
+ }
4763
+ function toUint8Array(value) {
4764
+ if (value instanceof Uint8Array) return value;
4765
+ if (value instanceof ArrayBuffer) return new Uint8Array(value);
4766
+ if (ArrayBuffer.isView(value)) {
4767
+ const view = value;
4768
+ return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
4769
+ }
4770
+ if (Array.isArray(value)) return new Uint8Array(value);
4771
+ return null;
4772
+ }
4773
+
4774
+ // src/stac-hydrate.ts
4775
+ async function hydrateStacItems(root, baseHref, adapter, opts) {
4776
+ const limit = opts.limit ?? 2e3;
4777
+ const concurrency = opts.concurrency ?? 12;
4778
+ const followPagination = opts.followPagination ?? true;
4779
+ const signal = opts.signal;
4780
+ const urlToKey = opts.urlToKey;
4781
+ const items = [];
4782
+ let truncated = false;
4783
+ const emit = (batch) => {
4784
+ if (batch.length === 0) return;
4785
+ items.push(...batch);
4786
+ opts.onBatch?.(batch);
4787
+ opts.onProgress?.(items.length, void 0);
4788
+ };
4789
+ if (root.kind === "item") {
4790
+ emit([absolutizeItemAssets(root.item, baseHref)]);
4791
+ return { items, truncated: false, rootBaseHref: baseHref };
4792
+ }
4793
+ if (root.kind === "item-collection") {
4794
+ await consumeFeatureCollection(root.fc, baseHref, adapter, {
4795
+ signal,
4796
+ concurrency,
4797
+ limit,
4798
+ followPagination,
4799
+ urlToKey,
4800
+ itemsQuery: opts.itemsQuery,
4801
+ onAccept: (batch) => emit(batch),
4802
+ stopCheck: () => items.length >= limit,
4803
+ onTruncate: () => {
4804
+ truncated = true;
4805
+ }
4806
+ });
4807
+ return { items: items.slice(0, limit), truncated, rootBaseHref: baseHref };
4808
+ }
4809
+ if (root.kind === "collection" || root.kind === "catalog") {
4810
+ const itemLinks = collectItemLinks(root.payload, baseHref);
4811
+ const childLinks = root.kind === "catalog" || itemLinks.length === 0 ? collectChildLinks(root.payload, baseHref) : [];
4812
+ const itemsEndpoint = itemLinks.length === 0 ? collectItemsEndpoint(root.payload, baseHref, opts.itemsQuery) : null;
4813
+ if (itemLinks.length > 0) {
4814
+ await fetchItems(itemLinks, adapter, baseHref, {
4815
+ signal,
4816
+ concurrency,
4817
+ urlToKey,
4818
+ onBatch: (batch) => emit(batch),
4819
+ stopCheck: () => items.length >= limit,
4820
+ onTruncate: () => {
4821
+ truncated = true;
4822
+ }
4823
+ });
4824
+ } else if (itemsEndpoint) {
4825
+ try {
4826
+ const json = await fetchJson(adapter, itemsEndpoint, baseHref, signal, urlToKey);
4827
+ if (isStacFeatureCollection(json)) {
4828
+ await consumeFeatureCollection(json, itemsEndpoint, adapter, {
4829
+ signal,
4830
+ concurrency,
4831
+ limit,
4832
+ followPagination,
4833
+ urlToKey,
4834
+ itemsQuery: opts.itemsQuery,
4835
+ onAccept: (batch) => emit(batch),
4836
+ stopCheck: () => items.length >= limit,
4837
+ onTruncate: () => {
4838
+ truncated = true;
4839
+ }
4840
+ });
4841
+ }
4842
+ } catch {
2998
4843
  }
2999
- const azureBlob = host.match(AZURE_BLOB_RE);
3000
- if (azureBlob && pathParts.length > 0) {
3001
- return {
3002
- bucket: pathParts[0],
3003
- region: defaults.region || "",
3004
- endpoint: `${url.protocol}//${url.host}`,
3005
- provider: "azure",
3006
- prefix: pathParts.slice(1).join("/")
3007
- };
4844
+ }
4845
+ if (!truncated && items.length < limit && childLinks.length > 0) {
4846
+ const queue = [...childLinks];
4847
+ const stopCheck = () => items.length >= limit || signal.aborted;
4848
+ const workerCount = Math.min(concurrency, queue.length);
4849
+ const workers = [];
4850
+ for (let i = 0; i < workerCount; i++) {
4851
+ workers.push(
4852
+ (async () => {
4853
+ while (queue.length > 0) {
4854
+ if (stopCheck()) {
4855
+ if (items.length >= limit) truncated = true;
4856
+ queue.length = 0;
4857
+ return;
4858
+ }
4859
+ const childHref = queue.shift();
4860
+ if (!childHref) return;
4861
+ try {
4862
+ const childJson = await fetchJson(adapter, childHref, baseHref, signal, urlToKey);
4863
+ if (stopCheck()) {
4864
+ if (items.length >= limit) truncated = true;
4865
+ queue.length = 0;
4866
+ return;
4867
+ }
4868
+ const childKind = classifyFetchedJson(childJson);
4869
+ if (childKind.kind === "none") continue;
4870
+ const sub = await hydrateStacItems(childKind, childHref, adapter, {
4871
+ ...opts,
4872
+ limit: limit - items.length,
4873
+ onBatch: (batch) => emit(batch),
4874
+ onProgress: void 0
4875
+ });
4876
+ if (sub.truncated) truncated = true;
4877
+ } catch {
4878
+ }
4879
+ }
4880
+ })()
4881
+ );
3008
4882
  }
3009
- const storjGateway = host.match(STORJ_GATEWAY_RE);
3010
- if (storjGateway && pathParts.length > 0) {
3011
- return {
3012
- bucket: pathParts[0],
3013
- region: storjGateway[1] || defaults.region || "us1",
3014
- endpoint: `${url.protocol}//${url.host}`,
3015
- provider: "storj",
3016
- prefix: pathParts.slice(1).join("/")
3017
- };
4883
+ await Promise.all(workers);
4884
+ }
4885
+ return { items: items.slice(0, limit), truncated, rootBaseHref: baseHref };
4886
+ }
4887
+ return { items, truncated: false, rootBaseHref: baseHref };
4888
+ }
4889
+ function collectItemLinks(payload, baseHref) {
4890
+ return (payload.links ?? []).filter((l) => !!l && typeof l.href === "string" && l.rel === "item").map((l) => absolutizeHref(l.href, baseHref));
4891
+ }
4892
+ function collectChildLinks(payload, baseHref) {
4893
+ return (payload.links ?? []).filter((l) => !!l && typeof l.href === "string" && l.rel === "child").map((l) => absolutizeHref(l.href, baseHref));
4894
+ }
4895
+ function hasStacItemsEndpoint(payload) {
4896
+ return (payload.links ?? []).some((l) => !!l && typeof l.href === "string" && l.rel === "items");
4897
+ }
4898
+ function collectItemsEndpoint(payload, baseHref, query) {
4899
+ const link = (payload.links ?? []).find(
4900
+ (l) => !!l && typeof l.href === "string" && l.rel === "items"
4901
+ );
4902
+ if (!link) return null;
4903
+ return applyItemsQuery(absolutizeHref(link.href, baseHref), query);
4904
+ }
4905
+ function applyItemsQuery(absolute, query) {
4906
+ if (!query) return absolute;
4907
+ try {
4908
+ const url = new URL(absolute);
4909
+ if (query.bbox && query.bbox.length === 4 && !url.searchParams.has("bbox")) {
4910
+ url.searchParams.set("bbox", query.bbox.join(","));
4911
+ }
4912
+ if (query.datetime && !url.searchParams.has("datetime")) {
4913
+ url.searchParams.set("datetime", query.datetime);
4914
+ }
4915
+ if (typeof query.limit === "number" && query.limit > 0 && !url.searchParams.has("limit")) {
4916
+ url.searchParams.set("limit", String(Math.floor(query.limit)));
4917
+ }
4918
+ if (query.filter !== void 0 && query.filter !== null && !url.searchParams.has("filter")) {
4919
+ try {
4920
+ url.searchParams.set("filter", JSON.stringify(query.filter));
4921
+ if (!url.searchParams.has("filter-lang")) {
4922
+ url.searchParams.set("filter-lang", "cql2-json");
4923
+ }
4924
+ } catch {
3018
4925
  }
3019
- const storjLink = host.match(STORJ_LINK_RE);
3020
- if (storjLink && pathParts.length >= 3 && (pathParts[0] === "raw" || pathParts[0] === "s")) {
3021
- return {
3022
- bucket: pathParts[2],
3023
- region: storjLink[1] || defaults.region || "us1",
3024
- endpoint: `${url.protocol}//${url.host}/${pathParts[0]}/${pathParts[1]}`,
3025
- provider: "storj",
3026
- prefix: pathParts.slice(3).join("/")
3027
- };
4926
+ }
4927
+ return url.toString();
4928
+ } catch {
4929
+ return absolute;
4930
+ }
4931
+ }
4932
+ async function consumeFeatureCollection(fc, baseHref, adapter, ctx) {
4933
+ const accepted = fc.features.filter(isStacItem).map((item) => absolutizeItemAssets(item, baseHref));
4934
+ ctx.onAccept(accepted);
4935
+ if (ctx.stopCheck()) {
4936
+ ctx.onTruncate();
4937
+ return;
4938
+ }
4939
+ if (!ctx.followPagination) return;
4940
+ const next = (fc.links ?? []).find((l) => l && l.rel === "next" && typeof l.href === "string");
4941
+ if (!next) return;
4942
+ const nextHref = applyItemsQuery(absolutizeHref(next.href, baseHref), ctx.itemsQuery);
4943
+ if (ctx.signal.aborted) return;
4944
+ try {
4945
+ const json = await fetchJson(adapter, nextHref, baseHref, ctx.signal, ctx.urlToKey);
4946
+ if (!isStacFeatureCollection(json)) return;
4947
+ await consumeFeatureCollection(json, nextHref, adapter, ctx);
4948
+ } catch {
4949
+ }
4950
+ }
4951
+ async function fetchItems(hrefs, adapter, baseHref, ctx) {
4952
+ const queue = [...hrefs];
4953
+ const workers = [];
4954
+ const workerCount = Math.min(ctx.concurrency, queue.length);
4955
+ for (let i = 0; i < workerCount; i++) {
4956
+ workers.push(
4957
+ (async () => {
4958
+ while (queue.length > 0) {
4959
+ if (ctx.signal.aborted) return;
4960
+ if (ctx.stopCheck()) {
4961
+ ctx.onTruncate();
4962
+ queue.length = 0;
4963
+ return;
4964
+ }
4965
+ const href = queue.shift();
4966
+ if (!href) return;
4967
+ try {
4968
+ const json = await fetchJson(adapter, href, baseHref, ctx.signal, ctx.urlToKey);
4969
+ if (isStacItem(json)) ctx.onBatch([absolutizeItemAssets(json, href)]);
4970
+ } catch {
4971
+ }
4972
+ }
4973
+ })()
4974
+ );
4975
+ }
4976
+ await Promise.all(workers);
4977
+ }
4978
+ async function fetchJson(adapter, href, _baseHref, signal, urlToKey) {
4979
+ if (/^https?:/i.test(href)) {
4980
+ const ownKey = urlToKey ? urlToKey(href) : null;
4981
+ if (ownKey !== null) {
4982
+ const buf2 = await adapter.read(ownKey, void 0, void 0, signal);
4983
+ return JSON.parse(new TextDecoder().decode(buf2));
4984
+ }
4985
+ const res = await fetch(href, { signal });
4986
+ if (!res.ok) throw new Error(`HTTP ${res.status} for ${href}`);
4987
+ return await res.json();
4988
+ }
4989
+ const buf = await adapter.read(href, void 0, void 0, signal);
4990
+ return JSON.parse(new TextDecoder().decode(buf));
4991
+ }
4992
+ function classifyFetchedJson(json) {
4993
+ if (isStacItem(json)) return { kind: "item", item: json };
4994
+ if (isStacFeatureCollection(json)) return { kind: "item-collection", fc: json };
4995
+ if (isStacCollection(json)) return { kind: "collection", payload: json };
4996
+ if (isStacCatalog(json)) return { kind: "catalog", payload: json };
4997
+ return { kind: "none" };
4998
+ }
4999
+ function absolutizeHref(href, baseHref) {
5000
+ if (/^https?:/i.test(href) || /^s3:/i.test(href) || /^azure:/i.test(href)) return href;
5001
+ try {
5002
+ return new URL(href, baseHref).toString();
5003
+ } catch {
5004
+ return href;
5005
+ }
5006
+ }
5007
+ function absolutizeItemAssets(item, itemHref) {
5008
+ const assets = item.assets;
5009
+ if (!assets) return item;
5010
+ let changed = false;
5011
+ const resolved = {};
5012
+ for (const [key, asset] of Object.entries(assets)) {
5013
+ if (asset?.href && typeof asset.href === "string") {
5014
+ const abs = absolutizeHref(asset.href, itemHref);
5015
+ if (abs !== asset.href) changed = true;
5016
+ resolved[key] = { ...asset, href: abs };
5017
+ } else {
5018
+ resolved[key] = asset;
5019
+ }
5020
+ }
5021
+ return changed ? { ...item, assets: resolved } : item;
5022
+ }
5023
+
5024
+ // src/stac-pushdown.ts
5025
+ var CONFORMANCE_SIGNATURES = [
5026
+ {
5027
+ key: "bbox",
5028
+ matchers: [/ogc-api[/-]features.*\/conf\/core/i, /stac-api.*\/item-search/i]
5029
+ },
5030
+ {
5031
+ key: "datetime",
5032
+ matchers: [/ogc-api[/-]features.*\/conf\/core/i, /stac-api.*\/item-search/i]
5033
+ },
5034
+ {
5035
+ key: "collections",
5036
+ matchers: [/stac-api.*\/item-search/i]
5037
+ },
5038
+ {
5039
+ key: "cql2",
5040
+ matchers: [/stac-api.*\/item-search.*\/filter/i, /ogc-api[/-]features.*\/cql2/i, /cql2-json/i]
5041
+ },
5042
+ {
5043
+ key: "queryables",
5044
+ matchers: [/stac-api.*\/item-search.*\/queryables/i, /ogc-api[/-]features.*\/queryables/i]
5045
+ }
5046
+ ];
5047
+ function sniffApiCapabilities(conformsTo) {
5048
+ const caps = {
5049
+ bbox: false,
5050
+ datetime: false,
5051
+ collections: false,
5052
+ cql2: false,
5053
+ queryables: false
5054
+ };
5055
+ if (!Array.isArray(conformsTo)) return caps;
5056
+ const normalised = conformsTo.filter((s) => typeof s === "string");
5057
+ for (const sig of CONFORMANCE_SIGNATURES) {
5058
+ if (normalised.some((uri) => sig.matchers.some((re) => re.test(uri)))) {
5059
+ caps[sig.key] = true;
5060
+ }
5061
+ }
5062
+ return caps;
5063
+ }
5064
+ function toNativeQuery(state, caps, opts = {}) {
5065
+ const out = {};
5066
+ if (opts.bbox && caps.bbox) out.bbox = opts.bbox;
5067
+ if (typeof opts.limit === "number" && opts.limit > 0) out.limit = Math.floor(opts.limit);
5068
+ if (!state) return out;
5069
+ if (caps.datetime && state.datetime && (state.datetime.min || state.datetime.max)) {
5070
+ out.datetime = formatDatetimeInterval(state.datetime.min, state.datetime.max);
5071
+ }
5072
+ if (caps.collections && state.enums?.collection?.length) {
5073
+ out.collections = [...state.enums.collection];
5074
+ }
5075
+ if (caps.cql2) {
5076
+ const filter = toCql2Filter(state, caps);
5077
+ if (filter) {
5078
+ out.filter = filter;
5079
+ out["filter-lang"] = "cql2-json";
5080
+ }
5081
+ }
5082
+ return out;
5083
+ }
5084
+ function formatDatetimeInterval(min, max) {
5085
+ const lo = min ?? "..";
5086
+ const hi = max ?? "..";
5087
+ if (lo === ".." && hi === "..") return "..";
5088
+ if (lo === hi) return lo;
5089
+ return `${lo}/${hi}`;
5090
+ }
5091
+ function toCql2Filter(state, caps) {
5092
+ if (!state) return null;
5093
+ const clauses = [];
5094
+ if (state.numeric) {
5095
+ const n = state.numeric;
5096
+ pushBetween(clauses, "eo:cloud_cover", n.cloudCover);
5097
+ pushBetween(clauses, "gsd", n.gsd);
5098
+ }
5099
+ if (state.enums) {
5100
+ const e = state.enums;
5101
+ pushIn(clauses, "platform", e.platform);
5102
+ pushIn(clauses, "constellation", e.constellation);
5103
+ pushOverlap(clauses, "instruments", e.instruments);
5104
+ if (!caps.collections) pushIn(clauses, "collection", e.collection);
5105
+ }
5106
+ if (!caps.datetime && state.datetime && (state.datetime.min || state.datetime.max)) {
5107
+ const lo = state.datetime.min;
5108
+ const hi = state.datetime.max;
5109
+ if (lo && hi) {
5110
+ clauses.push({
5111
+ op: "t_intersects",
5112
+ args: [{ property: "datetime" }, { interval: [lo, hi] }]
5113
+ });
5114
+ } else if (lo) {
5115
+ clauses.push({ op: ">=", args: [{ property: "datetime" }, { timestamp: lo }] });
5116
+ } else if (hi) {
5117
+ clauses.push({ op: "<=", args: [{ property: "datetime" }, { timestamp: hi }] });
5118
+ }
5119
+ }
5120
+ if (clauses.length === 0) return null;
5121
+ if (clauses.length === 1) return clauses[0];
5122
+ return { op: "and", args: clauses };
5123
+ }
5124
+ function pushBetween(out, property, range) {
5125
+ if (!range) return;
5126
+ const { min, max } = range;
5127
+ if (min == null && max == null) return;
5128
+ if (min != null && max != null) {
5129
+ out.push({ op: "between", args: [{ property }, [min, max]] });
5130
+ return;
5131
+ }
5132
+ if (min != null) out.push({ op: ">=", args: [{ property }, min] });
5133
+ if (max != null) out.push({ op: "<=", args: [{ property }, max] });
5134
+ }
5135
+ function pushIn(out, property, values) {
5136
+ if (!values || values.length === 0) return;
5137
+ if (values.length === 1) {
5138
+ out.push({ op: "=", args: [{ property }, values[0]] });
5139
+ return;
5140
+ }
5141
+ out.push({ op: "in", args: [{ property }, values] });
5142
+ }
5143
+ function pushOverlap(out, property, values) {
5144
+ if (!values || values.length === 0) return;
5145
+ out.push({ op: "a_overlaps", args: [{ property }, values] });
5146
+ }
5147
+ function residualState(state, caps) {
5148
+ if (!state) return {};
5149
+ const out = {};
5150
+ if (state.datetime && !caps.datetime && !caps.cql2) out.datetime = state.datetime;
5151
+ else if (state.datetime && (caps.datetime || caps.cql2)) ;
5152
+ if (state.numeric) {
5153
+ const remaining = {};
5154
+ for (const [field, range] of Object.entries(state.numeric)) {
5155
+ if (!range) continue;
5156
+ if (caps.cql2) continue;
5157
+ remaining[field] = range;
5158
+ }
5159
+ if (Object.keys(remaining).length > 0) out.numeric = remaining;
5160
+ }
5161
+ if (state.enums) {
5162
+ const remaining = {};
5163
+ for (const [field, values] of Object.entries(state.enums)) {
5164
+ if (!Array.isArray(values) || values.length === 0) continue;
5165
+ if (field === "collection" && caps.collections) continue;
5166
+ if (caps.cql2 && (field === "platform" || field === "constellation" || field === "instruments")) {
5167
+ continue;
3028
5168
  }
3029
- if (STAC_API_PATH_RE.test(url.pathname)) {
3030
- return {
3031
- ...defaultResult(defaults),
3032
- endpoint: `${url.protocol}//${url.host}`
5169
+ remaining[field] = values;
5170
+ }
5171
+ if (Object.keys(remaining).length > 0) out.enums = remaining;
5172
+ }
5173
+ return out;
5174
+ }
5175
+
5176
+ // src/stac-source.ts
5177
+ function emptyPushdown() {
5178
+ return {
5179
+ bbox: false,
5180
+ datetime: false,
5181
+ collection: false,
5182
+ cloudCover: false,
5183
+ gsd: false,
5184
+ epsg: false,
5185
+ platform: false,
5186
+ constellation: false,
5187
+ instruments: false,
5188
+ assetRoles: false
5189
+ };
5190
+ }
5191
+
5192
+ // src/stac-source-api.ts
5193
+ function subtractState(full, residual) {
5194
+ if (!full) return {};
5195
+ const out = {};
5196
+ if (full.datetime && !residual.datetime) out.datetime = full.datetime;
5197
+ if (full.numeric) {
5198
+ const kept = {};
5199
+ const residualNumeric = residual.numeric ?? {};
5200
+ for (const [k, v] of Object.entries(full.numeric)) {
5201
+ if (!v) continue;
5202
+ if (residualNumeric[k]) continue;
5203
+ kept[k] = v;
5204
+ }
5205
+ if (Object.keys(kept).length > 0) out.numeric = kept;
5206
+ }
5207
+ if (full.enums) {
5208
+ const kept = {};
5209
+ const residualEnums = residual.enums ?? {};
5210
+ for (const [k, v] of Object.entries(full.enums)) {
5211
+ if (!Array.isArray(v) || v.length === 0) continue;
5212
+ if (Array.isArray(residualEnums[k])) continue;
5213
+ kept[k] = v;
5214
+ }
5215
+ if (Object.keys(kept).length > 0) out.enums = kept;
5216
+ }
5217
+ return out;
5218
+ }
5219
+ function datetimeFacetToRfc3339(dt) {
5220
+ if (!dt) return void 0;
5221
+ const lo = dt.min;
5222
+ const hi = dt.max;
5223
+ if (!lo && !hi) return void 0;
5224
+ if (lo && hi) return lo === hi ? lo : `${lo}/${hi}`;
5225
+ if (lo) return `${lo}/..`;
5226
+ return `../${hi}`;
5227
+ }
5228
+ function createApiSource(kind, deps) {
5229
+ const capabilities = {
5230
+ kind: "api",
5231
+ label: "STAC API",
5232
+ countAvailable: false,
5233
+ streaming: true,
5234
+ pushdown: {
5235
+ ...emptyPushdown(),
5236
+ bbox: true,
5237
+ datetime: true,
5238
+ collection: true,
5239
+ cloudCover: true,
5240
+ gsd: true,
5241
+ platform: true,
5242
+ constellation: true,
5243
+ instruments: true
5244
+ }
5245
+ };
5246
+ let capsPromise = null;
5247
+ const getCaps = (signal) => {
5248
+ if (!capsPromise) {
5249
+ capsPromise = sniffSourceCapabilities(kind, deps, signal).catch(() => SLICE_1_CAPS);
5250
+ }
5251
+ return capsPromise;
5252
+ };
5253
+ return {
5254
+ capabilities,
5255
+ query(req) {
5256
+ return apiQueryIterable(kind, deps, req, getCaps);
5257
+ }
5258
+ };
5259
+ }
5260
+ var SLICE_1_CAPS = {
5261
+ bbox: true,
5262
+ datetime: true,
5263
+ collections: false,
5264
+ cql2: false,
5265
+ queryables: false
5266
+ };
5267
+ async function sniffSourceCapabilities(kind, deps, signal) {
5268
+ const inline = readConformsTo(kind);
5269
+ if (inline && inline.length > 0) return sniffApiCapabilities(inline);
5270
+ const json = await fetchRootJson(deps, signal);
5271
+ if (json && typeof json === "object") {
5272
+ const conformsTo = json.conformsTo;
5273
+ if (Array.isArray(conformsTo)) return sniffApiCapabilities(conformsTo);
5274
+ }
5275
+ return SLICE_1_CAPS;
5276
+ }
5277
+ function readConformsTo(kind) {
5278
+ if (kind.kind === "catalog" || kind.kind === "collection") {
5279
+ const ct = kind.payload.conformsTo;
5280
+ return Array.isArray(ct) ? ct : null;
5281
+ }
5282
+ if (kind.kind === "item-collection") {
5283
+ const ct = kind.fc.conformsTo;
5284
+ return Array.isArray(ct) ? ct : null;
5285
+ }
5286
+ return null;
5287
+ }
5288
+ async function fetchRootJson(deps, signal) {
5289
+ const href = deps.baseHref;
5290
+ if (/^https?:/i.test(href)) {
5291
+ const ownKey = deps.urlToKey ? deps.urlToKey(href) : null;
5292
+ if (ownKey !== null) {
5293
+ const buf2 = await deps.adapter.read(ownKey, void 0, void 0, signal);
5294
+ return JSON.parse(new TextDecoder().decode(buf2));
5295
+ }
5296
+ const res = await fetch(href, { signal });
5297
+ if (!res.ok) throw new Error(`HTTP ${res.status} for ${href}`);
5298
+ return await res.json();
5299
+ }
5300
+ const buf = await deps.adapter.read(href, void 0, void 0, signal);
5301
+ return JSON.parse(new TextDecoder().decode(buf));
5302
+ }
5303
+ async function* apiQueryIterable(kind, deps, req, getCaps) {
5304
+ if (req.signal.aborted) throw new DOMException("Aborted", "AbortError");
5305
+ const caps = await getCaps(req.signal);
5306
+ if (req.signal.aborted) throw new DOMException("Aborted", "AbortError");
5307
+ const native = toNativeQuery(req.filter, caps, {
5308
+ bbox: req.bbox,
5309
+ limit: req.pageSize ?? req.limit
5310
+ });
5311
+ const datetime = native.datetime ?? datetimeFacetToRfc3339(req.filter.datetime);
5312
+ const itemsQuery = {
5313
+ bbox: native.bbox ?? req.bbox,
5314
+ datetime,
5315
+ limit: native.limit ?? req.pageSize ?? req.limit,
5316
+ filter: native.filter
5317
+ };
5318
+ const residual = residualState(req.filter, caps);
5319
+ const pushedDown = subtractState(req.filter, residual);
5320
+ if (!caps.cql2) {
5321
+ itemsQuery.filter = void 0;
5322
+ } else if (itemsQuery.filter === void 0) {
5323
+ const cql = toCql2Filter(req.filter, caps);
5324
+ if (cql) itemsQuery.filter = cql;
5325
+ }
5326
+ const buffer = [];
5327
+ let pendingResolve = null;
5328
+ const push = (s) => {
5329
+ if (pendingResolve) {
5330
+ const r = pendingResolve;
5331
+ pendingResolve = null;
5332
+ r(s);
5333
+ } else {
5334
+ buffer.push(s);
5335
+ }
5336
+ };
5337
+ const next = () => {
5338
+ if (buffer.length > 0) return Promise.resolve(buffer.shift());
5339
+ return new Promise((resolve) => {
5340
+ pendingResolve = resolve;
5341
+ });
5342
+ };
5343
+ const onAbort = () => push({ kind: "error", error: new DOMException("Aborted", "AbortError") });
5344
+ req.signal.addEventListener("abort", onAbort, { once: true });
5345
+ void (async () => {
5346
+ try {
5347
+ await hydrateStacItems(kind, deps.baseHref, deps.adapter, {
5348
+ signal: req.signal,
5349
+ concurrency: deps.concurrency ?? 12,
5350
+ limit: req.limit,
5351
+ urlToKey: deps.urlToKey,
5352
+ itemsQuery,
5353
+ onBatch: (batch) => {
5354
+ if (batch.length > 0) push({ kind: "value", batch });
5355
+ }
5356
+ });
5357
+ push({ kind: "done" });
5358
+ } catch (err) {
5359
+ push({ kind: "error", error: err });
5360
+ }
5361
+ })();
5362
+ try {
5363
+ while (true) {
5364
+ const state = await next();
5365
+ if (state.kind === "value") {
5366
+ yield {
5367
+ items: state.batch,
5368
+ pushedDown,
5369
+ residual,
5370
+ done: false
3033
5371
  };
5372
+ continue;
3034
5373
  }
3035
- if (pathParts.length > 0) {
3036
- const endpoint = `${url.protocol}//${url.host}`;
3037
- return {
3038
- bucket: pathParts[0],
3039
- region: defaults.region || "us-east-1",
3040
- endpoint,
3041
- provider: defaults.provider || "s3",
3042
- prefix: pathParts.slice(1).join("/")
5374
+ if (state.kind === "done") {
5375
+ yield {
5376
+ items: [],
5377
+ pushedDown,
5378
+ residual,
5379
+ done: true
3043
5380
  };
5381
+ return;
3044
5382
  }
3045
- return {
3046
- ...defaultResult(defaults),
3047
- endpoint: `${url.protocol}//${url.host}`
3048
- };
3049
- } catch {
5383
+ throw state.error;
3050
5384
  }
5385
+ } finally {
5386
+ req.signal.removeEventListener("abort", onAbort);
3051
5387
  }
3052
- const cleaned = trimmed.replace(/^\/+|\/+$/g, "");
5388
+ }
5389
+
5390
+ // src/stac-source-static.ts
5391
+ function createStaticSource(kind, deps) {
5392
+ const capabilities = {
5393
+ kind: "static",
5394
+ label: "Static catalog",
5395
+ countAvailable: false,
5396
+ streaming: true,
5397
+ pushdown: { ...emptyPushdown() }
5398
+ };
3053
5399
  return {
3054
- bucket: cleaned,
3055
- region: defaults.region || "us-east-1",
3056
- endpoint: defaults.endpoint || "",
3057
- provider: defaults.provider || "s3",
3058
- prefix: ""
5400
+ capabilities,
5401
+ query(req) {
5402
+ return staticQueryIterable(kind, deps, req);
5403
+ }
3059
5404
  };
3060
5405
  }
3061
- function looksLikeUrl(input) {
3062
- const lower = input.trim().toLowerCase();
3063
- if (lower.startsWith("http://") || lower.startsWith("https://")) return true;
3064
- for (const scheme of Object.keys(SCHEME_MAP)) {
3065
- if (lower.startsWith(scheme)) return true;
5406
+ async function* staticQueryIterable(kind, deps, req) {
5407
+ if (req.signal.aborted) throw new DOMException("Aborted", "AbortError");
5408
+ const pushedDown = {};
5409
+ const residual = req.filter;
5410
+ const buffer = [];
5411
+ let pendingResolve = null;
5412
+ const push = (s) => {
5413
+ if (pendingResolve) {
5414
+ const r = pendingResolve;
5415
+ pendingResolve = null;
5416
+ r(s);
5417
+ } else {
5418
+ buffer.push(s);
5419
+ }
5420
+ };
5421
+ const next = () => {
5422
+ if (buffer.length > 0) return Promise.resolve(buffer.shift());
5423
+ return new Promise((resolve) => {
5424
+ pendingResolve = resolve;
5425
+ });
5426
+ };
5427
+ const onAbort = () => push({ kind: "error", error: new DOMException("Aborted", "AbortError") });
5428
+ req.signal.addEventListener("abort", onAbort, { once: true });
5429
+ void (async () => {
5430
+ try {
5431
+ await hydrateStacItems(kind, deps.baseHref, deps.adapter, {
5432
+ signal: req.signal,
5433
+ concurrency: deps.concurrency ?? 12,
5434
+ limit: req.limit,
5435
+ urlToKey: deps.urlToKey,
5436
+ onBatch: (batch) => {
5437
+ if (batch.length > 0) push({ kind: "value", batch });
5438
+ }
5439
+ });
5440
+ push({ kind: "done" });
5441
+ } catch (err) {
5442
+ push({ kind: "error", error: err });
5443
+ }
5444
+ })();
5445
+ try {
5446
+ while (true) {
5447
+ const state = await next();
5448
+ if (state.kind === "value") {
5449
+ yield {
5450
+ items: state.batch,
5451
+ pushedDown,
5452
+ residual,
5453
+ done: false
5454
+ };
5455
+ continue;
5456
+ }
5457
+ if (state.kind === "done") {
5458
+ yield { items: [], pushedDown, residual, done: true };
5459
+ return;
5460
+ }
5461
+ throw state.error;
5462
+ }
5463
+ } finally {
5464
+ req.signal.removeEventListener("abort", onAbort);
3066
5465
  }
3067
- return false;
3068
5466
  }
3069
- function describeParseResult(parsed) {
3070
- const parts = [];
3071
- if (parsed.bucket) parts.push(`bucket="${parsed.bucket}"`);
3072
- if (parsed.endpoint) parts.push(`endpoint="${parsed.endpoint}"`);
3073
- if (parsed.region && parsed.region !== "us-east-1") parts.push(`region="${parsed.region}"`);
3074
- if (parsed.provider !== "s3") parts.push(`provider=${parsed.provider}`);
3075
- if (parsed.prefix) parts.push(`prefix="${parsed.prefix}"`);
3076
- return parts.length > 0 ? `Detected: ${parts.join(", ")}` : "";
5467
+
5468
+ // src/storage-smoketest.ts
5469
+ async function smokeTestHref(href, signal) {
5470
+ let response;
5471
+ try {
5472
+ response = await fetch(href, {
5473
+ method: "GET",
5474
+ headers: { Range: "bytes=0-0" },
5475
+ signal,
5476
+ redirect: "follow"
5477
+ });
5478
+ } catch (err) {
5479
+ if (err instanceof DOMException && err.name === "AbortError") throw err;
5480
+ const reason2 = err instanceof Error ? err.message : String(err);
5481
+ return { ok: false, status: null, reason: reason2 };
5482
+ }
5483
+ if (response.ok || response.status === 206) {
5484
+ await response.body?.cancel().catch(() => {
5485
+ });
5486
+ return { ok: true };
5487
+ }
5488
+ const reason = `${response.status} ${response.statusText || ""}`.trim();
5489
+ return { ok: false, status: response.status, reason };
3077
5490
  }
3078
5491
 
3079
- // ../../src/lib/utils/wkb.ts
5492
+ // src/wkb.ts
3080
5493
  var WKB_POINT = 1;
3081
5494
  var WKB_LINESTRING = 2;
3082
5495
  var WKB_POLYGON = 3;
@@ -3331,6 +5744,6 @@ function isWKT(value) {
3331
5744
  return WKT_TYPES.some((t) => s.startsWith(t) || s.startsWith(`MULTI${t}`));
3332
5745
  }
3333
5746
 
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 };
5747
+ export { COPY_FEEDBACK_MS, DATETIME_HISTOGRAM_BINS, DATETIME_HISTOGRAM_BINS_MAX, DEFAULT_APP_CONFIG, DEFAULT_TARGET_CRS, DUCKDB_INIT_TIMEOUT_MS, LAYER_HUE_MULTIPLIER, LruCache, MAX_QUERY_HISTORY_ENTRIES, MarkdownSqlContext, PRESETS, PROVIDERS, PROVIDER_IDS, QueryCancelledError, SF_LABELS, SQL_PREVIEW_LENGTH, STAC_API_PATH_RE, STAC_COG_ASSET_KEYS, STAC_GEOPARQUET_REQUIRED_COLUMNS, STORAGE_KEYS, UrlAdapter, VIEWER_DIR_EXTENSIONS, WGS84_CODES, absolutizeHref, allChannelsBand0, applyFacets, applyPreset, applyStacItemStorageHints, applyStorageHintsToConnection, attachPixelInspector, availablePresets, buildDataTypeLabel, buildDuckDbSource, buildEndpointFromTemplate, buildFacets, buildGeoArrowTables, buildMosaicSourceMeta, buildProviderBaseUrl, buildTransformExpr, clampBounds, classifyStac, classifyType, classifyUrl, coerceBool, coercePositiveInt, coerceString, coerceTheme, compositeFromUrl, compositeToUrl, connectionIdentityKey, copyToClipboard, createApiSource, createStaticSource, describeParseResult, detectHostBucket, detectMosaicCapable, detectMultiCogCapable, detectStorageExtensionVersion, emptyFacetState, emptyPushdown, emptyStorageHints, escapeCsvField, exportToCsv, exportToJson, extractBounds, extractCogAssets, extractEpsgFromGeoMeta, extractGeometryTypes, extractItemView, extractMosaicAssets, extractRasterBandAssets, extractSentinelBandAssets, extractStorageHints, findGeoColumn, findGeoColumnFromRows, flattenStacBbox, formatDate, formatFileSize, formatValue, generateHexDump, getAccessMode, getDuckDbReadFn, getFileExtension, getFileTypeInfo, getMimeType, getNativeScheme, getProvider, getViewerKind, handleLoadError, hasActiveFilters, hasCompositableBands, hasRgbBands, hasStacItemsEndpoint, hydrateStacItems, interpolateTemplates, isAbortError, isCloudNativeFormat, isGcsProvider, isKnownBucketHost, isPubliclyStreamable, isQueryable, isSameConnectionIdentity, isSingleAssetComposite, isStacCatalog, isStacCollection, isStacFeatureCollection, isStacGeoparquetSchema, isStacItem, isWgs84Crs, jsonReplacerBigInt, loadFromStorage, looksLikeUrl, markSqlBlocks, mergeAppConfig, normalizeEndpoint, normalizeGeomType, normalizeProvider, parseGeometryTypeCrs, parseMarkdownDocument, parseStorageUrl, parseVisibilityParam, parseWKB, persistToStorage, pickCogAssetHref, pickGranularity, pickNaturalColorComposite, pickStacPrimaryAsset, presetMatchesComposite, readParquetMetadata, renderNotebook, residualState, resolveBandSlotAssetKey, resolveBasemap, resolveCloudUrl, resolvePresetComposite, resolveProviderEndpoint, resolveSetting, resolveStacAssetHref, safeClamp, safeDecodeURIComponent, serializeToCsv, serializeToJson, smokeTestHref, sniffApiCapabilities, sortFileEntries, sortViews, spatialCellKey, stacItemBbox, stacRowToItem, syntheticSelfAsset, toBinary, toCql2Filter, toNativeQuery, toggleSortField, typeBadgeClass, typeColor, typeLabel, wireCodeCopyButtons, wrapWkbWithCrs };
3335
5748
  //# sourceMappingURL=index.js.map
3336
5749
  //# sourceMappingURL=index.js.map