@walkthru-earth/objex-utils 1.0.0 → 1.2.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 +99 -0
- package/dist/index.cjs +1224 -956
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +230 -7
- package/dist/index.d.ts +230 -7
- package/dist/index.js +1202 -957
- package/dist/index.js.map +1 -1
- package/docs/README.md +102 -0
- package/docs/cog.md +303 -0
- package/docs/errors.md +34 -0
- package/docs/file-sort.md +67 -0
- package/docs/file-types.md +141 -0
- package/docs/formatting.md +192 -0
- package/docs/geometry.md +198 -0
- package/docs/local-storage.md +51 -0
- package/docs/markdown-sql.md +109 -0
- package/docs/parquet-metadata.md +133 -0
- package/docs/query-engine.md +140 -0
- package/docs/storage.md +251 -0
- package/docs/types-constants.md +173 -0
- package/package.json +9 -3
package/docs/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# `@walkthru-earth/objex-utils` Developer Docs
|
|
2
|
+
|
|
3
|
+
Professional reference documentation for every public export of the package.
|
|
4
|
+
|
|
5
|
+
Each page lists the exact TypeScript signature, parameter semantics, return shape, peer-dependency requirements, and non-obvious behavior. Intended as the single source of truth for integrators.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @walkthru-earth/objex-utils
|
|
11
|
+
# or
|
|
12
|
+
npm install @walkthru-earth/objex-utils
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Minimum: ES2022, modern browser or Node 18+. Ships both ESM (`dist/index.js`) and CJS (`dist/index.cjs`) with full `.d.ts`.
|
|
16
|
+
|
|
17
|
+
## Optional peer dependencies
|
|
18
|
+
|
|
19
|
+
All heavy dependencies are **optional** peer dependencies. Install only the ones you touch.
|
|
20
|
+
|
|
21
|
+
| Peer | Version | Required by |
|
|
22
|
+
|------|---------|-------------|
|
|
23
|
+
| `apache-arrow` | `>=14` | `buildGeoArrowTables`, `normalizeGeomType` (indirectly) |
|
|
24
|
+
| `hyparquet` | `>=1.25` | `readParquetMetadata` and its extractors |
|
|
25
|
+
| `hyparquet-compressors` | `>=1.1` | `readParquetMetadata` (for SNAPPY/ZSTD/GZIP/etc.) |
|
|
26
|
+
| `yaml` | `>=2` | `parseMarkdownDocument` (lazy-loaded, only when YAML frontmatter is present) |
|
|
27
|
+
|
|
28
|
+
As of v1.2, `yaml` is loaded via dynamic `import()` inside `parseMarkdownDocument`. Consumers who do not call that function never touch `yaml` at load time.
|
|
29
|
+
|
|
30
|
+
## Reference pages
|
|
31
|
+
|
|
32
|
+
| Page | Covers |
|
|
33
|
+
|------|--------|
|
|
34
|
+
| [`geometry.md`](./geometry.md) | WKB parser, GeoArrow table builder, geometry-column detection |
|
|
35
|
+
| [`cog.md`](./cog.md) | Cloud-Optimized GeoTIFF helpers (pipeline selection, band configs, color ramps, bounds clamping) |
|
|
36
|
+
| [`parquet-metadata.md`](./parquet-metadata.md) | `readParquetMetadata` + CRS / bounds / geometry-types extractors |
|
|
37
|
+
| [`storage.md`](./storage.md) | URL parsing (`parseStorageUrl`, `resolveCloudUrl`), provider registry, `StorageAdapter` interface, `UrlAdapter` |
|
|
38
|
+
| [`query-engine.md`](./query-engine.md) | `QueryEngine` interface and associated result/handle types |
|
|
39
|
+
| [`file-types.md`](./file-types.md) | File-type registry (`getFileTypeInfo`, `getViewerKind`, `getDuckDbReadFn`, ...) |
|
|
40
|
+
| [`formatting.md`](./formatting.md) | Display formatters, column-type classification, hex dump, CSV/JSON export |
|
|
41
|
+
| [`file-sort.md`](./file-sort.md) | `sortFileEntries`, `toggleSortField` |
|
|
42
|
+
| [`markdown-sql.md`](./markdown-sql.md) | Markdown + SQL block parsing (Evidence-compatible syntax) |
|
|
43
|
+
| [`local-storage.md`](./local-storage.md) | SSR-safe `loadFromStorage` / `persistToStorage` |
|
|
44
|
+
| [`errors.md`](./errors.md) | `handleLoadError` |
|
|
45
|
+
| [`types-constants.md`](./types-constants.md) | `Connection`, `Tab`, `FileEntry`, `WriteResult`, `Theme`, shared constants |
|
|
46
|
+
|
|
47
|
+
## Quick recipes
|
|
48
|
+
|
|
49
|
+
### Render a GeoParquet file on deck.gl
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import {
|
|
53
|
+
readParquetMetadata,
|
|
54
|
+
extractEpsgFromGeoMeta,
|
|
55
|
+
extractGeometryTypes,
|
|
56
|
+
buildGeoArrowTables
|
|
57
|
+
} from '@walkthru-earth/objex-utils';
|
|
58
|
+
|
|
59
|
+
// 1. Read footer metadata (~512KB range requests, no DuckDB)
|
|
60
|
+
const meta = await readParquetMetadata(url);
|
|
61
|
+
const sourceCrs = extractEpsgFromGeoMeta(meta.geo!); // e.g. 'EPSG:27700' or null
|
|
62
|
+
|
|
63
|
+
// 2. Get WKB buffers + attributes from your SQL engine (DuckDB-WASM, etc.)
|
|
64
|
+
const { wkbArrays, attributes } = await myEngine.queryForMap(...);
|
|
65
|
+
|
|
66
|
+
// 3. Build GeoArrow tables (one per geometry type)
|
|
67
|
+
const tables = buildGeoArrowTables(wkbArrays, attributes);
|
|
68
|
+
|
|
69
|
+
// 4. Feed into deck.gl GeoArrowLayer(s)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Parse a cloud storage URL a user pasted
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { parseStorageUrl, looksLikeUrl, describeParseResult } from '@walkthru-earth/objex-utils';
|
|
76
|
+
|
|
77
|
+
const parsed = parseStorageUrl(input);
|
|
78
|
+
// { provider: 's3', bucket: 'my-bucket', region: 'us-east-1', endpoint: '...', prefix: '...' }
|
|
79
|
+
|
|
80
|
+
console.log(describeParseResult(parsed));
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Read a binary blob as a hex dump
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { generateHexDump } from '@walkthru-earth/objex-utils';
|
|
87
|
+
|
|
88
|
+
const rows = generateHexDump(bytes);
|
|
89
|
+
// rows: [{ offset: '00000000', hex: ['48','65','6c',...], ascii: 'Hello...' }, ...]
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Upstream source
|
|
93
|
+
|
|
94
|
+
All modules re-export from `src/lib/` in the [objex](https://github.com/walkthru-earth/objex) monorepo. The re-export list lives at [`packages/objex-utils/src/index.ts`](../src/index.ts).
|
|
95
|
+
|
|
96
|
+
## Versioning & releases
|
|
97
|
+
|
|
98
|
+
Both `@walkthru-earth/objex` and `@walkthru-earth/objex-utils` ship together via Changesets with a `fixed` config, so their versions stay in lockstep. See [`RELEASE.md`](../../../RELEASE.md) in the monorepo root.
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
|
package/docs/cog.md
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# Cloud-Optimized GeoTIFF (COG) helpers
|
|
2
|
+
|
|
3
|
+
Pure helpers plus lazy wrappers for rendering Cloud-Optimized GeoTIFFs with [`@developmentseed/deck.gl-geotiff`](https://github.com/developmentseed/deck.gl-geotiff) v0.5 and MapLibre GL.
|
|
4
|
+
|
|
5
|
+
The "pure" subset (types, safe-math, data-type labels, color ramps) has no native dependencies. The rendering-oriented functions pull in `@developmentseed/geotiff`, `@developmentseed/deck.gl-geotiff`, `maplibre-gl`, `proj4`, and `@developmentseed/proj` — treat all of those as **optional** peers.
|
|
6
|
+
|
|
7
|
+
Source: `src/lib/utils/cog.ts`.
|
|
8
|
+
|
|
9
|
+
## Types
|
|
10
|
+
|
|
11
|
+
### `GeoBounds`
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
interface GeoBounds {
|
|
15
|
+
west: number;
|
|
16
|
+
south: number;
|
|
17
|
+
east: number;
|
|
18
|
+
north: number;
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### `CogInfo`
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
interface CogInfo {
|
|
26
|
+
width: number;
|
|
27
|
+
height: number;
|
|
28
|
+
bandCount: number;
|
|
29
|
+
dataType: string; // e.g. 'uint8', 'float32', 'int16'
|
|
30
|
+
bounds: GeoBounds;
|
|
31
|
+
downsampled?: boolean; // true when renderNonTiledBitmap decimated the array
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### `BandConfig`
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
interface BandConfig {
|
|
39
|
+
mode: 'rgb' | 'single';
|
|
40
|
+
rBand: number; // 0-indexed
|
|
41
|
+
gBand: number;
|
|
42
|
+
bBand: number;
|
|
43
|
+
band: number; // single-band mode selection
|
|
44
|
+
colorRamp: ColorRampId;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### `RescaleConfig`
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
interface RescaleConfig {
|
|
52
|
+
min: number;
|
|
53
|
+
max: number;
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### `CogTagInfo` / `ResolvedCogPipeline`
|
|
58
|
+
|
|
59
|
+
Internal-but-exported descriptors returned by `inspectCogTags` / `selectCogPipeline`. See source for the full field list; the common shape is "metadata about which rendering pipeline should be used and why."
|
|
60
|
+
|
|
61
|
+
## Pure helpers (no peer deps)
|
|
62
|
+
|
|
63
|
+
### `SF_LABELS`
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const SF_LABELS: Record<number, string> = {
|
|
67
|
+
1: 'uint', 2: 'int', 3: 'float', 4: 'void',
|
|
68
|
+
5: 'complex int', 6: 'complex float',
|
|
69
|
+
};
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Human labels for the GeoTIFF SampleFormat tag.
|
|
73
|
+
|
|
74
|
+
### `safeClamp(v, lo, hi, fallback)`
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
function safeClamp(v: number, lo: number, hi: number, fallback: number): number
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Clamp `v` into `[lo, hi]`. If `v` is `NaN` or `±Infinity`, return `fallback`. Use instead of `Math.min(hi, Math.max(lo, v))` — NaN would otherwise propagate silently.
|
|
81
|
+
|
|
82
|
+
### `clampBounds(b)`
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
function clampBounds(b: GeoBounds): GeoBounds
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Clamp geographic bounds to valid MapLibre Web Mercator range: ±180° longitude, ±85.051129° latitude.
|
|
89
|
+
|
|
90
|
+
### `buildDataTypeLabel(sampleFormat, bitsPerSample)`
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
function buildDataTypeLabel(
|
|
94
|
+
sampleFormat: number,
|
|
95
|
+
bitsPerSample: number
|
|
96
|
+
): string
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Combine the GeoTIFF `SampleFormat` tag with `BitsPerSample` into `'uint8'`, `'int16'`, `'float32'`, etc.
|
|
100
|
+
|
|
101
|
+
### `defaultBandConfig(bandCount, sampleFormat)`
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
function defaultBandConfig(bandCount: number, sampleFormat: number): BandConfig
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Sensible initial band config: RGB mode for ≥3 bands, single-band with a terrain ramp for int/float single-band imagery.
|
|
108
|
+
|
|
109
|
+
### `isDefaultBandConfig(config, bandCount, sampleFormat)`
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
function isDefaultBandConfig(
|
|
113
|
+
config: BandConfig,
|
|
114
|
+
bandCount: number,
|
|
115
|
+
sampleFormat: number
|
|
116
|
+
): boolean
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`true` when `config` equals the default for this COG — used to short-circuit custom-pipeline selection.
|
|
120
|
+
|
|
121
|
+
### Color ramps
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
type ColorRampId = 'grayscale' | 'terrain' | 'viridis' | 'magma' | 'turbo' | 'spectral';
|
|
125
|
+
|
|
126
|
+
const COLOR_RAMP_STOPS: Record<ColorRampId, [number, number, number][]>;
|
|
127
|
+
|
|
128
|
+
function interpolateRamp(
|
|
129
|
+
stops: [number, number, number][],
|
|
130
|
+
t: number
|
|
131
|
+
): [number, number, number];
|
|
132
|
+
|
|
133
|
+
function rampToGradientCss(id: ColorRampId): string;
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`interpolateRamp(stops, t)` returns an RGB triple for normalized `t ∈ [0, 1]` (clamped). `rampToGradientCss` produces a CSS `linear-gradient(...)` string for UI previews.
|
|
137
|
+
|
|
138
|
+
## Pipeline helpers (require peer deps)
|
|
139
|
+
|
|
140
|
+
### `inspectCogTags(geotiff)`
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
function inspectCogTags(geotiff: GeoTIFF): CogTagInfo
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Centralized reader for the TIFF tags that determine which render pipeline to use (Photometric.Palette, SampleFormat, ColorMap, etc.).
|
|
147
|
+
|
|
148
|
+
### `needsCustomPipeline(geotiff)`
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
function needsCustomPipeline(geotiff: GeoTIFF): boolean
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`true` when the library's `inferRenderPipeline` can't handle this COG (non-uint sample formats). Triggers the custom JS pipeline.
|
|
155
|
+
|
|
156
|
+
### `needsCustomPipelineForConfig(geotiff, config)`
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
function needsCustomPipelineForConfig(
|
|
160
|
+
geotiff: GeoTIFF,
|
|
161
|
+
config: BandConfig
|
|
162
|
+
): boolean
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`true` when `config` deviates from what the library default pipeline supports (non-standard RGB band order, single-band mode, non-uint, non-palette).
|
|
166
|
+
|
|
167
|
+
### `createCustomGetTileData(geotiff)` / `customRenderTile(data)`
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
function createCustomGetTileData(
|
|
171
|
+
geotiff: GeoTIFF
|
|
172
|
+
): (image, options) => Promise<CustomTileData>;
|
|
173
|
+
|
|
174
|
+
function customRenderTile(data: CustomTileData): { image: ImageData };
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Band-0-only fallback path for non-uint COGs. Uses GDAL statistics when available, otherwise per-tile adaptive stretch, and applies the `terrain` color ramp.
|
|
178
|
+
|
|
179
|
+
### `createConfigurableGetTileData(geotiff, config)`
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
function createConfigurableGetTileData(
|
|
183
|
+
geotiff: GeoTIFF,
|
|
184
|
+
config: BandConfig
|
|
185
|
+
): (image, options) => Promise<CustomTileData>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Band-selectable variant. Honors `BandConfig.mode` (`'rgb'` vs `'single'`) and `colorRamp`.
|
|
189
|
+
|
|
190
|
+
### `isRescaleActive(cfg)` / `createRescaledPipeline(geotiff, rescale)`
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
function isRescaleActive(cfg: RescaleConfig): boolean;
|
|
194
|
+
|
|
195
|
+
function createRescaledPipeline(
|
|
196
|
+
geotiff: GeoTIFF,
|
|
197
|
+
rescale: RescaleConfig
|
|
198
|
+
): { getTileData: Function; renderTile: Function };
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
GPU-side LinearRescale module appended to the library default uint pipeline. Only meaningful for uint COGs whose shape / colormap falls inside the library default — see `selectCogPipeline` for the dispatch rules.
|
|
202
|
+
|
|
203
|
+
### `selectCogPipeline(geotiff, opts?)`
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
function selectCogPipeline(
|
|
207
|
+
geotiff: GeoTIFF,
|
|
208
|
+
opts?: SelectCogPipelineOptions
|
|
209
|
+
): ResolvedCogPipeline
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Single entry point that returns the correct `{ getTileData, renderTile }` pair for a given COG + config. Four outcomes in priority order:
|
|
213
|
+
|
|
214
|
+
1. Custom configurable — band swap or color ramp change is active and the config is non-default.
|
|
215
|
+
2. Custom non-uint — Int/Float COG without an explicit config yet.
|
|
216
|
+
3. Library default + LinearRescale — uint COG with `rescale` active.
|
|
217
|
+
4. Library default — everything else (empty object is returned; `COGLayer` uses its built-ins).
|
|
218
|
+
|
|
219
|
+
### `normalizeCogGeotiff(geotiff)`
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
function normalizeCogGeotiff(geotiff: GeoTIFF): void
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Apply in-place upstream workarounds:
|
|
226
|
+
|
|
227
|
+
- Drop overview IFDs smaller than one tile (cause proj4 NaN on polar projections).
|
|
228
|
+
- Clamp EPSG:4326 bbox to ±85.051129° latitude (Web Mercator safe range).
|
|
229
|
+
|
|
230
|
+
Idempotent — safe to call repeatedly.
|
|
231
|
+
|
|
232
|
+
### `createEpsgResolver()`
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
function createEpsgResolver(): (code: number) => Promise<ProjectionDefinition>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Async factory returning an `epsgResolver` compatible with `@developmentseed/deck.gl-geotiff`'s `COGLayer` prop of the same name. Looks up numeric EPSG codes in the bundled `@developmentseed/epsg` gzipped CSV and parses each WKT with `parseWkt()` from `@developmentseed/proj`. Throws a clear `Error` if the code is not present.
|
|
239
|
+
|
|
240
|
+
Replaces runtime `epsg.io` fetches — first COG per session pulls the CSV (~200 KB gzipped) once.
|
|
241
|
+
|
|
242
|
+
### Pixel / CRS helpers
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
function readPixelAtLngLat(
|
|
246
|
+
geotiff: GeoTIFF,
|
|
247
|
+
lng: number,
|
|
248
|
+
lat: number,
|
|
249
|
+
proj4Def: string | null,
|
|
250
|
+
pool: any,
|
|
251
|
+
signal?: AbortSignal
|
|
252
|
+
): Promise<PixelValue | null>;
|
|
253
|
+
|
|
254
|
+
function resolveProj4Def(
|
|
255
|
+
crs: number | unknown,
|
|
256
|
+
signal: AbortSignal
|
|
257
|
+
): Promise<string | null>;
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
`readPixelAtLngLat` converts `(lng, lat)` into source CRS, then pixel coords, and reads every band. Returns `null` when outside the raster.
|
|
261
|
+
|
|
262
|
+
`resolveProj4Def` returns a proj4-compatible WKT/ProjJSON string for a CRS identifier, or `null` for WGS84 / unknown.
|
|
263
|
+
|
|
264
|
+
### MapLibre helpers
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
function getMaxTextureSize(map: maplibregl.Map): number; // fallback 4096
|
|
268
|
+
|
|
269
|
+
function fitCogBounds(map: maplibregl.Map, b: GeoBounds): void;
|
|
270
|
+
|
|
271
|
+
function cleanupNativeBitmap(map: maplibregl.Map): void; // idempotent
|
|
272
|
+
|
|
273
|
+
function renderNonTiledBitmap(options: {
|
|
274
|
+
url: string;
|
|
275
|
+
map: maplibregl.Map;
|
|
276
|
+
signal?: AbortSignal;
|
|
277
|
+
geotiff?: GeoTIFF;
|
|
278
|
+
}): Promise<CogInfo>;
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
`renderNonTiledBitmap` is the fallback path for non-tiled GeoTIFFs: opens the file, reads band 0, normalizes to grayscale RGBA (terrain ramp for single-band int/float), adds a MapLibre native `image` source. Throws when the raster exceeds 100 M pixels or projection bounds cannot be computed.
|
|
282
|
+
|
|
283
|
+
## Usage outline
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
import {
|
|
287
|
+
defaultBandConfig,
|
|
288
|
+
selectCogPipeline,
|
|
289
|
+
normalizeCogGeotiff,
|
|
290
|
+
createEpsgResolver,
|
|
291
|
+
fitCogBounds,
|
|
292
|
+
clampBounds,
|
|
293
|
+
} from '@walkthru-earth/objex-utils';
|
|
294
|
+
|
|
295
|
+
normalizeCogGeotiff(geotiff);
|
|
296
|
+
const config = defaultBandConfig(geotiff.bandCount, sampleFormat);
|
|
297
|
+
const pipeline = selectCogPipeline(geotiff, { bandConfig: config });
|
|
298
|
+
const resolver = createEpsgResolver();
|
|
299
|
+
|
|
300
|
+
// feed into COGLayer({ ...pipeline, epsgResolver: resolver })
|
|
301
|
+
|
|
302
|
+
fitCogBounds(map, clampBounds(bounds));
|
|
303
|
+
```
|
package/docs/errors.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Error handling
|
|
2
|
+
|
|
3
|
+
One function. Normalizes thrown values into a displayable string, with special-casing for `AbortError`.
|
|
4
|
+
|
|
5
|
+
Source: `src/lib/utils/error.ts`.
|
|
6
|
+
|
|
7
|
+
## `handleLoadError(err)`
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
function handleLoadError(err: unknown): string | null
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Extract a display message from an unknown caught value.
|
|
14
|
+
|
|
15
|
+
| Input | Output | Meaning |
|
|
16
|
+
|-------|--------|---------|
|
|
17
|
+
| `DOMException` with `name === 'AbortError'` | `null` | Caller should silently return — the user cancelled. |
|
|
18
|
+
| `Error` with `name === 'AbortError'` (fetch cancel) | `null` | Same as above. |
|
|
19
|
+
| Other `Error` | `err.message` | Standard error. |
|
|
20
|
+
| Everything else | `String(err)` | Best-effort coercion. |
|
|
21
|
+
|
|
22
|
+
## Usage pattern
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { handleLoadError } from '@walkthru-earth/objex-utils';
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
data = await adapter.read(path, 0, undefined, signal);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
const msg = handleLoadError(err);
|
|
31
|
+
if (msg === null) return; // aborted — do nothing
|
|
32
|
+
errorState = msg;
|
|
33
|
+
}
|
|
34
|
+
```
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# File sorting
|
|
2
|
+
|
|
3
|
+
Sort a `FileEntry[]` with directories pinned on top. Pure — no mutation of the input.
|
|
4
|
+
|
|
5
|
+
Source: `src/lib/utils/file-sort.ts`.
|
|
6
|
+
|
|
7
|
+
## Types
|
|
8
|
+
|
|
9
|
+
### `SortField`
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
type SortField = 'name' | 'size' | 'modified' | 'extension';
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### `SortDirection`
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
type SortDirection = 'asc' | 'desc';
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### `SortConfig`
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
interface SortConfig {
|
|
25
|
+
field: SortField;
|
|
26
|
+
direction: SortDirection;
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Functions
|
|
31
|
+
|
|
32
|
+
### `sortFileEntries(entries, config)`
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
function sortFileEntries(entries: FileEntry[], config: SortConfig): FileEntry[]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Return a **new** array sorted according to `config`.
|
|
39
|
+
|
|
40
|
+
- Directories always sort before files regardless of direction.
|
|
41
|
+
- `name` uses locale-aware comparison.
|
|
42
|
+
- `size` and `modified` use numeric comparison.
|
|
43
|
+
- `extension` falls back to `name` when equal.
|
|
44
|
+
- Stable for equal keys.
|
|
45
|
+
|
|
46
|
+
### `toggleSortField(current, field)`
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
function toggleSortField(current: SortConfig, field: SortField): SortConfig
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
UI helper for clickable column headers:
|
|
53
|
+
|
|
54
|
+
- If `field === current.field`: flip direction.
|
|
55
|
+
- Otherwise: switch to `{ field, direction: 'asc' }`.
|
|
56
|
+
|
|
57
|
+
## Example
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { sortFileEntries, toggleSortField } from '@walkthru-earth/objex-utils';
|
|
61
|
+
|
|
62
|
+
let config = { field: 'modified', direction: 'desc' };
|
|
63
|
+
const sorted = sortFileEntries(entries, config);
|
|
64
|
+
|
|
65
|
+
// onClick a column header:
|
|
66
|
+
config = toggleSortField(config, 'size');
|
|
67
|
+
```
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# File-type registry
|
|
2
|
+
|
|
3
|
+
Central mapping between file extensions and everything objex needs to render or query them — icon, color, viewer, MIME type, DuckDB read function.
|
|
4
|
+
|
|
5
|
+
Source: `src/lib/file-icons/index.ts`.
|
|
6
|
+
|
|
7
|
+
## Types
|
|
8
|
+
|
|
9
|
+
### `FileCategory`
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
type FileCategory =
|
|
13
|
+
| 'data' | 'geo' | 'code' | 'document' | 'config'
|
|
14
|
+
| 'image' | 'video' | 'audio' | 'archive'
|
|
15
|
+
| 'database' | '3d' | 'other';
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### `ViewerKind`
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
type ViewerKind =
|
|
22
|
+
| 'table' | 'image' | 'video' | 'audio' | 'markdown' | 'code'
|
|
23
|
+
| 'cog' | 'pmtiles' | 'flatgeobuf' | 'pdf' | '3d' | 'archive'
|
|
24
|
+
| 'database' | 'zarr' | 'copc' | 'notebook' | 'raw';
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Use this to drive viewer routing in your own UI.
|
|
28
|
+
|
|
29
|
+
### `DuckDbReadFn`
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
type DuckDbReadFn = 'read_parquet' | 'read_csv' | 'read_json' | 'ST_Read';
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Which DuckDB table function should be used to ingest the file. `ST_Read` covers GDAL-backed vector formats (Shapefile, GeoPackage, FlatGeobuf, KML, GML).
|
|
36
|
+
|
|
37
|
+
### `FileTypeInfo`
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
interface FileTypeInfo {
|
|
41
|
+
icon: string; // Lucide icon name
|
|
42
|
+
color: string; // Tailwind color classes (light + dark)
|
|
43
|
+
label: string; // human-readable type label
|
|
44
|
+
category: FileCategory;
|
|
45
|
+
viewer: ViewerKind;
|
|
46
|
+
queryable: boolean;
|
|
47
|
+
duckdbReadFn: DuckDbReadFn | null;
|
|
48
|
+
mimeType: string;
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Functions
|
|
53
|
+
|
|
54
|
+
### `getFileTypeInfo(extension, isDir?)`
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
function getFileTypeInfo(extension: string, isDir?: boolean): FileTypeInfo
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Return a fully populated `FileTypeInfo` for an extension.
|
|
61
|
+
|
|
62
|
+
- `extension` is matched with or without a leading dot, case-insensitive.
|
|
63
|
+
- `isDir === true` short-circuits to a directory entry (folder icon, `category: 'other'`, `viewer: 'raw'`).
|
|
64
|
+
- Unknown extensions return the `raw` entry.
|
|
65
|
+
|
|
66
|
+
### `getViewerKind(extension)`
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
function getViewerKind(extension: string): ViewerKind
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Shorthand that returns `info.viewer` only. `extension` may be a full filename or an extension.
|
|
73
|
+
|
|
74
|
+
### `getMimeType(extension)`
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
function getMimeType(extension: string): string
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Return the MIME type (`application/octet-stream` fallback). Useful for `Content-Type` on uploads and `<a download>` links.
|
|
81
|
+
|
|
82
|
+
### `isQueryable(extension)`
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
function isQueryable(extension: string): boolean
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`true` when the format can be queried with DuckDB (Parquet, CSV, TSV, JSONL, NDJSON, Shapefile, GeoPackage, FlatGeobuf, KML, GML, etc.).
|
|
89
|
+
|
|
90
|
+
### `getDuckDbReadFn(pathOrExt)`
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
function getDuckDbReadFn(pathOrExt: string): string
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Return the DuckDB table-function name (`'read_parquet'`, `'read_csv'`, `'read_json'`, `'ST_Read'`). Falls back to `'read_parquet'` when unknown.
|
|
97
|
+
|
|
98
|
+
Accepts either a file path or a bare extension.
|
|
99
|
+
|
|
100
|
+
### `isCloudNativeFormat(pathOrExt)`
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
function isCloudNativeFormat(pathOrExt: string): boolean
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`true` for formats DuckDB can query directly over HTTP range requests without buffering the whole file (`.parquet`, `.geoparquet`, `.gpq`, `.gparquet`, `.ducklake`).
|
|
107
|
+
|
|
108
|
+
### `buildDuckDbSource(pathOrExt, url)`
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
function buildDuckDbSource(pathOrExt: string, url: string): string
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Return a ready-to-paste FROM-clause expression. Key behavior:
|
|
115
|
+
|
|
116
|
+
| Ext | Output |
|
|
117
|
+
|-----|--------|
|
|
118
|
+
| `.parquet` / `.geoparquet` | `read_parquet('<url>')` |
|
|
119
|
+
| `.csv` / `.tsv` | `read_csv('<url>')` |
|
|
120
|
+
| `.jsonl` / `.ndjson` | `read_json('<url>')` |
|
|
121
|
+
| `.json` | Unnested expression that flattens GeoJSON `features[*]` into rows with property columns + a `geometry` column |
|
|
122
|
+
| `.shp`, `.gpkg`, `.fgb`, `.kml`, `.gml` | `ST_Read('<url>')` |
|
|
123
|
+
| Unknown | `read_parquet('<url>')` fallback |
|
|
124
|
+
|
|
125
|
+
## Example
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import {
|
|
129
|
+
getFileTypeInfo,
|
|
130
|
+
isQueryable,
|
|
131
|
+
buildDuckDbSource,
|
|
132
|
+
} from '@walkthru-earth/objex-utils';
|
|
133
|
+
|
|
134
|
+
const info = getFileTypeInfo('streets.fgb');
|
|
135
|
+
// { viewer: 'flatgeobuf', category: 'geo', queryable: true, duckdbReadFn: 'ST_Read', ... }
|
|
136
|
+
|
|
137
|
+
if (isQueryable('streets.fgb')) {
|
|
138
|
+
const sql = `SELECT COUNT(*) FROM ${buildDuckDbSource('streets.fgb', url)}`;
|
|
139
|
+
// SELECT COUNT(*) FROM ST_Read('https://.../streets.fgb')
|
|
140
|
+
}
|
|
141
|
+
```
|