@walkthru-earth/objex 1.2.1 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/dist/components/layout/ConnectionDialog.svelte +35 -3
- package/dist/components/layout/Sidebar.svelte +1 -2
- package/dist/components/viewers/CodeViewer.svelte +51 -14
- package/dist/components/viewers/CodeViewer.svelte.d.ts +11 -1
- package/dist/components/viewers/CogControls.svelte +151 -22
- package/dist/components/viewers/CogControls.svelte.d.ts +5 -1
- package/dist/components/viewers/CogViewer.svelte +75 -8
- package/dist/components/viewers/MultiCogViewer.svelte +416 -0
- package/dist/components/viewers/MultiCogViewer.svelte.d.ts +9 -0
- package/dist/components/viewers/StacMapViewer.svelte +19 -5
- package/dist/components/viewers/StacMapViewer.svelte.d.ts +1 -0
- package/dist/components/viewers/StacMosaicViewer.svelte +785 -0
- package/dist/components/viewers/StacMosaicViewer.svelte.d.ts +9 -0
- package/dist/components/viewers/StacTabViewer.svelte +254 -0
- package/dist/components/viewers/StacTabViewer.svelte.d.ts +13 -0
- package/dist/components/viewers/ViewerRouter.svelte +155 -2
- package/dist/components/viewers/ViewerRouter.svelte.d.ts +1 -1
- package/dist/components/viewers/ZarrMapViewer.svelte +143 -4
- package/dist/components/viewers/ZarrMapViewer.svelte.d.ts +8 -2
- package/dist/components/viewers/ZarrViewer.svelte +1 -0
- package/dist/i18n/ar.js +27 -0
- package/dist/i18n/en.js +27 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/query/stac-geoparquet.d.ts +31 -0
- package/dist/query/stac-geoparquet.js +136 -0
- package/dist/stores/connections.svelte.d.ts +38 -23
- package/dist/stores/connections.svelte.js +105 -114
- package/dist/utils/cog-pure.d.ts +25 -0
- package/dist/utils/cog-pure.js +35 -0
- package/dist/utils/cog.d.ts +88 -43
- package/dist/utils/cog.js +192 -152
- package/dist/utils/colormap-sprite.d.ts +39 -0
- package/dist/utils/colormap-sprite.js +77 -0
- package/dist/utils/connection-identity.d.ts +51 -0
- package/dist/utils/connection-identity.js +97 -0
- package/dist/utils/host-detection.js +48 -302
- package/dist/utils/parquet-metadata.d.ts +7 -1
- package/dist/utils/parquet-metadata.js +35 -1
- package/dist/utils/stac-geoparquet.d.ts +90 -0
- package/dist/utils/stac-geoparquet.js +223 -0
- package/dist/utils/stac-hydrate.d.ts +38 -0
- package/dist/utils/stac-hydrate.js +243 -0
- package/dist/utils/stac.d.ts +136 -0
- package/dist/utils/stac.js +176 -0
- package/dist/utils/storage-url.d.ts +26 -0
- package/dist/utils/storage-url.js +164 -28
- package/dist/utils/zarr.d.ts +34 -0
- package/dist/utils/zarr.js +94 -0
- package/package.json +14 -13
|
@@ -1,13 +1,56 @@
|
|
|
1
1
|
import { STORAGE_KEYS } from '../constants.js';
|
|
2
|
+
import { connectionIdentityKey } from '../utils/connection-identity.js';
|
|
2
3
|
import { loadFromStorage, persistToStorage } from '../utils/local-storage.js';
|
|
3
4
|
import { credentialStore, storeToNative } from './credentials.svelte.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
function toConnection(id, config) {
|
|
6
|
+
return {
|
|
7
|
+
id,
|
|
8
|
+
name: config.name,
|
|
9
|
+
provider: config.provider,
|
|
10
|
+
endpoint: config.endpoint,
|
|
11
|
+
bucket: config.bucket,
|
|
12
|
+
region: config.region,
|
|
13
|
+
anonymous: config.anonymous,
|
|
14
|
+
authMethod: config.authMethod,
|
|
15
|
+
rootPrefix: config.rootPrefix
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function applyCredentials(id, config) {
|
|
19
|
+
if (config.anonymous) {
|
|
20
|
+
credentialStore.remove(id);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (config.sas_token) {
|
|
24
|
+
const creds = { type: 'sas-token', sasToken: config.sas_token };
|
|
25
|
+
credentialStore.set(id, creds);
|
|
26
|
+
storeToNative(id, creds).catch(() => { });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (config.access_key && config.secret_key) {
|
|
30
|
+
const creds = {
|
|
31
|
+
type: 'sigv4',
|
|
32
|
+
accessKey: config.access_key,
|
|
33
|
+
secretKey: config.secret_key
|
|
34
|
+
};
|
|
35
|
+
credentialStore.set(id, creds);
|
|
36
|
+
storeToNative(id, creds).catch(() => { });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
credentialStore.remove(id);
|
|
40
|
+
}
|
|
7
41
|
function createConnectionsStore() {
|
|
8
42
|
let connections = $state([]);
|
|
9
43
|
let loaded = $state(false);
|
|
10
44
|
let dialogRequest = $state(0);
|
|
45
|
+
function persist() {
|
|
46
|
+
persistToStorage(STORAGE_KEYS.CONNECTIONS, connections);
|
|
47
|
+
}
|
|
48
|
+
function findByIdentity(input, excludeId) {
|
|
49
|
+
const key = connectionIdentityKey(input);
|
|
50
|
+
if (!key)
|
|
51
|
+
return undefined;
|
|
52
|
+
return connections.find((c) => c.id !== excludeId && connectionIdentityKey(c) === key);
|
|
53
|
+
}
|
|
11
54
|
return {
|
|
12
55
|
get items() {
|
|
13
56
|
return connections;
|
|
@@ -17,7 +60,7 @@ function createConnectionsStore() {
|
|
|
17
60
|
},
|
|
18
61
|
/**
|
|
19
62
|
* Load connections from localStorage.
|
|
20
|
-
* Safe to call multiple times
|
|
63
|
+
* Safe to call multiple times, subsequent calls are no-ops.
|
|
21
64
|
*/
|
|
22
65
|
async load() {
|
|
23
66
|
if (loaded)
|
|
@@ -25,112 +68,58 @@ function createConnectionsStore() {
|
|
|
25
68
|
connections = loadFromStorage(STORAGE_KEYS.CONNECTIONS, []);
|
|
26
69
|
loaded = true;
|
|
27
70
|
},
|
|
28
|
-
/**
|
|
29
|
-
* Force-reload connections.
|
|
30
|
-
*/
|
|
31
71
|
async reload() {
|
|
32
72
|
loaded = false;
|
|
33
73
|
await this.load();
|
|
34
74
|
},
|
|
35
75
|
/**
|
|
36
|
-
*
|
|
76
|
+
* Persist a connection. If an existing connection shares the same
|
|
77
|
+
* identity (see `connectionIdentityKey`), it's reused and its
|
|
78
|
+
* credentials are refreshed from the new config instead of spawning
|
|
79
|
+
* a duplicate record. Returns `{ id, existed }` so UI can distinguish
|
|
80
|
+
* "created" from "merged".
|
|
37
81
|
*/
|
|
38
82
|
async save(config) {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
id,
|
|
42
|
-
|
|
43
|
-
provider: config.provider,
|
|
44
|
-
endpoint: config.endpoint,
|
|
45
|
-
bucket: config.bucket,
|
|
46
|
-
region: config.region,
|
|
47
|
-
anonymous: config.anonymous,
|
|
48
|
-
authMethod: config.authMethod,
|
|
49
|
-
rootPrefix: config.rootPrefix
|
|
50
|
-
};
|
|
51
|
-
connections = [...connections, conn];
|
|
52
|
-
persistToStorage(STORAGE_KEYS.CONNECTIONS, connections);
|
|
53
|
-
// Store credentials in memory (never persisted to localStorage).
|
|
54
|
-
if (!config.anonymous) {
|
|
55
|
-
if (config.sas_token) {
|
|
56
|
-
const creds = { type: 'sas-token', sasToken: config.sas_token };
|
|
57
|
-
credentialStore.set(id, creds);
|
|
58
|
-
storeToNative(id, creds).catch(() => { });
|
|
59
|
-
}
|
|
60
|
-
else if (config.access_key && config.secret_key) {
|
|
61
|
-
const creds = {
|
|
62
|
-
type: 'sigv4',
|
|
63
|
-
accessKey: config.access_key,
|
|
64
|
-
secretKey: config.secret_key
|
|
65
|
-
};
|
|
66
|
-
credentialStore.set(id, creds);
|
|
67
|
-
storeToNative(id, creds).catch(() => { });
|
|
68
|
-
}
|
|
83
|
+
const existing = findByIdentity(config);
|
|
84
|
+
if (existing) {
|
|
85
|
+
applyCredentials(existing.id, config);
|
|
86
|
+
return { id: existing.id, existed: true };
|
|
69
87
|
}
|
|
70
|
-
|
|
88
|
+
const id = crypto.randomUUID();
|
|
89
|
+
connections = [...connections, toConnection(id, config)];
|
|
90
|
+
persist();
|
|
91
|
+
applyCredentials(id, config);
|
|
92
|
+
return { id, existed: false };
|
|
71
93
|
},
|
|
72
94
|
/**
|
|
73
|
-
* Update an existing connection.
|
|
95
|
+
* Update an existing connection. Throws `DuplicateConnectionError`
|
|
96
|
+
* when the new identity would collide with a different saved row,
|
|
97
|
+
* rather than silently overwriting and leaving a phantom duplicate.
|
|
74
98
|
*/
|
|
75
99
|
async update(id, config) {
|
|
76
100
|
const idx = connections.findIndex((c) => c.id === id);
|
|
77
101
|
if (idx === -1)
|
|
78
102
|
return false;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
bucket: config.bucket,
|
|
85
|
-
region: config.region,
|
|
86
|
-
anonymous: config.anonymous,
|
|
87
|
-
authMethod: config.authMethod,
|
|
88
|
-
rootPrefix: config.rootPrefix
|
|
89
|
-
};
|
|
103
|
+
const collision = findByIdentity(config, id);
|
|
104
|
+
if (collision) {
|
|
105
|
+
throw new DuplicateConnectionError(collision.id, collision.name);
|
|
106
|
+
}
|
|
107
|
+
connections[idx] = toConnection(id, config);
|
|
90
108
|
connections = [...connections];
|
|
91
|
-
|
|
109
|
+
persist();
|
|
92
110
|
// Invalidate cached adapter for this connection
|
|
93
111
|
import('../storage/index.js').then(({ clearAdapterCache }) => clearAdapterCache(id));
|
|
94
|
-
|
|
95
|
-
if (!config.anonymous) {
|
|
96
|
-
if (config.sas_token) {
|
|
97
|
-
const creds = { type: 'sas-token', sasToken: config.sas_token };
|
|
98
|
-
credentialStore.set(id, creds);
|
|
99
|
-
storeToNative(id, creds).catch(() => { });
|
|
100
|
-
}
|
|
101
|
-
else if (config.access_key && config.secret_key) {
|
|
102
|
-
const creds = {
|
|
103
|
-
type: 'sigv4',
|
|
104
|
-
accessKey: config.access_key,
|
|
105
|
-
secretKey: config.secret_key
|
|
106
|
-
};
|
|
107
|
-
credentialStore.set(id, creds);
|
|
108
|
-
storeToNative(id, creds).catch(() => { });
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
credentialStore.remove(id);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
credentialStore.remove(id);
|
|
116
|
-
}
|
|
112
|
+
applyCredentials(id, config);
|
|
117
113
|
return true;
|
|
118
114
|
},
|
|
119
|
-
/**
|
|
120
|
-
* Remove a connection by ID.
|
|
121
|
-
*/
|
|
122
115
|
async remove(id) {
|
|
123
116
|
const before = connections.length;
|
|
124
117
|
connections = connections.filter((c) => c.id !== id);
|
|
125
|
-
|
|
118
|
+
persist();
|
|
126
119
|
credentialStore.remove(id);
|
|
127
|
-
// Invalidate cached adapter for this connection
|
|
128
120
|
import('../storage/index.js').then(({ clearAdapterCache }) => clearAdapterCache(id));
|
|
129
121
|
return connections.length < before;
|
|
130
122
|
},
|
|
131
|
-
/**
|
|
132
|
-
* Test whether a connection is reachable via a lightweight list.
|
|
133
|
-
*/
|
|
134
123
|
async test(id) {
|
|
135
124
|
const { getAdapter } = await import('../storage/index.js');
|
|
136
125
|
const adapter = getAdapter('remote', id);
|
|
@@ -143,23 +132,11 @@ function createConnectionsStore() {
|
|
|
143
132
|
*/
|
|
144
133
|
async testWithConfig(config, existingId) {
|
|
145
134
|
const tempId = existingId ?? `temp-test-${Date.now()}`;
|
|
146
|
-
const tempConn =
|
|
147
|
-
id: tempId,
|
|
148
|
-
name: config.name,
|
|
149
|
-
provider: config.provider,
|
|
150
|
-
endpoint: config.endpoint,
|
|
151
|
-
bucket: config.bucket,
|
|
152
|
-
region: config.region,
|
|
153
|
-
anonymous: config.anonymous,
|
|
154
|
-
authMethod: config.authMethod,
|
|
155
|
-
rootPrefix: config.rootPrefix
|
|
156
|
-
};
|
|
157
|
-
// Temporarily register connection + credentials so the adapter can find them
|
|
135
|
+
const tempConn = toConnection(tempId, config);
|
|
158
136
|
const hadConn = connections.some((c) => c.id === tempId);
|
|
159
137
|
const prevCreds = credentialStore.get(tempId);
|
|
160
138
|
if (!hadConn) {
|
|
161
139
|
connections = [...connections, tempConn];
|
|
162
|
-
// Don't persist — this is a temp test connection
|
|
163
140
|
}
|
|
164
141
|
if (!config.anonymous) {
|
|
165
142
|
if (config.sas_token) {
|
|
@@ -180,56 +157,55 @@ function createConnectionsStore() {
|
|
|
180
157
|
return true;
|
|
181
158
|
}
|
|
182
159
|
finally {
|
|
183
|
-
// Cleanup: remove temp connection if we added it
|
|
184
160
|
if (!hadConn) {
|
|
185
161
|
connections = connections.filter((c) => c.id !== tempId);
|
|
186
|
-
// Don't persist — was never in localStorage
|
|
187
162
|
}
|
|
188
|
-
// Restore previous credentials or remove temp ones
|
|
189
163
|
if (prevCreds) {
|
|
190
164
|
credentialStore.set(tempId, prevCreds);
|
|
191
165
|
}
|
|
192
166
|
else if (!hadConn) {
|
|
193
167
|
credentialStore.remove(tempId);
|
|
194
168
|
}
|
|
195
|
-
// Also clear any cached adapter for the temp connection
|
|
196
169
|
import('../storage/index.js').then(({ clearAdapterCache }) => clearAdapterCache(tempId));
|
|
197
170
|
}
|
|
198
171
|
},
|
|
199
|
-
/** True when a dialog open has been requested and not yet consumed. */
|
|
200
172
|
get dialogRequested() {
|
|
201
173
|
return dialogRequest > 0;
|
|
202
174
|
},
|
|
203
|
-
/** Request opening the new-connection dialog from anywhere. */
|
|
204
175
|
requestDialog() {
|
|
205
176
|
dialogRequest++;
|
|
206
177
|
},
|
|
207
|
-
/** Mark the dialog request as consumed. */
|
|
208
178
|
clearDialogRequest() {
|
|
209
179
|
dialogRequest = 0;
|
|
210
180
|
},
|
|
211
|
-
/**
|
|
212
|
-
* Synchronous lookup by ID (from the already-loaded list).
|
|
213
|
-
*/
|
|
214
181
|
getById(id) {
|
|
215
182
|
return connections.find((c) => c.id === id);
|
|
216
183
|
},
|
|
217
184
|
/**
|
|
218
|
-
* Find an
|
|
185
|
+
* Find an already-saved connection that matches the canonical identity
|
|
186
|
+
* of `input` (provider + bucket + endpoint/region per provider rules).
|
|
187
|
+
* Used by auto-detect, manual-add dedup, and edit-collision checks.
|
|
219
188
|
*/
|
|
220
|
-
|
|
221
|
-
return
|
|
189
|
+
findByIdentity(input) {
|
|
190
|
+
return findByIdentity(input);
|
|
222
191
|
},
|
|
223
192
|
/**
|
|
224
|
-
*
|
|
225
|
-
*
|
|
193
|
+
* Auto-connect path for a URL-detected bucket. Reuses an existing
|
|
194
|
+
* connection when identity matches, otherwise creates one anonymously.
|
|
195
|
+
* Always returns the final connection ID.
|
|
226
196
|
*/
|
|
227
197
|
async saveHostConnection(detected) {
|
|
228
|
-
const
|
|
198
|
+
const identity = {
|
|
199
|
+
provider: detected.provider,
|
|
200
|
+
endpoint: detected.endpoint,
|
|
201
|
+
bucket: detected.bucket,
|
|
202
|
+
region: detected.region
|
|
203
|
+
};
|
|
204
|
+
const existing = findByIdentity(identity);
|
|
229
205
|
if (existing)
|
|
230
206
|
return existing.id;
|
|
231
207
|
const name = detected.bucket === '$web' ? `Azure Static Web` : detected.bucket;
|
|
232
|
-
const
|
|
208
|
+
const result = await this.save({
|
|
233
209
|
name,
|
|
234
210
|
provider: detected.provider === 'unknown' ? 's3' : detected.provider,
|
|
235
211
|
endpoint: detected.endpoint,
|
|
@@ -238,9 +214,24 @@ function createConnectionsStore() {
|
|
|
238
214
|
anonymous: true,
|
|
239
215
|
rootPrefix: detected.rootPrefix || undefined
|
|
240
216
|
});
|
|
241
|
-
return id;
|
|
217
|
+
return result.id;
|
|
242
218
|
}
|
|
243
219
|
};
|
|
244
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* Thrown by `update()` when the proposed identity collides with a different
|
|
223
|
+
* saved connection. Lets the UI tell the user which connection already owns
|
|
224
|
+
* that identity instead of silently producing a phantom duplicate.
|
|
225
|
+
*/
|
|
226
|
+
export class DuplicateConnectionError extends Error {
|
|
227
|
+
existingId;
|
|
228
|
+
existingName;
|
|
229
|
+
constructor(existingId, existingName) {
|
|
230
|
+
super(`A connection already exists for this bucket: "${existingName}"`);
|
|
231
|
+
this.name = 'DuplicateConnectionError';
|
|
232
|
+
this.existingId = existingId;
|
|
233
|
+
this.existingName = existingName;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
245
236
|
export const connectionStore = createConnectionsStore();
|
|
246
237
|
export { connectionStore as connections };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** SampleFormat tag value → human label. */
|
|
2
|
+
export declare const SF_LABELS: Record<number, string>;
|
|
3
|
+
export interface GeoBounds {
|
|
4
|
+
west: number;
|
|
5
|
+
south: number;
|
|
6
|
+
east: number;
|
|
7
|
+
north: number;
|
|
8
|
+
}
|
|
9
|
+
export interface CogInfo {
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
bandCount: number;
|
|
13
|
+
dataType: string;
|
|
14
|
+
bounds: GeoBounds;
|
|
15
|
+
downsampled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** Safely clamp a number to a range, treating NaN/Infinity as the fallback. */
|
|
18
|
+
export declare function safeClamp(v: number, lo: number, hi: number, fallback: number): number;
|
|
19
|
+
/** Clamp geographic bounds to valid MapLibre web-Mercator range. */
|
|
20
|
+
export declare function clampBounds(b: GeoBounds): GeoBounds;
|
|
21
|
+
/**
|
|
22
|
+
* Build a data-type label from GeoTIFF sample format and bits per sample.
|
|
23
|
+
* e.g. "uint8", "float32", "int16"
|
|
24
|
+
*/
|
|
25
|
+
export declare function buildDataTypeLabel(sampleFormat: number, bitsPerSample: number): string;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Dependency-free subset of `cog.ts` so that `@walkthru-earth/objex-utils`
|
|
2
|
+
// can re-export these helpers without dragging in `@developmentseed/epsg`,
|
|
3
|
+
// `@developmentseed/geotiff`, `@developmentseed/proj`, `proj4`, or
|
|
4
|
+
// `maplibre-gl`. tsup preserves bare side-effect imports from externalized
|
|
5
|
+
// modules even when all named bindings are tree-shaken, so the pure surface
|
|
6
|
+
// MUST live in a module that has zero heavy imports.
|
|
7
|
+
/** SampleFormat tag value → human label. */
|
|
8
|
+
export const SF_LABELS = {
|
|
9
|
+
1: 'uint',
|
|
10
|
+
2: 'int',
|
|
11
|
+
3: 'float',
|
|
12
|
+
4: 'void',
|
|
13
|
+
5: 'complex int',
|
|
14
|
+
6: 'complex float'
|
|
15
|
+
};
|
|
16
|
+
/** Safely clamp a number to a range, treating NaN/Infinity as the fallback. */
|
|
17
|
+
export function safeClamp(v, lo, hi, fallback) {
|
|
18
|
+
return Number.isFinite(v) ? Math.max(lo, Math.min(hi, v)) : fallback;
|
|
19
|
+
}
|
|
20
|
+
/** Clamp geographic bounds to valid MapLibre web-Mercator range. */
|
|
21
|
+
export function clampBounds(b) {
|
|
22
|
+
return {
|
|
23
|
+
west: safeClamp(b.west, -180, 180, -180),
|
|
24
|
+
south: safeClamp(b.south, -85.051129, 85.051129, -85.051129),
|
|
25
|
+
east: safeClamp(b.east, -180, 180, 180),
|
|
26
|
+
north: safeClamp(b.north, -85.051129, 85.051129, 85.051129)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Build a data-type label from GeoTIFF sample format and bits per sample.
|
|
31
|
+
* e.g. "uint8", "float32", "int16"
|
|
32
|
+
*/
|
|
33
|
+
export function buildDataTypeLabel(sampleFormat, bitsPerSample) {
|
|
34
|
+
return `${SF_LABELS[sampleFormat] ?? `sf${sampleFormat}`}${bitsPerSample ?? ''}`;
|
|
35
|
+
}
|
package/dist/utils/cog.d.ts
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import type { GetTileDataOptions, MinimalDataT } from '@developmentseed/deck.gl-geotiff';
|
|
2
|
-
import type { RenderTileResult } from '@developmentseed/deck.gl-raster';
|
|
2
|
+
import type { RasterModule, RenderTileResult } from '@developmentseed/deck.gl-raster';
|
|
3
3
|
import type { GeoTIFF as GeoTIFFType, Overview } from '@developmentseed/geotiff';
|
|
4
4
|
import { GeoTIFF } from '@developmentseed/geotiff';
|
|
5
5
|
import type { EpsgResolver } from '@developmentseed/proj';
|
|
6
|
+
import type { Device } from '@luma.gl/core';
|
|
6
7
|
import type maplibregl from 'maplibre-gl';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
import { buildDataTypeLabel, type CogInfo, clampBounds, type GeoBounds, SF_LABELS, safeClamp } from './cog-pure.js';
|
|
9
|
+
import { type ColormapName } from './colormap-sprite.js';
|
|
10
|
+
export { buildDataTypeLabel, type CogInfo, clampBounds, type GeoBounds, SF_LABELS, safeClamp };
|
|
11
|
+
/**
|
|
12
|
+
* Any of the 107 named ramps shipped in `@developmentseed/deck.gl-raster`'s
|
|
13
|
+
* `colormaps.png` sprite (matplotlib + rio-tiler + cmocean). Rendering is
|
|
14
|
+
* GPU-side via the `Colormap` shader module; switching ramps is a uniform
|
|
15
|
+
* update, no tile re-decode.
|
|
16
|
+
*/
|
|
17
|
+
export type ColorRampId = ColormapName;
|
|
15
18
|
export interface BandConfig {
|
|
16
19
|
mode: 'rgb' | 'single';
|
|
17
20
|
/** 0-indexed band indices for RGB channels */
|
|
@@ -78,6 +81,18 @@ export declare function createRescaledPipeline(geotiff: GeoTIFFType, rescale: Re
|
|
|
78
81
|
getTileData: (image: GeoTIFFType | Overview, options: GetTileDataOptions) => Promise<MinimalDataT>;
|
|
79
82
|
renderTile: (data: MinimalDataT) => RenderTileResult;
|
|
80
83
|
};
|
|
84
|
+
export interface BandRenderPipelineOptions {
|
|
85
|
+
/** Value treated as "no-data" and zeroed out by `FilterNoDataVal`. */
|
|
86
|
+
noDataVal?: number | null;
|
|
87
|
+
/** Linear rescale applied after no-data masking. Omit for no rescaling. */
|
|
88
|
+
rescale?: RescaleConfig;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Build a `renderPipeline` array for `MultiCOGLayer` / raster mosaics.
|
|
92
|
+
* Combines optional `FilterNoDataVal` + `LinearRescale` stages in the order
|
|
93
|
+
* the GPU expects (no-data mask first, then rescale).
|
|
94
|
+
*/
|
|
95
|
+
export declare function buildBandRenderPipeline(opts?: BandRenderPipelineOptions): RasterModule[];
|
|
81
96
|
/**
|
|
82
97
|
* Apply the two upstream-bug workarounds a GeoTIFF needs before being handed
|
|
83
98
|
* to `COGLayer`:
|
|
@@ -123,29 +138,6 @@ export interface SelectCogPipelineOptions {
|
|
|
123
138
|
* viewers can call it per sub-COG without re-implementing the decision tree.
|
|
124
139
|
*/
|
|
125
140
|
export declare function selectCogPipeline(geotiff: GeoTIFFType, opts?: SelectCogPipelineOptions): ResolvedCogPipeline;
|
|
126
|
-
export interface GeoBounds {
|
|
127
|
-
west: number;
|
|
128
|
-
south: number;
|
|
129
|
-
east: number;
|
|
130
|
-
north: number;
|
|
131
|
-
}
|
|
132
|
-
export interface CogInfo {
|
|
133
|
-
width: number;
|
|
134
|
-
height: number;
|
|
135
|
-
bandCount: number;
|
|
136
|
-
dataType: string;
|
|
137
|
-
bounds: GeoBounds;
|
|
138
|
-
downsampled?: boolean;
|
|
139
|
-
}
|
|
140
|
-
/** Safely clamp a number to a range, treating NaN/Infinity as the fallback. */
|
|
141
|
-
export declare function safeClamp(v: number, lo: number, hi: number, fallback: number): number;
|
|
142
|
-
/** Clamp geographic bounds to valid MapLibre web-Mercator range. */
|
|
143
|
-
export declare function clampBounds(b: GeoBounds): GeoBounds;
|
|
144
|
-
/**
|
|
145
|
-
* Build a data-type label from GeoTIFF sample format and bits per sample.
|
|
146
|
-
* e.g. "uint8", "float32", "int16"
|
|
147
|
-
*/
|
|
148
|
-
export declare function buildDataTypeLabel(sampleFormat: number, bitsPerSample: number): string;
|
|
149
141
|
/**
|
|
150
142
|
* Query the GPU's MAX_TEXTURE_SIZE from MapLibre's WebGL context.
|
|
151
143
|
* Falls back to 4096 (lowest common denominator for mobile GPUs).
|
|
@@ -178,42 +170,95 @@ export interface CustomTileData {
|
|
|
178
170
|
imageData: ImageData;
|
|
179
171
|
width: number;
|
|
180
172
|
height: number;
|
|
173
|
+
/**
|
|
174
|
+
* `sampler2DArray` colormap texture for single-band renders. Set by
|
|
175
|
+
* `createConfigurableGetTileData` / `createCustomGetTileData` when the
|
|
176
|
+
* first tile resolves the device-bound sprite texture; `undefined` for
|
|
177
|
+
* RGB-mode tiles (no colormap needed). Passed through to `renderTile`
|
|
178
|
+
* so the Colormap shader module can bind it on every layer.
|
|
179
|
+
*/
|
|
180
|
+
colormapTexture?: Texture;
|
|
181
|
+
/**
|
|
182
|
+
* Normalized `color.r` sentinel value for nodata pixels in single-band
|
|
183
|
+
* mode. The `Colormap` shader module overwrites all 4 output channels
|
|
184
|
+
* from the 1D ramp sample, destroying the α=0 flag, so we reserve
|
|
185
|
+
* `r = 0` for nodata and renormalize valid data into `(0, 1]`.
|
|
186
|
+
* `FilterNoDataVal` then discards matching fragments before the ramp
|
|
187
|
+
* lookup. `undefined` for RGB tiles.
|
|
188
|
+
*/
|
|
189
|
+
nodataSentinel?: number;
|
|
190
|
+
/**
|
|
191
|
+
* Per-tile 64-bin normalized histogram (0..1, nodata excluded) baked during
|
|
192
|
+
* single-band CPU decoding. `undefined` for RGB tiles. deck.gl's TileLayer
|
|
193
|
+
* caches the returned tile object, so this array is retained alongside the
|
|
194
|
+
* bitmap without a rebake on pan/zoom revisits. Summing the histograms of
|
|
195
|
+
* currently-visible tiles, via the TileLayer `onViewportLoad` hook, gives a
|
|
196
|
+
* cloud-native "histogram of what COG tiles the viewport currently shows at
|
|
197
|
+
* the active overview level", matching COG pyramid behavior.
|
|
198
|
+
*/
|
|
199
|
+
histogram?: Uint32Array;
|
|
181
200
|
}
|
|
201
|
+
type Texture = import('@luma.gl/core').Texture;
|
|
182
202
|
/**
|
|
183
203
|
* Check whether a GeoTIFF needs a custom render pipeline.
|
|
184
204
|
* v0.3's inferRenderPipeline only supports unsigned integers (SampleFormat 1).
|
|
185
205
|
* Signed int (2) and float (3) need custom getTileData/renderTile.
|
|
186
206
|
*/
|
|
187
207
|
export declare function needsCustomPipeline(geotiff: GeoTIFFType): boolean;
|
|
208
|
+
/**
|
|
209
|
+
* Shared options for the CPU tile-baking factories.
|
|
210
|
+
*
|
|
211
|
+
* The previous `onHistogram` callback accumulated a single closure-owned buffer
|
|
212
|
+
* across every tile ever baked, which grew unbounded on pan/zoom and never
|
|
213
|
+
* reflected "what the viewport currently shows". Histograms are now attached
|
|
214
|
+
* per tile to `CustomTileData.histogram` and aggregated by the viewer from
|
|
215
|
+
* TileLayer's `onViewportLoad(visibleTiles)` hook, matching COG overview-level
|
|
216
|
+
* behavior (few big tiles when zoomed out, small AOI-scoped tiles when zoomed
|
|
217
|
+
* in) and reusing deck.gl's tile cache for free.
|
|
218
|
+
*/
|
|
219
|
+
export type CustomGetTileDataOptions = Record<string, never>;
|
|
220
|
+
/** Number of histogram buckets produced by the CPU bake. */
|
|
221
|
+
export declare const HISTOGRAM_BIN_COUNT = 64;
|
|
188
222
|
/**
|
|
189
223
|
* Create custom getTileData for non-uint COGs.
|
|
190
224
|
* Reads band 0, normalizes using GDAL statistics / per-tile adaptive stretch,
|
|
191
|
-
*
|
|
225
|
+
* bakes a grayscale `r`-channel image so the GPU `Colormap` shader module
|
|
226
|
+
* (wired downstream by `selectCogPipeline`) can apply the ramp by sampling
|
|
227
|
+
* `colormaps.png`. Reserves `r = 0` for nodata so `FilterNoDataVal` can
|
|
228
|
+
* discard those fragments before the ramp sample.
|
|
192
229
|
*/
|
|
193
|
-
export declare function createCustomGetTileData(geotiff: GeoTIFFType): (image: GeoTIFFType | Overview, options: {
|
|
230
|
+
export declare function createCustomGetTileData(geotiff: GeoTIFFType, _opts?: CustomGetTileDataOptions): (image: GeoTIFFType | Overview, options: {
|
|
194
231
|
x: number;
|
|
195
232
|
y: number;
|
|
196
233
|
pool: unknown;
|
|
197
234
|
signal?: AbortSignal;
|
|
235
|
+
device: Device;
|
|
198
236
|
}) => Promise<CustomTileData>;
|
|
199
237
|
/**
|
|
200
|
-
* Custom renderTile for
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* the
|
|
238
|
+
* Custom renderTile for COGs that use the CPU pipeline. For RGB mode (and
|
|
239
|
+
* legacy multi-band non-uint), the `image` slot carries a fully-baked RGBA
|
|
240
|
+
* `ImageData` and there is nothing to append on the GPU. For single-band
|
|
241
|
+
* mode, the image carries a normalized `r`-channel and this function
|
|
242
|
+
* appends `FilterNoDataVal` (to discard r=0 nodata sentinels), optional
|
|
243
|
+
* `LinearRescale` (brightness/contrast slider), and the sprite-based
|
|
244
|
+
* `Colormap` module so switching ramps is a uniform update — no tile
|
|
245
|
+
* re-decode required. The `colormapTexture` is stashed on `data` by the
|
|
246
|
+
* corresponding `getTileData` factory; if the sprite failed to resolve we
|
|
247
|
+
* fall back to the plain grayscale image.
|
|
204
248
|
*/
|
|
205
|
-
export declare function
|
|
206
|
-
image: ImageData;
|
|
207
|
-
};
|
|
249
|
+
export declare function buildCustomRenderTile(config: BandConfig, rescale?: RescaleConfig): (data: CustomTileData) => RenderTileResult;
|
|
208
250
|
/**
|
|
209
251
|
* Create a configurable getTileData that respects BandConfig.
|
|
210
|
-
* Supports
|
|
252
|
+
* Supports RGB mode (multi-band → R,G,B with alpha=255, fully baked) and
|
|
253
|
+
* single-band mode (band N normalized into the `r` channel; the ramp is
|
|
254
|
+
* applied downstream by the GPU `Colormap` module via `buildCustomRenderTile`).
|
|
211
255
|
*/
|
|
212
|
-
export declare function createConfigurableGetTileData(geotiff: GeoTIFFType, config: BandConfig): (image: GeoTIFFType | Overview, options: {
|
|
256
|
+
export declare function createConfigurableGetTileData(geotiff: GeoTIFFType, config: BandConfig, _opts?: CustomGetTileDataOptions): (image: GeoTIFFType | Overview, options: {
|
|
213
257
|
x: number;
|
|
214
258
|
y: number;
|
|
215
259
|
pool: unknown;
|
|
216
260
|
signal?: AbortSignal;
|
|
261
|
+
device: Device;
|
|
217
262
|
}) => Promise<CustomTileData>;
|
|
218
263
|
export interface PixelValue {
|
|
219
264
|
lng: number;
|