@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
package/dist/utils/cog.js
CHANGED
|
@@ -1,34 +1,65 @@
|
|
|
1
|
+
import { SourceCache, SourceChunk } from '@chunkd/middleware';
|
|
2
|
+
import { SourceView } from '@chunkd/source';
|
|
3
|
+
import { SourceHttp } from '@chunkd/source-http';
|
|
1
4
|
import { inferRenderPipeline } from '@developmentseed/deck.gl-geotiff';
|
|
2
5
|
import { Colormap, FilterNoDataVal, LinearRescale } from '@developmentseed/deck.gl-raster/gpu-modules';
|
|
3
6
|
import loadEpsg from '@developmentseed/epsg/all';
|
|
4
7
|
import epsgCsvUrl from '@developmentseed/epsg/all.csv.gz?url';
|
|
5
8
|
import { GeoTIFF } from '@developmentseed/geotiff';
|
|
6
9
|
import { parseWkt } from '@developmentseed/proj';
|
|
10
|
+
import { buildDataTypeLabel, clampBounds, SF_LABELS, safeClamp } from '@walkthru-earth/objex-utils';
|
|
7
11
|
import proj4Lib from 'proj4';
|
|
8
|
-
import {
|
|
12
|
+
import { HISTOGRAM_BINS, readGdalStats, streamHistogram } from './cog-histogram.js';
|
|
9
13
|
import { COLORMAP_INDEX, getColormapTexture } from './colormap-sprite.js';
|
|
10
|
-
export { buildDataTypeLabel, clampBounds, SF_LABELS, safeClamp };
|
|
11
|
-
// ─── Constants ───────────────────────────────────────────────────
|
|
12
14
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* which the Apple-GPU path of luma.gl's WebGL2 backend rejects with
|
|
17
|
-
* `ERROR: 'sampler2DArray' : No precision specified`. In GLSL ES 3.00,
|
|
18
|
-
* every sampler type other than `sampler2D`/`samplerCube` needs explicit
|
|
19
|
-
* precision in fragment shaders.
|
|
15
|
+
* Open a `GeoTIFF` from a URL, priming `SourceHttp.metadata.size` before
|
|
16
|
+
* chunked reads start. Replaces `GeoTIFF.fromUrl` which skips the head and
|
|
17
|
+
* can leave size unset (chunkd#1666, stac-map#459).
|
|
20
18
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
19
|
+
* Primer strategy depends on whether the URL is SigV4-query-string signed.
|
|
20
|
+
* `presign.ts` signs URLs with `aws4fetch.signQuery({method: 'GET'})`, which
|
|
21
|
+
* binds the HTTP method into the signature, so a bare `HEAD` against a
|
|
22
|
+
* `GET`-signed URL 403s with `SignatureDoesNotMatch`. For signed URLs we
|
|
23
|
+
* use a 1-byte `Range: bytes=0-0` GET that returns `Content-Range:
|
|
24
|
+
* bytes 0-0/<TOTAL>`. For unsigned public URLs we use `source.head()`,
|
|
25
|
+
* which returns `Content-Length: <TOTAL>` directly.
|
|
26
|
+
*
|
|
27
|
+
* Why the split: the Range primer's total-size depends on the
|
|
28
|
+
* `Content-Range` response header being CORS-exposed via
|
|
29
|
+
* `Access-Control-Expose-Headers`. GCS public buckets only expose
|
|
30
|
+
* `Content-Length` and a handful of `X-Goog-*` headers (NOT
|
|
31
|
+
* `Content-Range`), so `response.headers.get('content-range')` returns
|
|
32
|
+
* null in the browser, `SourceHttp.metadata.size` falls back to
|
|
33
|
+
* `Content-Length` = 1, and `Tiff.readHeader` throws "offset is outside
|
|
34
|
+
* the bounds of the DataView" on the first IFD read past byte 0. HEAD
|
|
35
|
+
* avoids this entirely because `Content-Length` is a CORS-safelisted
|
|
36
|
+
* response header.
|
|
24
37
|
*/
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
'
|
|
38
|
+
function isSignedQueryUrl(href) {
|
|
39
|
+
try {
|
|
40
|
+
const url = new URL(href);
|
|
41
|
+
const params = url.searchParams;
|
|
42
|
+
return params.has('X-Amz-Signature') || params.has('X-Goog-Signature') || params.has('sig');
|
|
30
43
|
}
|
|
31
|
-
|
|
44
|
+
catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function loadGeoTIFF(href, options = {}) {
|
|
49
|
+
const { chunkSize = 32 * 1024, cacheSize = 1024 * 1024 } = options;
|
|
50
|
+
const source = new SourceHttp(href, {});
|
|
51
|
+
if (isSignedQueryUrl(href)) {
|
|
52
|
+
await source.fetch(0, 1);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
await source.head();
|
|
56
|
+
}
|
|
57
|
+
const chunk = new SourceChunk({ size: chunkSize });
|
|
58
|
+
const cache = new SourceCache({ size: cacheSize });
|
|
59
|
+
const view = new SourceView(source, [chunk, cache]);
|
|
60
|
+
return await GeoTIFF.open({ dataSource: source, headerSource: view });
|
|
61
|
+
}
|
|
62
|
+
export { buildDataTypeLabel, clampBounds, HISTOGRAM_BINS, readGdalStats, SF_LABELS, safeClamp, streamHistogram };
|
|
32
63
|
/** Create a sensible default band config based on COG metadata. */
|
|
33
64
|
export function defaultBandConfig(bandCount, sampleFormat) {
|
|
34
65
|
if (bandCount >= 3 && bandCount <= 4) {
|
|
@@ -113,11 +144,183 @@ export function needsCustomPipelineForConfig(geotiff, config) {
|
|
|
113
144
|
return true;
|
|
114
145
|
return false;
|
|
115
146
|
}
|
|
147
|
+
export const DEFAULT_NODATA_CONFIG = { mode: 'auto' };
|
|
148
|
+
/**
|
|
149
|
+
* Read the GDAL_NODATA tag from a GeoTIFF.
|
|
150
|
+
*
|
|
151
|
+
* GDAL convention: the tag is an ASCII string serialized via `Number()`; the
|
|
152
|
+
* literal `"nan"` (case-insensitive) round-trips to JS `NaN`. The library's
|
|
153
|
+
* `geotiff.nodata` getter already performs that `Number(rawString)` conversion,
|
|
154
|
+
* so this helper just exposes the resulting `number | null` (preserving `NaN`)
|
|
155
|
+
* behind a stable name for callers that want to drive `NodataConfig`.
|
|
156
|
+
*/
|
|
157
|
+
export function readGdalNodata(geotiff) {
|
|
158
|
+
return geotiff.nodata;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Custom shader module that discards pixels whose `color.r` is NaN. Float COGs
|
|
162
|
+
* commonly encode nodata as NaN, but the shipped `FilterNoDataVal` does a
|
|
163
|
+
* `color.r == nodata.value` comparison which IEEE 754 always returns false for
|
|
164
|
+
* NaN. Mirrors the reference implementation in `src/render/shader-modules.ts`.
|
|
165
|
+
*
|
|
166
|
+
* Slots into a `RasterModule[]` render pipeline the same way as the upstream
|
|
167
|
+
* modules (e.g. `LinearRescale`).
|
|
168
|
+
*/
|
|
169
|
+
// Typed as RasterModule['module'] (a luma.gl ShaderModule) so it composes
|
|
170
|
+
// with the upstream-shipped modules. No uniforms / no getUniforms needed.
|
|
171
|
+
export const FilterNaN = {
|
|
172
|
+
name: 'filterNaN',
|
|
173
|
+
inject: {
|
|
174
|
+
'fs:DECKGL_FILTER_COLOR': /* glsl */ `
|
|
175
|
+
if (isnan(color.r)) {
|
|
176
|
+
discard;
|
|
177
|
+
}
|
|
178
|
+
`
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* Pick the correct nodata-discard module for a resolved nodata value.
|
|
183
|
+
*
|
|
184
|
+
* - `NaN` → `FilterNaN` (IEEE 754: `x == NaN` is always false).
|
|
185
|
+
* - finite number → `FilterNoDataVal { value: nodata / sampleScale }`.
|
|
186
|
+
* - `null` / non-finite (e.g. ±Infinity) → no module (nodata off).
|
|
187
|
+
*
|
|
188
|
+
* `sampleScale` divides the raw nodata value to put it in the same coordinate
|
|
189
|
+
* space the shader sees after hardware normalization (e.g. 255 for r8unorm,
|
|
190
|
+
* 65535 for r16unorm). Pass `1` when the shader receives raw float values.
|
|
191
|
+
*/
|
|
192
|
+
export function nodataModule(nodata, sampleScale = 1) {
|
|
193
|
+
if (nodata === null)
|
|
194
|
+
return null;
|
|
195
|
+
if (Number.isNaN(nodata))
|
|
196
|
+
return { module: FilterNaN };
|
|
197
|
+
if (!Number.isFinite(nodata))
|
|
198
|
+
return null;
|
|
199
|
+
return {
|
|
200
|
+
module: FilterNoDataVal,
|
|
201
|
+
props: { value: nodata / sampleScale }
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/** Resolve a tri-state `NodataConfig` against the auto-detected GDAL_NODATA value. */
|
|
205
|
+
export function resolveNodata(cfg, autoNodata) {
|
|
206
|
+
if (cfg.mode === 'off')
|
|
207
|
+
return null;
|
|
208
|
+
if (cfg.mode === 'value')
|
|
209
|
+
return cfg.value ?? null;
|
|
210
|
+
return autoNodata;
|
|
211
|
+
}
|
|
116
212
|
export const DEFAULT_RESCALE = { min: 0, max: 1 };
|
|
117
213
|
/** True when the rescale values would produce a visible change on the GPU. */
|
|
118
214
|
export function isRescaleActive(cfg) {
|
|
119
215
|
return cfg.min !== DEFAULT_RESCALE.min || cfg.max !== DEFAULT_RESCALE.max;
|
|
120
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Pick a sensible default `RescaleConfig` for a freshly opened COG. The slider
|
|
219
|
+
* operates in normalized shader space [0, 1], but the GPU's hardware
|
|
220
|
+
* normalization (`r8unorm` / `r16unorm` in `MultiCOGLayer`, or the library
|
|
221
|
+
* default uint pipeline elsewhere) collapses raw integer values onto that
|
|
222
|
+
* range by dividing by the format's max (255 for uint8, 65535 for uint16).
|
|
223
|
+
*
|
|
224
|
+
* For uint8 visual COGs (Sentinel-2 `visual` TCI, NAIP `image`) the natural
|
|
225
|
+
* land brightness sits around raw 50-100, so `max: 0.3` (≈ raw 76) gives a
|
|
226
|
+
* nicely contrasted preview. For uint16 reflectance bands (Sentinel-2 raw
|
|
227
|
+
* `nir` / `swir16` / `red`, Landsat C2 L2 `*_B*`) typical land surfaces sit at
|
|
228
|
+
* raw 800-3000 (reflectance × 10000), which is `0.012-0.046` after r16unorm.
|
|
229
|
+
* `max: 0.3` would render those near-black; `max: 0.05` (≈ raw 3277) keeps
|
|
230
|
+
* vegetation, soil, and water in the visible range while leaving headroom for
|
|
231
|
+
* brighter targets.
|
|
232
|
+
*
|
|
233
|
+
* Float / int sample formats fall back to the conservative `{0, 1}` no-op so
|
|
234
|
+
* the user can dial in their own range via the slider.
|
|
235
|
+
*/
|
|
236
|
+
export function defaultRescaleForGeotiff(geotiff) {
|
|
237
|
+
const tags = geotiff.cachedTags;
|
|
238
|
+
const sampleFormat = tags.sampleFormat?.[0] ?? 1;
|
|
239
|
+
if (sampleFormat !== 1)
|
|
240
|
+
return { ...DEFAULT_RESCALE };
|
|
241
|
+
const bps = tags.bitsPerSample?.[0] ?? 8;
|
|
242
|
+
if (bps <= 8)
|
|
243
|
+
return { min: 0, max: 0.3 };
|
|
244
|
+
return { min: 0, max: 0.05 };
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Build a 64-bin histogram of band 0 from a GeoTIFF's smallest overview, in
|
|
248
|
+
* the same shader-space [0, 1] coordinate system the rescale slider operates
|
|
249
|
+
* on (raw / 65535 for uint16, raw / 255 for uint8, raw clamped to [0, 1] for
|
|
250
|
+
* float). Used by viewers on the multi-asset MultiCOGLayer path to give the
|
|
251
|
+
* rescale slider a histogram backdrop without hooking per-tile sampling into
|
|
252
|
+
* the layer.
|
|
253
|
+
*
|
|
254
|
+
* Returns null if the smallest overview cannot be fetched. Skips the GeoTIFF's
|
|
255
|
+
* declared nodata value and non-finite values.
|
|
256
|
+
*/
|
|
257
|
+
/**
|
|
258
|
+
* Walk a cumulative histogram (`HISTOGRAM_BIN_COUNT` bins covering [0, 1])
|
|
259
|
+
* and return the shader-space value at percentile `p` (0..1). Returns null
|
|
260
|
+
* when the histogram is empty. Linearly interpolates within the matching bin
|
|
261
|
+
* so the result is monotonic across calls with adjacent percentiles, instead
|
|
262
|
+
* of jumping in `1/HISTOGRAM_BIN_COUNT` increments.
|
|
263
|
+
*/
|
|
264
|
+
export function percentileFromHistogram(histogram, p) {
|
|
265
|
+
if (!histogram || histogram.length !== HISTOGRAM_BIN_COUNT)
|
|
266
|
+
return null;
|
|
267
|
+
let total = 0;
|
|
268
|
+
for (let i = 0; i < HISTOGRAM_BIN_COUNT; i++)
|
|
269
|
+
total += histogram[i];
|
|
270
|
+
if (total === 0)
|
|
271
|
+
return null;
|
|
272
|
+
const target = total * Math.max(0, Math.min(1, p));
|
|
273
|
+
let acc = 0;
|
|
274
|
+
for (let i = 0; i < HISTOGRAM_BIN_COUNT; i++) {
|
|
275
|
+
const next = acc + histogram[i];
|
|
276
|
+
if (next >= target) {
|
|
277
|
+
const frac = histogram[i] === 0 ? 0 : (target - acc) / histogram[i];
|
|
278
|
+
return (i + frac) / HISTOGRAM_BIN_COUNT;
|
|
279
|
+
}
|
|
280
|
+
acc = next;
|
|
281
|
+
}
|
|
282
|
+
return 1;
|
|
283
|
+
}
|
|
284
|
+
export async function buildHistogramFromGeotiff(geotiff, signal) {
|
|
285
|
+
const tags = geotiff.cachedTags;
|
|
286
|
+
const sampleFormat = tags.sampleFormat?.[0] ?? 1;
|
|
287
|
+
const bps = tags.bitsPerSample?.[0] ?? 8;
|
|
288
|
+
const norm = sampleFormat === 1 ? (bps <= 8 ? 255 : 65535) : 1;
|
|
289
|
+
const nodata = geotiff.nodata;
|
|
290
|
+
const overviews = geotiff.overviews ?? [];
|
|
291
|
+
const sourceImage = overviews.length ? overviews[overviews.length - 1] : geotiff;
|
|
292
|
+
try {
|
|
293
|
+
const tile = await sourceImage.fetchTile(0, 0, { signal });
|
|
294
|
+
if (signal?.aborted)
|
|
295
|
+
return null;
|
|
296
|
+
const arr = tile.array;
|
|
297
|
+
const data = arr.layout === 'band-separate' ? arr.bands[0] : arr.data;
|
|
298
|
+
const stride = arr.layout === 'band-separate' ? 1 : (arr.count ?? 1);
|
|
299
|
+
const histogram = new Uint32Array(HISTOGRAM_BIN_COUNT);
|
|
300
|
+
let counted = 0;
|
|
301
|
+
const len = data.length;
|
|
302
|
+
for (let i = 0; i < len; i += stride) {
|
|
303
|
+
const raw = data[i];
|
|
304
|
+
if (!Number.isFinite(raw))
|
|
305
|
+
continue;
|
|
306
|
+
if (nodata !== null && raw === nodata)
|
|
307
|
+
continue;
|
|
308
|
+
const t = sampleFormat === 1 ? raw / norm : Math.max(0, Math.min(1, raw));
|
|
309
|
+
if (t < 0 || t > 1)
|
|
310
|
+
continue;
|
|
311
|
+
const bin = Math.min(HISTOGRAM_BIN_COUNT - 1, Math.floor(t * HISTOGRAM_BIN_COUNT));
|
|
312
|
+
histogram[bin]++;
|
|
313
|
+
counted++;
|
|
314
|
+
}
|
|
315
|
+
if (counted === 0)
|
|
316
|
+
return null;
|
|
317
|
+
return histogram;
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
/* swallow */
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
121
324
|
/**
|
|
122
325
|
* Build a `getTileData` + `renderTile` pair that reuses the library-default
|
|
123
326
|
* uint pipeline (via `inferRenderPipeline`) and appends `LinearRescale` to the
|
|
@@ -137,7 +340,7 @@ export function createRescaledPipeline(geotiff, rescale) {
|
|
|
137
340
|
if (builtFor === device && defaultGetTileData && defaultRenderTile)
|
|
138
341
|
return;
|
|
139
342
|
const inferred = inferRenderPipeline(geotiff, device);
|
|
140
|
-
// `inferRenderPipeline` returns generic callbacks. `
|
|
343
|
+
// `inferRenderPipeline` returns generic callbacks. `MinimalTileData` is the
|
|
141
344
|
// contractual superset used by COGLayer — safe upcast.
|
|
142
345
|
defaultGetTileData = inferred.getTileData;
|
|
143
346
|
defaultRenderTile = inferred.renderTile;
|
|
@@ -163,17 +366,18 @@ export function createRescaledPipeline(geotiff, rescale) {
|
|
|
163
366
|
}
|
|
164
367
|
/**
|
|
165
368
|
* Build a `renderPipeline` array for `MultiCOGLayer` / raster mosaics.
|
|
166
|
-
* Combines optional
|
|
167
|
-
* the GPU expects (no-data mask first, then rescale).
|
|
369
|
+
* Combines an optional nodata-discard module + `LinearRescale` in the order
|
|
370
|
+
* the GPU expects (no-data mask first, then rescale). The nodata module is
|
|
371
|
+
* selected by `nodataModule()` so NaN sentinels route through `FilterNaN`
|
|
372
|
+
* instead of the always-false `==` comparison in `FilterNoDataVal`.
|
|
168
373
|
*/
|
|
169
374
|
export function buildBandRenderPipeline(opts = {}) {
|
|
170
375
|
const modules = [];
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
376
|
+
const nodataMod = opts.noDataVal !== undefined
|
|
377
|
+
? nodataModule(opts.noDataVal ?? null, opts.noDataSampleScale ?? 1)
|
|
378
|
+
: null;
|
|
379
|
+
if (nodataMod)
|
|
380
|
+
modules.push(nodataMod);
|
|
177
381
|
if (opts.rescale && isRescaleActive(opts.rescale)) {
|
|
178
382
|
modules.push({
|
|
179
383
|
module: LinearRescale,
|
|
@@ -240,8 +444,11 @@ export function selectCogPipeline(geotiff, opts = {}) {
|
|
|
240
444
|
? needsCustomPipelineForConfig(geotiff, bandConfig)
|
|
241
445
|
: needsCustomPipeline(geotiff);
|
|
242
446
|
if (useCustom && bandConfig) {
|
|
447
|
+
// Note: callers that want identity-stable getTileData across config
|
|
448
|
+
// swaps should call `createConfigurableGetTileData` directly and reuse
|
|
449
|
+
// the loader, rather than re-running selectCogPipeline.
|
|
243
450
|
return {
|
|
244
|
-
getTileData: createConfigurableGetTileData(geotiff, bandConfig),
|
|
451
|
+
getTileData: createConfigurableGetTileData(geotiff, bandConfig).getTileData,
|
|
245
452
|
renderTile: buildCustomRenderTile(bandConfig, rescale)
|
|
246
453
|
};
|
|
247
454
|
}
|
|
@@ -268,7 +475,7 @@ const BITMAP_SOURCE = 'geotiff-bitmap-src';
|
|
|
268
475
|
const BITMAP_LAYER = 'geotiff-bitmap-layer';
|
|
269
476
|
// ─── Types & pure helpers ────────────────────────────────────────
|
|
270
477
|
// `GeoBounds`, `CogInfo`, `safeClamp`, `clampBounds`, `buildDataTypeLabel`
|
|
271
|
-
// live in
|
|
478
|
+
// live in `cog-info.ts` in @walkthru-earth/objex-utils and are re-exported at the top of this file.
|
|
272
479
|
// ─── Map helpers (depend on maplibre-gl) ─────────────────────────
|
|
273
480
|
/**
|
|
274
481
|
* Query the GPU's MAX_TEXTURE_SIZE from MapLibre's WebGL context.
|
|
@@ -431,7 +638,7 @@ const MAX_NONTILED_PIXELS = 100_000_000;
|
|
|
431
638
|
export async function renderNonTiledBitmap(options) {
|
|
432
639
|
const { url, map, signal } = options;
|
|
433
640
|
// Open GeoTIFF (reuse if already opened for pre-flight)
|
|
434
|
-
const geotiff = options.geotiff ?? (await
|
|
641
|
+
const geotiff = options.geotiff ?? (await loadGeoTIFF(url));
|
|
435
642
|
if (signal.aborted)
|
|
436
643
|
throw new DOMException('Aborted', 'AbortError');
|
|
437
644
|
const imgW = geotiff.width;
|
|
@@ -577,8 +784,15 @@ export function needsCustomPipeline(geotiff) {
|
|
|
577
784
|
// sampleFormat is null or not uint → needs custom
|
|
578
785
|
return sf === null || sf[0] !== 1;
|
|
579
786
|
}
|
|
580
|
-
/**
|
|
581
|
-
|
|
787
|
+
/**
|
|
788
|
+
* Number of histogram buckets produced by the CPU bake.
|
|
789
|
+
*
|
|
790
|
+
* Canonical value lives in `./cog-histogram.ts` as `HISTOGRAM_BINS`; this
|
|
791
|
+
* is kept as an alias so existing CogViewer / StacMosaicViewer imports
|
|
792
|
+
* continue to compile. Both names resolve to the same number — see
|
|
793
|
+
* `./cog-histogram.ts` for the streaming histogram + GDAL stats reader.
|
|
794
|
+
*/
|
|
795
|
+
export const HISTOGRAM_BIN_COUNT = HISTOGRAM_BINS;
|
|
582
796
|
/**
|
|
583
797
|
* Create custom getTileData for non-uint COGs.
|
|
584
798
|
* Reads band 0, normalizes using GDAL statistics / per-tile adaptive stretch,
|
|
@@ -750,11 +964,7 @@ export function buildCustomRenderTile(config, rescale) {
|
|
|
750
964
|
props: { rescaleMin: rescale.min, rescaleMax: rescale.max }
|
|
751
965
|
});
|
|
752
966
|
}
|
|
753
|
-
pipeline.push(
|
|
754
|
-
// Precision shim must come before Colormap, its `fs:#decl` inject
|
|
755
|
-
// declares `precision highp sampler2DArray;` so the subsequent
|
|
756
|
-
// sampler uniform compiles on WebGL2 / Apple GPU.
|
|
757
|
-
{ module: Sampler2DArrayPrecision, props: {} }, {
|
|
967
|
+
pipeline.push({
|
|
758
968
|
module: Colormap,
|
|
759
969
|
props: {
|
|
760
970
|
colormapTexture: data.colormapTexture,
|
|
@@ -823,17 +1033,27 @@ function computeBandRanges(bands, bandIndices, pixelCount, nodata) {
|
|
|
823
1033
|
* Supports RGB mode (multi-band → R,G,B with alpha=255, fully baked) and
|
|
824
1034
|
* single-band mode (band N normalized into the `r` channel; the ramp is
|
|
825
1035
|
* applied downstream by the GPU `Colormap` module via `buildCustomRenderTile`).
|
|
1036
|
+
*
|
|
1037
|
+
* The returned `getTileData` reference is stable across `updateConfig` calls.
|
|
1038
|
+
* deck.gl's TileLayer treats a changed `getTileData` identity as a cache
|
|
1039
|
+
* invalidation, so reusing this loader across band/ramp swaps preserves tiles.
|
|
826
1040
|
*/
|
|
827
1041
|
export function createConfigurableGetTileData(geotiff, config, _opts = {}) {
|
|
828
1042
|
const bandCount = geotiff.count;
|
|
829
|
-
//
|
|
830
|
-
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
1043
|
+
// Mutable refs read on every tile bake. Mutating in place keeps the closure
|
|
1044
|
+
// — and therefore the function reference — stable across config swaps.
|
|
1045
|
+
const refs = {
|
|
1046
|
+
config,
|
|
1047
|
+
// Shared per-band ranges across tiles (seeded on first tile, widened by subsequent)
|
|
1048
|
+
sharedMins: new Map(),
|
|
1049
|
+
sharedMaxs: new Map(),
|
|
1050
|
+
// Resolve the sprite texture from the first tile's device; reuse per-device.
|
|
1051
|
+
texturePromise: null
|
|
1052
|
+
};
|
|
1053
|
+
const getTileData = async (image, options) => {
|
|
1054
|
+
const currentConfig = refs.config;
|
|
1055
|
+
if (currentConfig.mode === 'single' && !refs.texturePromise) {
|
|
1056
|
+
refs.texturePromise = getColormapTexture(options.device);
|
|
837
1057
|
}
|
|
838
1058
|
const [tile, colormapTexture] = await Promise.all([
|
|
839
1059
|
image.fetchTile(options.x, options.y, {
|
|
@@ -842,7 +1062,7 @@ export function createConfigurableGetTileData(geotiff, config, _opts = {}) {
|
|
|
842
1062
|
pool: options.pool,
|
|
843
1063
|
signal: options.signal
|
|
844
1064
|
}),
|
|
845
|
-
texturePromise ?? Promise.resolve(undefined)
|
|
1065
|
+
refs.texturePromise ?? Promise.resolve(undefined)
|
|
846
1066
|
]);
|
|
847
1067
|
const arr = tile.array;
|
|
848
1068
|
const { width, height, nodata } = arr;
|
|
@@ -853,27 +1073,27 @@ export function createConfigurableGetTileData(geotiff, config, _opts = {}) {
|
|
|
853
1073
|
// object. Cloud-native by construction: at each zoom level, COG only
|
|
854
1074
|
// decodes the overview tiles that cover the viewport, so the summed
|
|
855
1075
|
// histogram naturally reflects "what the user is looking at right now".
|
|
856
|
-
const histogram =
|
|
857
|
-
if (
|
|
1076
|
+
const histogram = currentConfig.mode === 'single' ? new Uint32Array(HISTOGRAM_BIN_COUNT) : null;
|
|
1077
|
+
if (currentConfig.mode === 'rgb') {
|
|
858
1078
|
// RGB mode: map 3 bands to R, G, B
|
|
859
|
-
const indices = [
|
|
1079
|
+
const indices = [currentConfig.rBand, currentConfig.gBand, currentConfig.bBand];
|
|
860
1080
|
// Compute ranges for the 3 selected bands
|
|
861
1081
|
for (const bi of indices) {
|
|
862
|
-
if (!sharedMins.has(bi)) {
|
|
1082
|
+
if (!refs.sharedMins.has(bi)) {
|
|
863
1083
|
const { mins, maxs } = computeBandRanges(bands, [bi], pixelCount, nodata);
|
|
864
|
-
sharedMins.set(bi, mins[0]);
|
|
865
|
-
sharedMaxs.set(bi, maxs[0]);
|
|
1084
|
+
refs.sharedMins.set(bi, mins[0]);
|
|
1085
|
+
refs.sharedMaxs.set(bi, maxs[0]);
|
|
866
1086
|
}
|
|
867
1087
|
}
|
|
868
|
-
const rBand = bands[
|
|
869
|
-
const gBand = bands[
|
|
870
|
-
const bBand = bands[
|
|
871
|
-
const rMin = sharedMins.get(
|
|
872
|
-
const rMax = sharedMaxs.get(
|
|
873
|
-
const gMin = sharedMins.get(
|
|
874
|
-
const gMax = sharedMaxs.get(
|
|
875
|
-
const bMin = sharedMins.get(
|
|
876
|
-
const bMax = sharedMaxs.get(
|
|
1088
|
+
const rBand = bands[currentConfig.rBand];
|
|
1089
|
+
const gBand = bands[currentConfig.gBand];
|
|
1090
|
+
const bBand = bands[currentConfig.bBand];
|
|
1091
|
+
const rMin = refs.sharedMins.get(currentConfig.rBand);
|
|
1092
|
+
const rMax = refs.sharedMaxs.get(currentConfig.rBand);
|
|
1093
|
+
const gMin = refs.sharedMins.get(currentConfig.gBand);
|
|
1094
|
+
const gMax = refs.sharedMaxs.get(currentConfig.gBand);
|
|
1095
|
+
const bMin = refs.sharedMins.get(currentConfig.bBand);
|
|
1096
|
+
const bMax = refs.sharedMaxs.get(currentConfig.bBand);
|
|
877
1097
|
const rRange = rMax - rMin || 1;
|
|
878
1098
|
const gRange = gMax - gMin || 1;
|
|
879
1099
|
const bRange = bMax - bMin || 1;
|
|
@@ -902,15 +1122,15 @@ export function createConfigurableGetTileData(geotiff, config, _opts = {}) {
|
|
|
902
1122
|
// Single-band mode: normalize the selected band into the `r`
|
|
903
1123
|
// channel and reserve `r = 0` as a nodata sentinel that
|
|
904
1124
|
// `FilterNoDataVal` discards before the `Colormap` GPU lookup.
|
|
905
|
-
const bi =
|
|
1125
|
+
const bi = currentConfig.band;
|
|
906
1126
|
const bandData = bands[bi];
|
|
907
|
-
if (!sharedMins.has(bi) && bandData) {
|
|
1127
|
+
if (!refs.sharedMins.has(bi) && bandData) {
|
|
908
1128
|
const { mins, maxs } = computeBandRanges(bands, [bi], pixelCount, nodata);
|
|
909
|
-
sharedMins.set(bi, mins[0]);
|
|
910
|
-
sharedMaxs.set(bi, maxs[0]);
|
|
1129
|
+
refs.sharedMins.set(bi, mins[0]);
|
|
1130
|
+
refs.sharedMaxs.set(bi, maxs[0]);
|
|
911
1131
|
}
|
|
912
|
-
const rangeMin = sharedMins.get(bi) ?? 0;
|
|
913
|
-
const rangeMax = sharedMaxs.get(bi) ?? 1;
|
|
1132
|
+
const rangeMin = refs.sharedMins.get(bi) ?? 0;
|
|
1133
|
+
const rangeMax = refs.sharedMaxs.get(bi) ?? 1;
|
|
914
1134
|
const range = rangeMax - rangeMin || 1;
|
|
915
1135
|
for (let i = 0; i < pixelCount; i++) {
|
|
916
1136
|
const raw = bandData?.[i] ?? 0;
|
|
@@ -939,11 +1159,29 @@ export function createConfigurableGetTileData(geotiff, config, _opts = {}) {
|
|
|
939
1159
|
imageData: new ImageData(rgba, width, height),
|
|
940
1160
|
width,
|
|
941
1161
|
height,
|
|
942
|
-
colormapTexture:
|
|
943
|
-
nodataSentinel:
|
|
1162
|
+
colormapTexture: currentConfig.mode === 'single' ? colormapTexture : undefined,
|
|
1163
|
+
nodataSentinel: currentConfig.mode === 'single' ? 0 : undefined,
|
|
944
1164
|
histogram: histogram ?? undefined
|
|
945
1165
|
};
|
|
946
1166
|
};
|
|
1167
|
+
return {
|
|
1168
|
+
getTileData,
|
|
1169
|
+
updateConfig(next) {
|
|
1170
|
+
// Mode/band swap implies different per-band ranges; clear so the next
|
|
1171
|
+
// tile reseeds them. The function identity itself is preserved.
|
|
1172
|
+
const prev = refs.config;
|
|
1173
|
+
refs.config = next;
|
|
1174
|
+
const bandsChanged = prev.mode !== next.mode ||
|
|
1175
|
+
(prev.mode === 'rgb' &&
|
|
1176
|
+
next.mode === 'rgb' &&
|
|
1177
|
+
(prev.rBand !== next.rBand || prev.gBand !== next.gBand || prev.bBand !== next.bBand)) ||
|
|
1178
|
+
(prev.mode === 'single' && next.mode === 'single' && prev.band !== next.band);
|
|
1179
|
+
if (bandsChanged) {
|
|
1180
|
+
refs.sharedMins.clear();
|
|
1181
|
+
refs.sharedMaxs.clear();
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
947
1185
|
}
|
|
948
1186
|
// ─── EPSG resolution via bundled database ────────────────────────
|
|
949
1187
|
/**
|
|
@@ -1030,13 +1268,58 @@ export async function resolveProj4Def(crs, _signal) {
|
|
|
1030
1268
|
// ProjJSON — stringify for proj4
|
|
1031
1269
|
return JSON.stringify(crs);
|
|
1032
1270
|
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Standard Web Mercator ground resolution (meters per screen pixel) at a given
|
|
1273
|
+
* zoom and latitude. Equivalent to MapLibre's `metersPerPixel`. Use this to
|
|
1274
|
+
* pick a COG overview level that matches what's painted on screen.
|
|
1275
|
+
*/
|
|
1276
|
+
export function mapResolutionMetersPerPixel(zoom, latitude) {
|
|
1277
|
+
return (156543.03392 * Math.cos((latitude * Math.PI) / 180)) / 2 ** zoom;
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Pick the coarsest overview whose pixel size is ≤ `targetMetersPerPixel`,
|
|
1281
|
+
* matching lazycogs' `_select_overview`. Walks `geotiff.overviews` in
|
|
1282
|
+
* finest → coarsest order (the documented sort) and returns the last entry
|
|
1283
|
+
* that still satisfies the constraint. Returns `null` to mean "use full
|
|
1284
|
+
* resolution" (either the COG has no overviews, the target is already finer
|
|
1285
|
+
* than native, or every overview is coarser than the target — never
|
|
1286
|
+
* upsample).
|
|
1287
|
+
*
|
|
1288
|
+
* The COG's affine `a` component is the X-pixel-size in the COG's native CRS
|
|
1289
|
+
* units. For Web Mercator and other meter-based CRSes this is meters/pixel
|
|
1290
|
+
* and lines up directly with `mapResolutionMetersPerPixel`. For degree-based
|
|
1291
|
+
* CRSes (EPSG:4326) the comparison is against degrees/pixel, so callers
|
|
1292
|
+
* should derive the target in the same units.
|
|
1293
|
+
*/
|
|
1294
|
+
export function selectOverviewForResolution(geotiff, targetMetersPerPixel) {
|
|
1295
|
+
if (!geotiff.overviews.length)
|
|
1296
|
+
return null;
|
|
1297
|
+
const nativeRes = Math.abs(geotiff.transform[0]);
|
|
1298
|
+
if (targetMetersPerPixel <= nativeRes)
|
|
1299
|
+
return null;
|
|
1300
|
+
let selected = null;
|
|
1301
|
+
for (const overview of geotiff.overviews) {
|
|
1302
|
+
if (Math.abs(overview.transform[0]) <= targetMetersPerPixel) {
|
|
1303
|
+
selected = overview;
|
|
1304
|
+
}
|
|
1305
|
+
else {
|
|
1306
|
+
break;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
return selected;
|
|
1310
|
+
}
|
|
1033
1311
|
/**
|
|
1034
1312
|
* Read pixel values at a given lng/lat from a GeoTIFF.
|
|
1035
1313
|
* Converts WGS84 → source CRS → pixel coords, fetches the tile, reads all bands.
|
|
1314
|
+
*
|
|
1315
|
+
* If `options.overview` is supplied, the read happens against that overview
|
|
1316
|
+
* level instead of the full-resolution image (so the inspected value matches
|
|
1317
|
+
* the overview deck.gl is currently painting on screen). Pass `null` /
|
|
1318
|
+
* `undefined` to keep the legacy full-resolution behaviour.
|
|
1036
1319
|
*/
|
|
1037
1320
|
export async function readPixelAtLngLat(geotiff, lng, lat, proj4Def,
|
|
1038
1321
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1039
|
-
pool, signal) {
|
|
1322
|
+
pool, signal, options) {
|
|
1040
1323
|
// Convert WGS84 to source CRS
|
|
1041
1324
|
let srcX = lng;
|
|
1042
1325
|
let srcY = lat;
|
|
@@ -1050,15 +1333,17 @@ pool, signal) {
|
|
|
1050
1333
|
return null;
|
|
1051
1334
|
}
|
|
1052
1335
|
}
|
|
1336
|
+
// Read against the selected overview if one was supplied, else full res.
|
|
1337
|
+
const source = options?.overview ?? geotiff;
|
|
1053
1338
|
// Get pixel indices (row, col)
|
|
1054
|
-
const [row, col] =
|
|
1055
|
-
if (row < 0 || row >=
|
|
1339
|
+
const [row, col] = source.index(srcX, srcY);
|
|
1340
|
+
if (row < 0 || row >= source.height || col < 0 || col >= source.width)
|
|
1056
1341
|
return null;
|
|
1057
1342
|
// Compute tile indices
|
|
1058
|
-
const tileX = Math.floor(col /
|
|
1059
|
-
const tileY = Math.floor(row /
|
|
1343
|
+
const tileX = Math.floor(col / source.tileWidth);
|
|
1344
|
+
const tileY = Math.floor(row / source.tileHeight);
|
|
1060
1345
|
// Fetch tile
|
|
1061
|
-
const tile = await
|
|
1346
|
+
const tile = await source.fetchTile(tileX, tileY, { pool, signal });
|
|
1062
1347
|
const arr = tile.array;
|
|
1063
1348
|
// Read all band values at this pixel
|
|
1064
1349
|
const localCol = col - tileX * arr.width;
|
|
@@ -17,8 +17,6 @@ export { COLORMAP_INDEX, type ColormapName } from '@developmentseed/deck.gl-rast
|
|
|
17
17
|
export declare const COLORMAP_SPRITE_URL: any;
|
|
18
18
|
/** Number of distinct ramps encoded as 1-pixel-tall rows in the sprite. */
|
|
19
19
|
export declare const COLORMAP_SPRITE_LAYERS: number;
|
|
20
|
-
/** Width of each ramp row in pixels (also the sampling resolution). */
|
|
21
|
-
export declare const COLORMAP_SPRITE_WIDTH = 256;
|
|
22
20
|
/** All ramp names, sorted alphabetically (matches `COLORMAP_INDEX` key order). */
|
|
23
21
|
export declare const COLORMAP_NAMES: ColormapName[];
|
|
24
22
|
/** Decode the shipped sprite once per session. Safe to call repeatedly. */
|
|
@@ -30,10 +28,3 @@ export declare function loadColormapSprite(): Promise<ImageData>;
|
|
|
30
28
|
* `Colormap` shader module.
|
|
31
29
|
*/
|
|
32
30
|
export declare function getColormapTexture(device: Device): Promise<Texture>;
|
|
33
|
-
/**
|
|
34
|
-
* CSS `background` properties that render a single colormap row from the
|
|
35
|
-
* shipped sprite. Vertically scales the sprite so each 1-pixel row fills
|
|
36
|
-
* the container's full height, then offsets to land on the requested layer.
|
|
37
|
-
* Returns `undefined` for unknown ramp names so the caller can fall back.
|
|
38
|
-
*/
|
|
39
|
-
export declare function spriteBackgroundStyle(name: ColormapName, heightPx: number): string | undefined;
|
|
@@ -21,8 +21,6 @@ export { COLORMAP_INDEX } from '@developmentseed/deck.gl-raster/gpu-modules';
|
|
|
21
21
|
export const COLORMAP_SPRITE_URL = colormapsPngUrl;
|
|
22
22
|
/** Number of distinct ramps encoded as 1-pixel-tall rows in the sprite. */
|
|
23
23
|
export const COLORMAP_SPRITE_LAYERS = Object.keys(COLORMAP_INDEX).length;
|
|
24
|
-
/** Width of each ramp row in pixels (also the sampling resolution). */
|
|
25
|
-
export const COLORMAP_SPRITE_WIDTH = 256;
|
|
26
24
|
/** All ramp names, sorted alphabetically (matches `COLORMAP_INDEX` key order). */
|
|
27
25
|
export const COLORMAP_NAMES = Object.keys(COLORMAP_INDEX).sort();
|
|
28
26
|
let spritePromise = null;
|
|
@@ -56,22 +54,3 @@ export async function getColormapTexture(device) {
|
|
|
56
54
|
textureCache.set(device, texture);
|
|
57
55
|
return texture;
|
|
58
56
|
}
|
|
59
|
-
/**
|
|
60
|
-
* CSS `background` properties that render a single colormap row from the
|
|
61
|
-
* shipped sprite. Vertically scales the sprite so each 1-pixel row fills
|
|
62
|
-
* the container's full height, then offsets to land on the requested layer.
|
|
63
|
-
* Returns `undefined` for unknown ramp names so the caller can fall back.
|
|
64
|
-
*/
|
|
65
|
-
export function spriteBackgroundStyle(name, heightPx) {
|
|
66
|
-
const index = COLORMAP_INDEX[name];
|
|
67
|
-
if (index === undefined)
|
|
68
|
-
return undefined;
|
|
69
|
-
const totalHeight = COLORMAP_SPRITE_LAYERS * heightPx;
|
|
70
|
-
const yOffset = index * heightPx;
|
|
71
|
-
return [
|
|
72
|
-
`background-image: url("${COLORMAP_SPRITE_URL}")`,
|
|
73
|
-
'background-repeat: no-repeat',
|
|
74
|
-
`background-size: 100% ${totalHeight}px`,
|
|
75
|
-
`background-position: 0 -${yOffset}px`
|
|
76
|
-
].join('; ');
|
|
77
|
-
}
|