@walkthru-earth/objex 1.3.1 → 1.5.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 +28 -20
- package/dist/components/browser/FileTreeSidebar.svelte +32 -17
- package/dist/components/layout/AboutSheet.svelte +5 -2
- package/dist/components/layout/ConnectionDialog.svelte +7 -2
- package/dist/components/layout/SettingsSheet.svelte +238 -0
- package/dist/components/layout/SettingsSheet.svelte.d.ts +6 -0
- package/dist/components/layout/Sidebar.svelte +73 -6
- package/dist/components/layout/Sidebar.svelte.d.ts +4 -1
- package/dist/components/layout/StatusBar.svelte +17 -14
- package/dist/components/layout/TabBar.svelte +4 -4
- package/dist/components/ui/context-menu/context-menu-radio-group.svelte.d.ts +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte.d.ts +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte.d.ts +1 -1
- package/dist/components/ui/input/input.svelte.d.ts +1 -1
- package/dist/components/ui/resizable/index.d.ts +1 -1
- package/dist/components/ui/resizable/index.js +2 -2
- package/dist/components/ui/slider/index.d.ts +3 -0
- package/dist/components/ui/slider/index.js +5 -0
- package/dist/components/ui/slider/range-slider.svelte +94 -0
- package/dist/components/ui/slider/range-slider.svelte.d.ts +21 -0
- package/dist/components/ui/slider/slider.svelte +83 -0
- package/dist/components/ui/slider/slider.svelte.d.ts +7 -0
- package/dist/components/viewers/ArchiveViewer.svelte +140 -113
- package/dist/components/viewers/CodeViewer.svelte +45 -48
- package/dist/components/viewers/CodeViewer.svelte.d.ts +1 -1
- package/dist/components/viewers/CogControls.svelte +338 -184
- package/dist/components/viewers/CogControls.svelte.d.ts +33 -10
- package/dist/components/viewers/CogViewer.svelte +269 -116
- package/dist/components/viewers/CopcViewer.svelte +8 -15
- package/dist/components/viewers/DatabaseViewer.svelte +22 -21
- package/dist/components/viewers/FileInfo.svelte +16 -16
- package/dist/components/viewers/FlatGeobufViewer.svelte +16 -46
- package/dist/components/viewers/GeoParquetMapViewer.svelte +11 -9
- package/dist/components/viewers/GeoParquetMapViewer.svelte.d.ts +1 -1
- package/dist/components/viewers/ImageViewer.svelte +12 -14
- package/dist/components/viewers/LoadProgress.svelte +6 -6
- package/dist/components/viewers/MarkdownViewer.svelte +29 -30
- package/dist/components/viewers/MediaViewer.svelte +13 -14
- package/dist/components/viewers/ModelViewer.svelte +18 -21
- package/dist/components/viewers/MultiCogViewer.svelte +474 -106
- package/dist/components/viewers/MultiCogViewer.svelte.d.ts +1 -1
- package/dist/components/viewers/NotebookViewer.svelte +28 -29
- package/dist/components/viewers/PdfViewer.svelte +24 -33
- package/dist/components/viewers/PmtilesViewer.svelte +13 -15
- package/dist/components/viewers/QueryHistoryPanel.svelte +18 -18
- package/dist/components/viewers/RawViewer.svelte +27 -21
- package/dist/components/viewers/StacMapViewer.svelte +6 -13
- package/dist/components/viewers/StacMosaicViewer.svelte +1764 -410
- package/dist/components/viewers/StacMosaicViewer.svelte.d.ts +1 -1
- package/dist/components/viewers/StacTabViewer.svelte +26 -15
- package/dist/components/viewers/StacTabViewer.svelte.d.ts +1 -1
- package/dist/components/viewers/TableGrid.svelte +38 -34
- package/dist/components/viewers/TableStatusBar.svelte +7 -7
- package/dist/components/viewers/TableToolbar.svelte +10 -9
- package/dist/components/viewers/TableViewer.svelte +47 -30
- package/dist/components/viewers/TableViewer.svelte.d.ts +1 -0
- package/dist/components/viewers/ViewerHeader.svelte +18 -0
- package/dist/components/viewers/ViewerHeader.svelte.d.ts +10 -0
- package/dist/components/viewers/ViewerRouter.svelte +16 -8
- package/dist/components/viewers/ViewerStatus.svelte +19 -0
- package/dist/components/viewers/ViewerStatus.svelte.d.ts +7 -0
- package/dist/components/viewers/ZarrMapViewer.svelte +24 -21
- package/dist/components/viewers/ZarrViewer.svelte +98 -65
- package/dist/components/viewers/cog/ChannelPicker.svelte +83 -0
- package/dist/components/viewers/cog/ChannelPicker.svelte.d.ts +13 -0
- package/dist/components/viewers/cog/PixelInspectorPanel.svelte +87 -0
- package/dist/components/viewers/cog/PixelInspectorPanel.svelte.d.ts +17 -0
- package/dist/components/viewers/cog/buildRgbLayer.d.ts +78 -0
- package/dist/components/viewers/cog/buildRgbLayer.js +176 -0
- package/dist/components/viewers/map/AttributeTable.svelte +7 -7
- package/dist/components/viewers/map/MapContainer.svelte +38 -12
- package/dist/components/viewers/pmtiles/PmtilesArchiveView.svelte +109 -83
- package/dist/components/viewers/pmtiles/PmtilesTileInspector.svelte +16 -16
- package/dist/components/viewers/stac/StacDatetimeBar.svelte +175 -0
- package/dist/components/viewers/stac/StacDatetimeBar.svelte.d.ts +10 -0
- package/dist/components/viewers/stac/StacFilterPanel.svelte +243 -0
- package/dist/components/viewers/stac/StacFilterPanel.svelte.d.ts +14 -0
- package/dist/components/viewers/stac/StacItemInspector.svelte +223 -0
- package/dist/components/viewers/stac/StacItemInspector.svelte.d.ts +10 -0
- package/dist/components/viewers/stac/StacItemStrip.svelte +228 -0
- package/dist/components/viewers/stac/StacItemStrip.svelte.d.ts +12 -0
- package/dist/constants.d.ts +6 -0
- package/dist/constants.js +8 -0
- package/dist/file-icons/index.d.ts +1 -1
- package/dist/file-icons/index.js +1 -1
- package/dist/i18n/ar.js +113 -2
- package/dist/i18n/en.js +113 -2
- package/dist/index.d.ts +2 -28
- package/dist/index.js +7 -23
- package/dist/query/engine.d.ts +10 -0
- package/dist/query/source.js +1 -1
- package/dist/query/stac-source-factory.d.ts +65 -0
- package/dist/query/stac-source-factory.js +77 -0
- package/dist/query/stac-source-parquet.d.ts +135 -0
- package/dist/query/stac-source-parquet.js +468 -0
- package/dist/query/wasm.d.ts +8 -0
- package/dist/query/wasm.js +310 -65
- package/dist/storage/presign.js +3 -2
- package/dist/storage/providers.js +7 -6
- package/dist/stores/config.svelte.d.ts +15 -0
- package/dist/stores/config.svelte.js +46 -0
- package/dist/stores/connections.svelte.d.ts +2 -2
- package/dist/stores/connections.svelte.js +1 -2
- package/dist/stores/files.svelte.d.ts +1 -1
- package/dist/stores/files.svelte.js +1 -1
- package/dist/stores/query-history.svelte.js +1 -1
- package/dist/stores/settings.svelte.d.ts +16 -1
- package/dist/stores/settings.svelte.js +104 -48
- package/dist/stores/tabs.svelte.d.ts +3 -0
- package/dist/stores/tabs.svelte.js +17 -0
- package/dist/utils/cog-histogram.d.ts +121 -0
- package/dist/utils/cog-histogram.js +424 -0
- package/dist/utils/cog.d.ts +177 -20
- package/dist/utils/cog.js +361 -76
- package/dist/utils/colormap-sprite.d.ts +0 -9
- package/dist/utils/colormap-sprite.js +0 -21
- package/dist/utils/deck.d.ts +18 -12
- package/dist/utils/deck.js +15 -7
- package/dist/utils/media-query.svelte.d.ts +14 -0
- package/dist/utils/media-query.svelte.js +29 -0
- package/dist/utils/pmtiles-tile.js +2 -2
- package/dist/utils/signed-url-effect.d.ts +7 -0
- package/dist/utils/signed-url-effect.js +19 -0
- package/dist/utils/{url.d.ts → signed-url.d.ts} +15 -1
- package/dist/utils/{url.js → signed-url.js} +32 -10
- package/dist/utils/url-state.d.ts +36 -0
- package/dist/utils/url-state.js +72 -2
- package/dist/utils/zarr-tab.d.ts +1 -2
- package/dist/utils/zarr-tab.js +1 -2
- package/dist/utils/zarr.d.ts +0 -17
- package/dist/utils/zarr.js +1 -45
- package/package.json +55 -84
- package/dist/components/browser/Breadcrumb.svelte +0 -50
- package/dist/components/browser/Breadcrumb.svelte.d.ts +0 -7
- package/dist/components/browser/CreateFolderDialog.svelte +0 -98
- package/dist/components/browser/CreateFolderDialog.svelte.d.ts +0 -6
- package/dist/components/browser/DeleteConfirmDialog.svelte +0 -90
- package/dist/components/browser/DeleteConfirmDialog.svelte.d.ts +0 -8
- package/dist/components/browser/DropZone.svelte +0 -83
- package/dist/components/browser/DropZone.svelte.d.ts +0 -7
- package/dist/components/browser/FileBrowser.svelte +0 -252
- package/dist/components/browser/FileBrowser.svelte.d.ts +0 -3
- package/dist/components/browser/FileRow.svelte +0 -117
- package/dist/components/browser/FileRow.svelte.d.ts +0 -9
- package/dist/components/browser/RenameDialog.svelte +0 -101
- package/dist/components/browser/RenameDialog.svelte.d.ts +0 -8
- package/dist/components/browser/SearchBar.svelte +0 -40
- package/dist/components/browser/SearchBar.svelte.d.ts +0 -6
- package/dist/components/browser/UploadButton.svelte +0 -65
- package/dist/components/browser/UploadButton.svelte.d.ts +0 -3
- package/dist/query/stac-geoparquet.d.ts +0 -31
- package/dist/query/stac-geoparquet.js +0 -136
- package/dist/utils/clipboard.d.ts +0 -13
- package/dist/utils/clipboard.js +0 -38
- package/dist/utils/cloud-url.d.ts +0 -27
- package/dist/utils/cloud-url.js +0 -61
- package/dist/utils/cog-pure.d.ts +0 -25
- package/dist/utils/cog-pure.js +0 -35
- package/dist/utils/column-types.d.ts +0 -5
- package/dist/utils/column-types.js +0 -137
- package/dist/utils/connection-identity.d.ts +0 -51
- package/dist/utils/connection-identity.js +0 -97
- package/dist/utils/error.d.ts +0 -8
- package/dist/utils/error.js +0 -12
- package/dist/utils/evidence-context.d.ts +0 -22
- package/dist/utils/evidence-context.js +0 -56
- package/dist/utils/export.d.ts +0 -22
- package/dist/utils/export.js +0 -76
- package/dist/utils/file-sort.d.ts +0 -20
- package/dist/utils/file-sort.js +0 -41
- package/dist/utils/format.d.ts +0 -24
- package/dist/utils/format.js +0 -78
- package/dist/utils/geoarrow.d.ts +0 -32
- package/dist/utils/geoarrow.js +0 -672
- package/dist/utils/geometry-type.d.ts +0 -52
- package/dist/utils/geometry-type.js +0 -76
- package/dist/utils/hex.d.ts +0 -10
- package/dist/utils/hex.js +0 -27
- package/dist/utils/host-detection.d.ts +0 -23
- package/dist/utils/host-detection.js +0 -95
- package/dist/utils/local-storage.d.ts +0 -16
- package/dist/utils/local-storage.js +0 -37
- package/dist/utils/markdown-sql.d.ts +0 -30
- package/dist/utils/markdown-sql.js +0 -72
- package/dist/utils/notebook.d.ts +0 -59
- package/dist/utils/notebook.js +0 -211
- package/dist/utils/parquet-metadata.d.ts +0 -64
- package/dist/utils/parquet-metadata.js +0 -262
- package/dist/utils/stac-geoparquet.d.ts +0 -90
- package/dist/utils/stac-geoparquet.js +0 -223
- package/dist/utils/stac-hydrate.d.ts +0 -38
- package/dist/utils/stac-hydrate.js +0 -243
- package/dist/utils/stac.d.ts +0 -136
- package/dist/utils/stac.js +0 -176
- package/dist/utils/storage-url.d.ts +0 -90
- package/dist/utils/storage-url.js +0 -568
- package/dist/utils/wkb.d.ts +0 -43
- package/dist/utils/wkb.js +0 -359
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export type PixelInspectorRow = {
|
|
3
|
+
label: string;
|
|
4
|
+
sublabel?: string;
|
|
5
|
+
value: number | null;
|
|
6
|
+
};
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<script lang="ts">
|
|
10
|
+
import { t } from '../../../i18n/index.svelte.js';
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
lng,
|
|
14
|
+
lat,
|
|
15
|
+
rows,
|
|
16
|
+
footnote,
|
|
17
|
+
extraLine,
|
|
18
|
+
onClose,
|
|
19
|
+
inspecting = false
|
|
20
|
+
}: {
|
|
21
|
+
lng: number | null;
|
|
22
|
+
lat: number | null;
|
|
23
|
+
rows: PixelInspectorRow[] | null;
|
|
24
|
+
footnote?: string;
|
|
25
|
+
extraLine?: string;
|
|
26
|
+
onClose: () => void;
|
|
27
|
+
inspecting?: boolean;
|
|
28
|
+
} = $props();
|
|
29
|
+
|
|
30
|
+
// The panel renders whenever we have a coordinate + rows. The "reading" pill
|
|
31
|
+
// renders independently while inspecting is true. Both blocks can render at
|
|
32
|
+
// once during a follow-up click, this matches the existing per-viewer UX.
|
|
33
|
+
const showPanel = $derived(rows !== null && lng !== null && lat !== null);
|
|
34
|
+
const showReading = $derived(inspecting);
|
|
35
|
+
|
|
36
|
+
function formatValue(v: number | null): string {
|
|
37
|
+
if (v === null) return '-';
|
|
38
|
+
return Number.isInteger(v) ? String(v) : v.toFixed(4);
|
|
39
|
+
}
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
{#if showPanel && rows && lng !== null && lat !== null}
|
|
43
|
+
<div
|
|
44
|
+
class="absolute bottom-2 left-2 z-10 max-w-[calc(100vw-1rem)] rounded bg-card/90 p-2.5 text-xs text-card-foreground backdrop-blur-sm sm:max-w-none"
|
|
45
|
+
>
|
|
46
|
+
<div class="mb-1 flex items-center justify-between gap-3">
|
|
47
|
+
<span class="font-medium">{t('cog.pixelValue')}</span>
|
|
48
|
+
<button
|
|
49
|
+
class="inline-flex min-h-8 min-w-8 items-center justify-center text-base text-muted-foreground hover:text-card-foreground sm:min-h-0 sm:min-w-0 sm:text-xs"
|
|
50
|
+
style="touch-action: manipulation;"
|
|
51
|
+
onclick={onClose}
|
|
52
|
+
aria-label={t('stac.close')}
|
|
53
|
+
>
|
|
54
|
+
×
|
|
55
|
+
</button>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="space-y-0.5 text-muted-foreground">
|
|
58
|
+
<div>{lat.toFixed(6)}°, {lng.toFixed(6)}°</div>
|
|
59
|
+
{#if footnote}
|
|
60
|
+
<div class="text-[10px]">{footnote}</div>
|
|
61
|
+
{/if}
|
|
62
|
+
{#if extraLine}
|
|
63
|
+
<div class="truncate text-[10px]" title={extraLine}>{extraLine}</div>
|
|
64
|
+
{/if}
|
|
65
|
+
</div>
|
|
66
|
+
<div class="mt-1.5 space-y-0.5">
|
|
67
|
+
{#each rows as row}
|
|
68
|
+
<div class="flex justify-between gap-2">
|
|
69
|
+
<span class="text-muted-foreground">
|
|
70
|
+
{row.label}{#if row.sublabel}
|
|
71
|
+
<span class="ml-1 text-[10px] opacity-70">({row.sublabel})</span>
|
|
72
|
+
{/if}
|
|
73
|
+
</span>
|
|
74
|
+
<span class="font-mono tabular-nums">{formatValue(row.value)}</span>
|
|
75
|
+
</div>
|
|
76
|
+
{/each}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
{/if}
|
|
80
|
+
|
|
81
|
+
{#if showReading}
|
|
82
|
+
<div
|
|
83
|
+
class="pointer-events-none absolute bottom-2 left-2 z-10 rounded bg-card/80 px-2 py-1 text-xs text-card-foreground backdrop-blur-sm"
|
|
84
|
+
>
|
|
85
|
+
{t('cog.reading')}
|
|
86
|
+
</div>
|
|
87
|
+
{/if}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type PixelInspectorRow = {
|
|
2
|
+
label: string;
|
|
3
|
+
sublabel?: string;
|
|
4
|
+
value: number | null;
|
|
5
|
+
};
|
|
6
|
+
type $$ComponentProps = {
|
|
7
|
+
lng: number | null;
|
|
8
|
+
lat: number | null;
|
|
9
|
+
rows: PixelInspectorRow[] | null;
|
|
10
|
+
footnote?: string;
|
|
11
|
+
extraLine?: string;
|
|
12
|
+
onClose: () => void;
|
|
13
|
+
inspecting?: boolean;
|
|
14
|
+
};
|
|
15
|
+
declare const PixelInspectorPanel: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
16
|
+
type PixelInspectorPanel = ReturnType<typeof PixelInspectorPanel>;
|
|
17
|
+
export default PixelInspectorPanel;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer-construction dispatch for the unified RGB picker.
|
|
3
|
+
*
|
|
4
|
+
* Decision rule:
|
|
5
|
+
* - All three RGB channels point to the SAME asset → COGLayer. When a
|
|
6
|
+
* `preflightGeotiff` is supplied, the per-channel `bandIndex` values are
|
|
7
|
+
* translated into a `BandConfig` and run through `selectCogPipeline`,
|
|
8
|
+
* which returns a custom `getTileData` / `renderTile` pair that swaps
|
|
9
|
+
* bands as requested (the library's COGLayer does not accept a
|
|
10
|
+
* `bandConfig` prop, only the resolved pipeline). Without a preflight
|
|
11
|
+
* GeoTIFF the layer falls back to the library default pipeline, which
|
|
12
|
+
* reads bands 0/1/2 in that order, correct for single-band per-asset
|
|
13
|
+
* COGs and for the default natural-color order on pre-baked multi-band
|
|
14
|
+
* visuals.
|
|
15
|
+
* - Channels point to DIFFERENT assets → MultiCOGLayer with the legacy
|
|
16
|
+
* `composite: { r, g, b }` keyed on asset keys. MultiCOGLayer reads band 0
|
|
17
|
+
* of each source, per-channel band index is silently ignored on this path
|
|
18
|
+
* (library limitation, see spec Known Limitations).
|
|
19
|
+
*
|
|
20
|
+
* `buildRgbLayer` ONLY constructs the layer. It does not add overlays,
|
|
21
|
+
* register cleanup, or touch deck.gl state. Caller owns lifecycle.
|
|
22
|
+
*/
|
|
23
|
+
import { COGLayer, MultiCOGLayer } from '@developmentseed/deck.gl-geotiff';
|
|
24
|
+
import type { DecoderPool, GeoTIFF as GeoTIFFType } from '@developmentseed/geotiff';
|
|
25
|
+
import type { EpsgResolver } from '@developmentseed/proj';
|
|
26
|
+
import { type ChannelComposite, type CogAsset } from '@walkthru-earth/objex-utils';
|
|
27
|
+
import { type GeoBounds, type RescaleConfig } from '../../../utils/cog.js';
|
|
28
|
+
export type RgbLayerKind = 'cog' | 'multicog';
|
|
29
|
+
export interface BuildRgbLayerOptions {
|
|
30
|
+
id: string;
|
|
31
|
+
assets: CogAsset[];
|
|
32
|
+
composite: ChannelComposite;
|
|
33
|
+
rescale: RescaleConfig;
|
|
34
|
+
/** href → presigned-or-passthrough URL. */
|
|
35
|
+
resolveHref: (href: string) => Promise<string>;
|
|
36
|
+
pool?: DecoderPool | null;
|
|
37
|
+
epsgResolver: EpsgResolver;
|
|
38
|
+
signal: AbortSignal;
|
|
39
|
+
onLoad?: (info: {
|
|
40
|
+
kind: RgbLayerKind;
|
|
41
|
+
bounds?: GeoBounds;
|
|
42
|
+
}) => void;
|
|
43
|
+
/**
|
|
44
|
+
* Pre-opened GeoTIFF for the single-asset path. When provided, the per-channel
|
|
45
|
+
* `bandIndex` values from the composite are honored via `selectCogPipeline`,
|
|
46
|
+
* which inspects the COG's sample format / band count and returns a custom
|
|
47
|
+
* `getTileData` + `renderTile` pair that swaps bands as requested.
|
|
48
|
+
*
|
|
49
|
+
* When omitted, the layer falls back to the library default render pipeline,
|
|
50
|
+
* which always reads bands 0/1/2 in that order. That is fine for:
|
|
51
|
+
* - single-band per-asset COGs (Sentinel-2, Landsat per-band) where every
|
|
52
|
+
* `bandIndex` is 0 anyway, OR
|
|
53
|
+
* - pre-baked multi-band visuals (NAIP `image`, S2 `visual`) where the
|
|
54
|
+
* natural-color preset wants the default band order.
|
|
55
|
+
*/
|
|
56
|
+
preflightGeotiff?: GeoTIFFType | null;
|
|
57
|
+
/**
|
|
58
|
+
* Resolved nodata value threaded into `buildBandRenderPipeline` for the
|
|
59
|
+
* multi-asset `MultiCOGLayer` path. `null` (default) disables the nodata
|
|
60
|
+
* filter so legacy callers preserve their previous behaviour.
|
|
61
|
+
*/
|
|
62
|
+
noDataVal?: number | null;
|
|
63
|
+
}
|
|
64
|
+
export interface BuiltRgbLayer {
|
|
65
|
+
kind: RgbLayerKind;
|
|
66
|
+
layer: COGLayer | MultiCOGLayer;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Build the appropriate deck.gl layer for an RGB composite.
|
|
70
|
+
*
|
|
71
|
+
* For single-asset composites the band indices flow through `selectCogPipeline`
|
|
72
|
+
* (when `preflightGeotiff` is provided) into a custom `getTileData` /
|
|
73
|
+
* `renderTile` pair that honors the requested R/G/B band order. Without a
|
|
74
|
+
* preflight GeoTIFF the layer uses the library's default pipeline (bands 0/1/2).
|
|
75
|
+
* For multi-asset composites a warning is logged (once per call) when any
|
|
76
|
+
* non-band-0 index is requested, since MultiCOGLayer cannot honor it today.
|
|
77
|
+
*/
|
|
78
|
+
export declare function buildRgbLayer(opts: BuildRgbLayerOptions): Promise<BuiltRgbLayer>;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer-construction dispatch for the unified RGB picker.
|
|
3
|
+
*
|
|
4
|
+
* Decision rule:
|
|
5
|
+
* - All three RGB channels point to the SAME asset → COGLayer. When a
|
|
6
|
+
* `preflightGeotiff` is supplied, the per-channel `bandIndex` values are
|
|
7
|
+
* translated into a `BandConfig` and run through `selectCogPipeline`,
|
|
8
|
+
* which returns a custom `getTileData` / `renderTile` pair that swaps
|
|
9
|
+
* bands as requested (the library's COGLayer does not accept a
|
|
10
|
+
* `bandConfig` prop, only the resolved pipeline). Without a preflight
|
|
11
|
+
* GeoTIFF the layer falls back to the library default pipeline, which
|
|
12
|
+
* reads bands 0/1/2 in that order, correct for single-band per-asset
|
|
13
|
+
* COGs and for the default natural-color order on pre-baked multi-band
|
|
14
|
+
* visuals.
|
|
15
|
+
* - Channels point to DIFFERENT assets → MultiCOGLayer with the legacy
|
|
16
|
+
* `composite: { r, g, b }` keyed on asset keys. MultiCOGLayer reads band 0
|
|
17
|
+
* of each source, per-channel band index is silently ignored on this path
|
|
18
|
+
* (library limitation, see spec Known Limitations).
|
|
19
|
+
*
|
|
20
|
+
* `buildRgbLayer` ONLY constructs the layer. It does not add overlays,
|
|
21
|
+
* register cleanup, or touch deck.gl state. Caller owns lifecycle.
|
|
22
|
+
*/
|
|
23
|
+
import { COGLayer, MultiCOGLayer } from '@developmentseed/deck.gl-geotiff';
|
|
24
|
+
import { allChannelsBand0, isSingleAssetComposite } from '@walkthru-earth/objex-utils';
|
|
25
|
+
import { buildBandRenderPipeline, selectCogPipeline } from '../../../utils/cog.js';
|
|
26
|
+
/**
|
|
27
|
+
* Build the appropriate deck.gl layer for an RGB composite.
|
|
28
|
+
*
|
|
29
|
+
* For single-asset composites the band indices flow through `selectCogPipeline`
|
|
30
|
+
* (when `preflightGeotiff` is provided) into a custom `getTileData` /
|
|
31
|
+
* `renderTile` pair that honors the requested R/G/B band order. Without a
|
|
32
|
+
* preflight GeoTIFF the layer uses the library's default pipeline (bands 0/1/2).
|
|
33
|
+
* For multi-asset composites a warning is logged (once per call) when any
|
|
34
|
+
* non-band-0 index is requested, since MultiCOGLayer cannot honor it today.
|
|
35
|
+
*/
|
|
36
|
+
export async function buildRgbLayer(opts) {
|
|
37
|
+
const assetByKey = new Map(opts.assets.map((a) => [a.key, a]));
|
|
38
|
+
const c = opts.composite;
|
|
39
|
+
console.debug('[buildRgbLayer]', {
|
|
40
|
+
id: opts.id,
|
|
41
|
+
composite: c,
|
|
42
|
+
single: isSingleAssetComposite(c),
|
|
43
|
+
assetKeys: opts.assets.map((a) => a.key),
|
|
44
|
+
hasPreflightGeotiff: !!opts.preflightGeotiff
|
|
45
|
+
});
|
|
46
|
+
if (isSingleAssetComposite(c)) {
|
|
47
|
+
const asset = assetByKey.get(c.r.assetKey);
|
|
48
|
+
if (!asset)
|
|
49
|
+
throw new Error(`unknown asset key: ${c.r.assetKey}`);
|
|
50
|
+
const url = await opts.resolveHref(asset.href);
|
|
51
|
+
if (opts.signal.aborted)
|
|
52
|
+
throw new DOMException('Aborted', 'AbortError');
|
|
53
|
+
const onGeoTIFFLoad = (_g, info) => {
|
|
54
|
+
opts.onLoad?.({
|
|
55
|
+
kind: 'cog',
|
|
56
|
+
bounds: info.geographicBounds
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
// Branch on whether we have a pre-opened GeoTIFF.
|
|
60
|
+
// - Present: build a per-channel BandConfig from the composite, hand
|
|
61
|
+
// it to selectCogPipeline (which inspects sampleFormat / bandCount)
|
|
62
|
+
// and spread the resolved {getTileData?, renderTile?} into COGLayer.
|
|
63
|
+
// This is the only path that honors a non-default per-channel
|
|
64
|
+
// bandIndex on a single-asset multi-band COG (e.g. NAIP NIR-R-G).
|
|
65
|
+
// - Absent: fall back to the library's default render pipeline. Bands
|
|
66
|
+
// 0/1/2 are read in that order, which is correct for single-band
|
|
67
|
+
// per-asset COGs (every bandIndex is 0 anyway) and for the default
|
|
68
|
+
// natural-color order on pre-baked multi-band visuals.
|
|
69
|
+
if (opts.preflightGeotiff) {
|
|
70
|
+
const bandConfig = {
|
|
71
|
+
mode: 'rgb',
|
|
72
|
+
rBand: c.r.bandIndex,
|
|
73
|
+
gBand: c.g.bandIndex,
|
|
74
|
+
bBand: c.b.bandIndex,
|
|
75
|
+
band: 0,
|
|
76
|
+
colorRamp: 'viridis'
|
|
77
|
+
};
|
|
78
|
+
console.debug('[buildRgbLayer] cog single-asset with preflight', {
|
|
79
|
+
id: opts.id,
|
|
80
|
+
bandConfig,
|
|
81
|
+
url
|
|
82
|
+
});
|
|
83
|
+
const pipeline = selectCogPipeline(opts.preflightGeotiff, {
|
|
84
|
+
bandConfig,
|
|
85
|
+
rescale: opts.rescale
|
|
86
|
+
});
|
|
87
|
+
const layer = new COGLayer({
|
|
88
|
+
id: opts.id,
|
|
89
|
+
geotiff: url,
|
|
90
|
+
...pipeline,
|
|
91
|
+
pool: opts.pool ?? undefined,
|
|
92
|
+
epsgResolver: opts.epsgResolver,
|
|
93
|
+
signal: opts.signal,
|
|
94
|
+
onGeoTIFFLoad
|
|
95
|
+
});
|
|
96
|
+
return { kind: 'cog', layer };
|
|
97
|
+
}
|
|
98
|
+
console.debug('[buildRgbLayer] cog single-asset (library default pipeline)', {
|
|
99
|
+
id: opts.id,
|
|
100
|
+
url
|
|
101
|
+
});
|
|
102
|
+
// Fallback: no preflight GeoTIFF supplied. COGLayer's typed prop surface
|
|
103
|
+
// does not include `renderPipeline` (only `getTileData` + `renderTile`),
|
|
104
|
+
// so we cannot apply the band render pipeline statically here. Without
|
|
105
|
+
// a preflight to feed `selectCogPipeline`, we have no way to inspect
|
|
106
|
+
// sample format / band count up front, so we let the library infer its
|
|
107
|
+
// own pipeline from the GeoTIFF metadata at load time. Bands 0/1/2 are
|
|
108
|
+
// read in that order, which is correct for single-band per-asset COGs
|
|
109
|
+
// (every bandIndex is 0 anyway) and for the default natural-color order
|
|
110
|
+
// on pre-baked multi-band visuals (NAIP `image`, S2 `visual`).
|
|
111
|
+
const layer = new COGLayer({
|
|
112
|
+
id: opts.id,
|
|
113
|
+
geotiff: url,
|
|
114
|
+
pool: opts.pool ?? undefined,
|
|
115
|
+
epsgResolver: opts.epsgResolver,
|
|
116
|
+
signal: opts.signal,
|
|
117
|
+
onGeoTIFFLoad
|
|
118
|
+
});
|
|
119
|
+
return { kind: 'cog', layer };
|
|
120
|
+
}
|
|
121
|
+
if (!allChannelsBand0(c)) {
|
|
122
|
+
// Library limitation: MultiCOGLayer always reads band 0. Surface a
|
|
123
|
+
// console warning once per call so the consumer sees that the user's
|
|
124
|
+
// per-channel band index was dropped.
|
|
125
|
+
console.warn('[buildRgbLayer] multi-asset composite with non-band-0 indices, band index ignored on multi-asset path');
|
|
126
|
+
}
|
|
127
|
+
const sources = {};
|
|
128
|
+
for (const ref of [c.r, c.g, c.b, c.a].filter((x) => Boolean(x))) {
|
|
129
|
+
if (sources[ref.assetKey])
|
|
130
|
+
continue;
|
|
131
|
+
const asset = assetByKey.get(ref.assetKey);
|
|
132
|
+
if (!asset) {
|
|
133
|
+
console.warn('[buildRgbLayer] missing asset for ref', ref);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const url = await opts.resolveHref(asset.href);
|
|
137
|
+
if (opts.signal.aborted)
|
|
138
|
+
throw new DOMException('Aborted', 'AbortError');
|
|
139
|
+
sources[ref.assetKey] = { url };
|
|
140
|
+
}
|
|
141
|
+
const compositeSpec = {
|
|
142
|
+
r: c.r.assetKey,
|
|
143
|
+
g: c.g.assetKey,
|
|
144
|
+
b: c.b.assetKey
|
|
145
|
+
};
|
|
146
|
+
if (c.a && sources[c.a.assetKey])
|
|
147
|
+
compositeSpec.a = c.a.assetKey;
|
|
148
|
+
console.debug('[buildRgbLayer] multicog sources resolved', {
|
|
149
|
+
sourceKeys: Object.keys(sources),
|
|
150
|
+
composite: compositeSpec,
|
|
151
|
+
urls: Object.fromEntries(Object.entries(sources).map(([k, v]) => [k, v.url]))
|
|
152
|
+
});
|
|
153
|
+
const layer = new MultiCOGLayer({
|
|
154
|
+
id: opts.id,
|
|
155
|
+
sources,
|
|
156
|
+
composite: compositeSpec,
|
|
157
|
+
renderPipeline: buildBandRenderPipeline({
|
|
158
|
+
noDataVal: opts.noDataVal ?? null,
|
|
159
|
+
rescale: { ...opts.rescale }
|
|
160
|
+
}),
|
|
161
|
+
pool: opts.pool ?? undefined,
|
|
162
|
+
epsgResolver: opts.epsgResolver,
|
|
163
|
+
signal: opts.signal,
|
|
164
|
+
onGeoTIFFLoad: (_tiffs, info) => {
|
|
165
|
+
console.debug('[buildRgbLayer] MultiCOG onGeoTIFFLoad', {
|
|
166
|
+
id: opts.id,
|
|
167
|
+
bounds: info.geographicBounds
|
|
168
|
+
});
|
|
169
|
+
opts.onLoad?.({
|
|
170
|
+
kind: 'multicog',
|
|
171
|
+
bounds: info.geographicBounds
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
return { kind: 'multicog', layer };
|
|
176
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import XIcon from '@lucide/svelte/icons/x';
|
|
3
|
-
import { formatValue } from '
|
|
3
|
+
import { formatValue } from '@walkthru-earth/objex-utils';
|
|
4
4
|
|
|
5
5
|
let {
|
|
6
6
|
feature = null,
|
|
@@ -18,24 +18,24 @@ let {
|
|
|
18
18
|
class="absolute bottom-2 end-2 top-10 z-10 flex w-64 flex-col overflow-hidden rounded bg-card/95 text-card-foreground shadow-lg backdrop-blur-sm sm:w-72"
|
|
19
19
|
>
|
|
20
20
|
<div
|
|
21
|
-
class="flex items-center justify-between border-b border-
|
|
21
|
+
class="flex items-center justify-between border-b border-border px-3 py-2"
|
|
22
22
|
>
|
|
23
|
-
<h3 class="text-xs font-medium text-
|
|
23
|
+
<h3 class="text-xs font-medium text-muted-foreground">Feature Attributes</h3>
|
|
24
24
|
{#if onClose}
|
|
25
25
|
<button
|
|
26
|
-
class="rounded p-0.5 text-
|
|
26
|
+
class="rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
27
27
|
onclick={onClose}
|
|
28
28
|
>
|
|
29
29
|
<XIcon class="size-3.5" />
|
|
30
30
|
</button>
|
|
31
31
|
{/if}
|
|
32
32
|
</div>
|
|
33
|
-
<div class="flex-1 divide-y divide-
|
|
33
|
+
<div class="flex-1 divide-y divide-border overflow-auto">
|
|
34
34
|
{#each Object.entries(feature) as [key, value]}
|
|
35
35
|
<div class="px-3 py-1.5">
|
|
36
|
-
<div class="text-[10px] font-medium text-
|
|
36
|
+
<div class="text-[10px] font-medium text-muted-foreground">{key}</div>
|
|
37
37
|
<div
|
|
38
|
-
class="break-all text-xs text-
|
|
38
|
+
class="break-all text-xs text-foreground"
|
|
39
39
|
title={formatValue(value)}
|
|
40
40
|
>
|
|
41
41
|
{formatValue(value)}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import maplibregl from 'maplibre-gl';
|
|
3
3
|
import 'maplibre-gl/dist/maplibre-gl.css';
|
|
4
|
+
import { resolveBasemap } from '@walkthru-earth/objex-utils';
|
|
4
5
|
import { onDestroy } from 'svelte';
|
|
5
6
|
import { t } from '../../../i18n/index.svelte.js';
|
|
7
|
+
import { appConfig } from '../../../stores/config.svelte.js';
|
|
6
8
|
import { settings } from '../../../stores/settings.svelte.js';
|
|
7
9
|
|
|
8
10
|
const MAP_STYLES = {
|
|
@@ -37,11 +39,34 @@ let {
|
|
|
37
39
|
bounds?: [number, number, number, number];
|
|
38
40
|
} = $props();
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
function toMapStyle(variant: 'light' | 'dark'): string | maplibregl.StyleSpecification {
|
|
43
|
+
const bm = resolveBasemap(appConfig.value, variant, settings.basemapId);
|
|
44
|
+
if (!bm) return MAP_STYLES[variant];
|
|
45
|
+
if (bm.type === 'raster') {
|
|
46
|
+
return {
|
|
47
|
+
version: 8,
|
|
48
|
+
sources: {
|
|
49
|
+
'objex-basemap': { type: 'raster', tiles: [bm.url], tileSize: 256 }
|
|
50
|
+
},
|
|
51
|
+
layers: [{ id: 'objex-basemap', type: 'raster', source: 'objex-basemap' }]
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return bm.url;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const resolvedBasemap = $derived(
|
|
58
|
+
style ? undefined : resolveBasemap(appConfig.value, settings.resolved, settings.basemapId)
|
|
59
|
+
);
|
|
60
|
+
const resolvedStyle = $derived(style ?? toMapStyle(settings.resolved));
|
|
61
|
+
// Stable identity for style-swap comparison: a raster StyleSpecification is a
|
|
62
|
+
// fresh object on every derive, so compare by basemap id + variant instead.
|
|
63
|
+
const styleKey = $derived(
|
|
64
|
+
style ? 'custom' : `${resolvedBasemap?.id ?? 'fallback'}:${settings.resolved}`
|
|
65
|
+
);
|
|
41
66
|
|
|
42
67
|
let containerEl: HTMLDivElement | undefined = $state();
|
|
43
68
|
let map: maplibregl.Map | null = null;
|
|
44
|
-
let
|
|
69
|
+
let currentStyleKey: string | null = null;
|
|
45
70
|
let currentZoom = $state(2);
|
|
46
71
|
let webglLost = $state(false);
|
|
47
72
|
|
|
@@ -56,7 +81,7 @@ function initMap() {
|
|
|
56
81
|
zoom
|
|
57
82
|
});
|
|
58
83
|
|
|
59
|
-
|
|
84
|
+
currentStyleKey = styleKey;
|
|
60
85
|
|
|
61
86
|
map.addControl(
|
|
62
87
|
new maplibregl.NavigationControl({ showCompass: true, visualizePitch: true }),
|
|
@@ -117,12 +142,13 @@ $effect(() => {
|
|
|
117
142
|
}
|
|
118
143
|
});
|
|
119
144
|
|
|
120
|
-
// React to theme changes — swap basemap style
|
|
145
|
+
// React to theme / basemap changes — swap basemap style
|
|
121
146
|
$effect(() => {
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
147
|
+
const nextKey = styleKey;
|
|
148
|
+
const nextStyle = resolvedStyle;
|
|
149
|
+
if (map && currentStyleKey !== nextKey && !style) {
|
|
150
|
+
currentStyleKey = nextKey;
|
|
151
|
+
map.setStyle(nextStyle);
|
|
126
152
|
}
|
|
127
153
|
});
|
|
128
154
|
|
|
@@ -132,13 +158,13 @@ onDestroy(() => {
|
|
|
132
158
|
});
|
|
133
159
|
</script>
|
|
134
160
|
|
|
135
|
-
<div class="relative h-full w-full">
|
|
136
|
-
<div bind:this={containerEl} class="h-full w-full"></div>
|
|
161
|
+
<div class="relative h-full w-full" style="touch-action: pan-x pan-y;">
|
|
162
|
+
<div bind:this={containerEl} class="h-full w-full" style="touch-action: none;"></div>
|
|
137
163
|
<!-- Zoom level indicator — positioned above nav controls -->
|
|
138
164
|
<div
|
|
139
|
-
class="pointer-events-none absolute bottom-[
|
|
165
|
+
class="pointer-events-none absolute bottom-[7rem] right-[10px] z-10 flex size-[29px] items-center justify-center rounded-full border border-border bg-background shadow-sm sm:bottom-[10rem]"
|
|
140
166
|
>
|
|
141
|
-
<span class="text-[10px] font-semibold tabular-nums text-
|
|
167
|
+
<span class="text-[10px] font-semibold tabular-nums text-foreground">
|
|
142
168
|
{currentZoom.toFixed(1)}
|
|
143
169
|
</span>
|
|
144
170
|
</div>
|