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