@walkthru-earth/objex 1.2.0 → 1.3.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/README.md +6 -3
- package/dist/components/browser/FileTreeSidebar.svelte +1 -1
- package/dist/components/layout/ConnectionDialog.svelte +35 -3
- package/dist/components/layout/Sidebar.svelte +28 -2
- package/dist/components/viewers/ArchiveViewer.svelte +4 -4
- package/dist/components/viewers/CodeViewer.svelte +72 -19
- 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 +45 -10
- package/dist/components/viewers/CopcViewer.svelte +20 -2
- package/dist/components/viewers/FlatGeobufViewer.svelte +15 -9
- package/dist/components/viewers/MultiCogViewer.svelte +416 -0
- package/dist/components/viewers/MultiCogViewer.svelte.d.ts +9 -0
- package/dist/components/viewers/PmtilesViewer.svelte +2 -2
- package/dist/components/viewers/StacMapViewer.svelte +34 -12
- package/dist/components/viewers/StacMapViewer.svelte.d.ts +1 -0
- package/dist/components/viewers/StacMosaicViewer.svelte +699 -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/TableViewer.svelte +50 -21
- package/dist/components/viewers/ViewerRouter.svelte +155 -2
- package/dist/components/viewers/ViewerRouter.svelte.d.ts +1 -1
- package/dist/components/viewers/ZarrMapViewer.svelte +147 -8
- package/dist/components/viewers/ZarrMapViewer.svelte.d.ts +8 -2
- package/dist/components/viewers/ZarrViewer.svelte +3 -2
- package/dist/components/viewers/pmtiles/PmtilesMapView.svelte +0 -1
- package/dist/i18n/ar.js +28 -0
- package/dist/i18n/en.js +28 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +1 -1
- package/dist/query/source.d.ts +12 -0
- package/dist/query/source.js +25 -8
- package/dist/query/stac-geoparquet.d.ts +31 -0
- package/dist/query/stac-geoparquet.js +136 -0
- package/dist/query/wasm.js +130 -23
- package/dist/storage/adapter.d.ts +9 -0
- package/dist/storage/adapter.js +13 -1
- package/dist/storage/browser-azure.d.ts +1 -1
- package/dist/storage/browser-azure.js +4 -0
- package/dist/storage/browser-cloud.d.ts +1 -1
- package/dist/storage/browser-cloud.js +7 -0
- package/dist/storage/presign.d.ts +13 -0
- package/dist/storage/presign.js +55 -0
- package/dist/storage/providers.d.ts +6 -0
- package/dist/storage/providers.js +13 -2
- package/dist/stores/browser.svelte.d.ts +2 -0
- package/dist/stores/browser.svelte.js +17 -1
- package/dist/stores/connections.svelte.d.ts +38 -23
- package/dist/stores/connections.svelte.js +105 -114
- package/dist/utils/cog.d.ts +80 -18
- package/dist/utils/cog.js +187 -125
- 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/url.d.ts +13 -0
- package/dist/utils/url.js +36 -0
- package/dist/utils/wkb.js +22 -8
- package/dist/utils/zarr.d.ts +34 -0
- package/dist/utils/zarr.js +94 -0
- package/package.json +14 -13
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AuthRequiredError } from '../storage/adapter.js';
|
|
1
2
|
import { getAdapter } from '../storage/index.js';
|
|
2
3
|
import { credentialStore } from './credentials.svelte.js';
|
|
3
4
|
import { safeLock } from './safelock.svelte.js';
|
|
@@ -7,6 +8,7 @@ function createBrowserStore() {
|
|
|
7
8
|
let entries = $state([]);
|
|
8
9
|
let loading = $state(false);
|
|
9
10
|
let error = $state(null);
|
|
11
|
+
let authRequired = $state(null);
|
|
10
12
|
let uploading = $state(false);
|
|
11
13
|
let uploadProgress = $state({ current: 0, total: 0 });
|
|
12
14
|
async function browse(connection, prefix) {
|
|
@@ -22,12 +24,22 @@ function createBrowserStore() {
|
|
|
22
24
|
entries = result;
|
|
23
25
|
}
|
|
24
26
|
catch (e) {
|
|
25
|
-
|
|
27
|
+
if (e instanceof AuthRequiredError && connection.anonymous) {
|
|
28
|
+
// Auto-detected a private bucket. Surface it for the Sidebar to
|
|
29
|
+
// flip the connection to non-anonymous and prompt for credentials.
|
|
30
|
+
authRequired = connection;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
error = e instanceof Error ? e.message : String(e);
|
|
34
|
+
}
|
|
26
35
|
}
|
|
27
36
|
finally {
|
|
28
37
|
loading = false;
|
|
29
38
|
}
|
|
30
39
|
}
|
|
40
|
+
function clearAuthRequired() {
|
|
41
|
+
authRequired = null;
|
|
42
|
+
}
|
|
31
43
|
async function navigateTo(prefix) {
|
|
32
44
|
if (!activeConnection)
|
|
33
45
|
return;
|
|
@@ -145,6 +157,10 @@ function createBrowserStore() {
|
|
|
145
157
|
return false;
|
|
146
158
|
return credentialStore.has(activeConnection.id);
|
|
147
159
|
},
|
|
160
|
+
get authRequired() {
|
|
161
|
+
return authRequired;
|
|
162
|
+
},
|
|
163
|
+
clearAuthRequired,
|
|
148
164
|
browse,
|
|
149
165
|
navigateTo,
|
|
150
166
|
navigateUp,
|
|
@@ -1,55 +1,70 @@
|
|
|
1
1
|
import type { Connection, ConnectionConfig } from '../types.js';
|
|
2
|
+
import { type ConnectionIdentityInput } from '../utils/connection-identity.js';
|
|
2
3
|
import type { DetectedHost } from '../utils/host-detection.js';
|
|
4
|
+
/**
|
|
5
|
+
* Outcome of a write. `existed` is true when dedup reused an already-saved
|
|
6
|
+
* connection, false when a new row was persisted. Callers that present UI
|
|
7
|
+
* (dialogs, toasts) use this to decide whether to say "created" or "merged
|
|
8
|
+
* into existing".
|
|
9
|
+
*/
|
|
10
|
+
export interface ConnectionWriteResult {
|
|
11
|
+
id: string;
|
|
12
|
+
existed: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Thrown by `update()` when the proposed identity collides with a different
|
|
16
|
+
* saved connection. Lets the UI tell the user which connection already owns
|
|
17
|
+
* that identity instead of silently producing a phantom duplicate.
|
|
18
|
+
*/
|
|
19
|
+
export declare class DuplicateConnectionError extends Error {
|
|
20
|
+
readonly existingId: string;
|
|
21
|
+
readonly existingName: string;
|
|
22
|
+
constructor(existingId: string, existingName: string);
|
|
23
|
+
}
|
|
3
24
|
export declare const connectionStore: {
|
|
4
25
|
readonly items: Connection[];
|
|
5
26
|
readonly loaded: boolean;
|
|
6
27
|
/**
|
|
7
28
|
* Load connections from localStorage.
|
|
8
|
-
* Safe to call multiple times
|
|
29
|
+
* Safe to call multiple times, subsequent calls are no-ops.
|
|
9
30
|
*/
|
|
10
31
|
load(): Promise<void>;
|
|
11
|
-
/**
|
|
12
|
-
* Force-reload connections.
|
|
13
|
-
*/
|
|
14
32
|
reload(): Promise<void>;
|
|
15
33
|
/**
|
|
16
|
-
*
|
|
34
|
+
* Persist a connection. If an existing connection shares the same
|
|
35
|
+
* identity (see `connectionIdentityKey`), it's reused and its
|
|
36
|
+
* credentials are refreshed from the new config instead of spawning
|
|
37
|
+
* a duplicate record. Returns `{ id, existed }` so UI can distinguish
|
|
38
|
+
* "created" from "merged".
|
|
17
39
|
*/
|
|
18
|
-
save(config: ConnectionConfig): Promise<
|
|
40
|
+
save(config: ConnectionConfig): Promise<ConnectionWriteResult>;
|
|
19
41
|
/**
|
|
20
|
-
* Update an existing connection.
|
|
42
|
+
* Update an existing connection. Throws `DuplicateConnectionError`
|
|
43
|
+
* when the new identity would collide with a different saved row,
|
|
44
|
+
* rather than silently overwriting and leaving a phantom duplicate.
|
|
21
45
|
*/
|
|
22
46
|
update(id: string, config: ConnectionConfig): Promise<boolean>;
|
|
23
|
-
/**
|
|
24
|
-
* Remove a connection by ID.
|
|
25
|
-
*/
|
|
26
47
|
remove(id: string): Promise<boolean>;
|
|
27
|
-
/**
|
|
28
|
-
* Test whether a connection is reachable via a lightweight list.
|
|
29
|
-
*/
|
|
30
48
|
test(id: string): Promise<boolean>;
|
|
31
49
|
/**
|
|
32
50
|
* Test a connection using provided config values (works for both new
|
|
33
51
|
* and existing connections without saving first).
|
|
34
52
|
*/
|
|
35
53
|
testWithConfig(config: ConnectionConfig, existingId?: string): Promise<boolean>;
|
|
36
|
-
/** True when a dialog open has been requested and not yet consumed. */
|
|
37
54
|
readonly dialogRequested: boolean;
|
|
38
|
-
/** Request opening the new-connection dialog from anywhere. */
|
|
39
55
|
requestDialog(): void;
|
|
40
|
-
/** Mark the dialog request as consumed. */
|
|
41
56
|
clearDialogRequest(): void;
|
|
42
|
-
/**
|
|
43
|
-
* Synchronous lookup by ID (from the already-loaded list).
|
|
44
|
-
*/
|
|
45
57
|
getById(id: string): Connection | undefined;
|
|
46
58
|
/**
|
|
47
|
-
* Find an
|
|
59
|
+
* Find an already-saved connection that matches the canonical identity
|
|
60
|
+
* of `input` (provider + bucket + endpoint/region per provider rules).
|
|
61
|
+
* Used by auto-detect, manual-add dedup, and edit-collision checks.
|
|
48
62
|
*/
|
|
49
|
-
|
|
63
|
+
findByIdentity(input: ConnectionIdentityInput): Connection | undefined;
|
|
50
64
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
65
|
+
* Auto-connect path for a URL-detected bucket. Reuses an existing
|
|
66
|
+
* connection when identity matches, otherwise creates one anonymously.
|
|
67
|
+
* Always returns the final connection ID.
|
|
53
68
|
*/
|
|
54
69
|
saveHostConnection(detected: DetectedHost): Promise<string>;
|
|
55
70
|
};
|
|
@@ -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 };
|
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';
|
|
8
|
+
import { type ColormapName } from './colormap-sprite.js';
|
|
7
9
|
/** SampleFormat tag value → human label. */
|
|
8
10
|
export declare const SF_LABELS: Record<number, string>;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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`:
|
|
@@ -105,6 +120,11 @@ export interface SelectCogPipelineOptions {
|
|
|
105
120
|
bandConfig?: BandConfig | null;
|
|
106
121
|
/** Linear rescale GPU module values. No-op when omitted or at defaults. */
|
|
107
122
|
rescale?: RescaleConfig;
|
|
123
|
+
/**
|
|
124
|
+
* Forwarded to the CPU bake factories in single-band mode; receives a
|
|
125
|
+
* 64-bin histogram of normalized data after each tile for slider UI.
|
|
126
|
+
*/
|
|
127
|
+
onHistogram?: (bins: Uint32Array) => void;
|
|
108
128
|
}
|
|
109
129
|
/**
|
|
110
130
|
* Decide which getTileData/renderTile pair COGLayer should use for a GeoTIFF.
|
|
@@ -178,42 +198,83 @@ export interface CustomTileData {
|
|
|
178
198
|
imageData: ImageData;
|
|
179
199
|
width: number;
|
|
180
200
|
height: number;
|
|
201
|
+
/**
|
|
202
|
+
* `sampler2DArray` colormap texture for single-band renders. Set by
|
|
203
|
+
* `createConfigurableGetTileData` / `createCustomGetTileData` when the
|
|
204
|
+
* first tile resolves the device-bound sprite texture; `undefined` for
|
|
205
|
+
* RGB-mode tiles (no colormap needed). Passed through to `renderTile`
|
|
206
|
+
* so the Colormap shader module can bind it on every layer.
|
|
207
|
+
*/
|
|
208
|
+
colormapTexture?: Texture;
|
|
209
|
+
/**
|
|
210
|
+
* Normalized `color.r` sentinel value for nodata pixels in single-band
|
|
211
|
+
* mode. The `Colormap` shader module overwrites all 4 output channels
|
|
212
|
+
* from the 1D ramp sample, destroying the α=0 flag, so we reserve
|
|
213
|
+
* `r = 0` for nodata and renormalize valid data into `(0, 1]`.
|
|
214
|
+
* `FilterNoDataVal` then discards matching fragments before the ramp
|
|
215
|
+
* lookup. `undefined` for RGB tiles.
|
|
216
|
+
*/
|
|
217
|
+
nodataSentinel?: number;
|
|
181
218
|
}
|
|
219
|
+
type Texture = import('@luma.gl/core').Texture;
|
|
182
220
|
/**
|
|
183
221
|
* Check whether a GeoTIFF needs a custom render pipeline.
|
|
184
222
|
* v0.3's inferRenderPipeline only supports unsigned integers (SampleFormat 1).
|
|
185
223
|
* Signed int (2) and float (3) need custom getTileData/renderTile.
|
|
186
224
|
*/
|
|
187
225
|
export declare function needsCustomPipeline(geotiff: GeoTIFFType): boolean;
|
|
226
|
+
/** Shared options for the CPU tile-baking factories. */
|
|
227
|
+
export interface CustomGetTileDataOptions {
|
|
228
|
+
/**
|
|
229
|
+
* Called after each baked tile with a 64-bin histogram of normalized
|
|
230
|
+
* single-band values (0..1, nodata excluded). Bins accumulate across
|
|
231
|
+
* tiles; receivers should treat the array as monotonically growing and
|
|
232
|
+
* debounce UI updates. Never invoked in RGB mode.
|
|
233
|
+
*/
|
|
234
|
+
onHistogram?: (bins: Uint32Array) => void;
|
|
235
|
+
}
|
|
236
|
+
/** Number of histogram buckets produced by the CPU bake. */
|
|
237
|
+
export declare const HISTOGRAM_BIN_COUNT = 64;
|
|
188
238
|
/**
|
|
189
239
|
* Create custom getTileData for non-uint COGs.
|
|
190
240
|
* Reads band 0, normalizes using GDAL statistics / per-tile adaptive stretch,
|
|
191
|
-
*
|
|
241
|
+
* bakes a grayscale `r`-channel image so the GPU `Colormap` shader module
|
|
242
|
+
* (wired downstream by `selectCogPipeline`) can apply the ramp by sampling
|
|
243
|
+
* `colormaps.png`. Reserves `r = 0` for nodata so `FilterNoDataVal` can
|
|
244
|
+
* discard those fragments before the ramp sample.
|
|
192
245
|
*/
|
|
193
|
-
export declare function createCustomGetTileData(geotiff: GeoTIFFType): (image: GeoTIFFType | Overview, options: {
|
|
246
|
+
export declare function createCustomGetTileData(geotiff: GeoTIFFType, opts?: CustomGetTileDataOptions): (image: GeoTIFFType | Overview, options: {
|
|
194
247
|
x: number;
|
|
195
248
|
y: number;
|
|
196
249
|
pool: unknown;
|
|
197
250
|
signal?: AbortSignal;
|
|
251
|
+
device: Device;
|
|
198
252
|
}) => Promise<CustomTileData>;
|
|
199
253
|
/**
|
|
200
|
-
* Custom renderTile for
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* the
|
|
254
|
+
* Custom renderTile for COGs that use the CPU pipeline. For RGB mode (and
|
|
255
|
+
* legacy multi-band non-uint), the `image` slot carries a fully-baked RGBA
|
|
256
|
+
* `ImageData` and there is nothing to append on the GPU. For single-band
|
|
257
|
+
* mode, the image carries a normalized `r`-channel and this function
|
|
258
|
+
* appends `FilterNoDataVal` (to discard r=0 nodata sentinels), optional
|
|
259
|
+
* `LinearRescale` (brightness/contrast slider), and the sprite-based
|
|
260
|
+
* `Colormap` module so switching ramps is a uniform update — no tile
|
|
261
|
+
* re-decode required. The `colormapTexture` is stashed on `data` by the
|
|
262
|
+
* corresponding `getTileData` factory; if the sprite failed to resolve we
|
|
263
|
+
* fall back to the plain grayscale image.
|
|
204
264
|
*/
|
|
205
|
-
export declare function
|
|
206
|
-
image: ImageData;
|
|
207
|
-
};
|
|
265
|
+
export declare function buildCustomRenderTile(config: BandConfig, rescale?: RescaleConfig): (data: CustomTileData) => RenderTileResult;
|
|
208
266
|
/**
|
|
209
267
|
* Create a configurable getTileData that respects BandConfig.
|
|
210
|
-
* Supports
|
|
268
|
+
* Supports RGB mode (multi-band → R,G,B with alpha=255, fully baked) and
|
|
269
|
+
* single-band mode (band N normalized into the `r` channel; the ramp is
|
|
270
|
+
* applied downstream by the GPU `Colormap` module via `buildCustomRenderTile`).
|
|
211
271
|
*/
|
|
212
|
-
export declare function createConfigurableGetTileData(geotiff: GeoTIFFType, config: BandConfig): (image: GeoTIFFType | Overview, options: {
|
|
272
|
+
export declare function createConfigurableGetTileData(geotiff: GeoTIFFType, config: BandConfig, opts?: CustomGetTileDataOptions): (image: GeoTIFFType | Overview, options: {
|
|
213
273
|
x: number;
|
|
214
274
|
y: number;
|
|
215
275
|
pool: unknown;
|
|
216
276
|
signal?: AbortSignal;
|
|
277
|
+
device: Device;
|
|
217
278
|
}) => Promise<CustomTileData>;
|
|
218
279
|
export interface PixelValue {
|
|
219
280
|
lng: number;
|
|
@@ -242,3 +303,4 @@ export declare function resolveProj4Def(crs: number | unknown, _signal: AbortSig
|
|
|
242
303
|
* Converts WGS84 → source CRS → pixel coords, fetches the tile, reads all bands.
|
|
243
304
|
*/
|
|
244
305
|
export declare function readPixelAtLngLat(geotiff: GeoTIFFType, lng: number, lat: number, proj4Def: string | null, pool: any, signal?: AbortSignal): Promise<PixelValue | null>;
|
|
306
|
+
export {};
|