@walkthru-earth/objex-utils 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 +8 -0
- package/dist/index.cjs +280 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +178 -2
- package/dist/index.d.ts +178 -2
- package/dist/index.js +270 -31
- package/dist/index.js.map +1 -1
- package/docs/README.md +1 -0
- package/docs/cog.md +14 -225
- package/docs/parquet-metadata.md +9 -1
- package/docs/stac-geoparquet.md +198 -0
- package/docs/storage.md +2 -0
- package/package.json +9 -4
package/docs/README.md
CHANGED
|
@@ -34,6 +34,7 @@ As of v1.2, `yaml` is loaded via dynamic `import()` inside `parseMarkdownDocumen
|
|
|
34
34
|
| [`geometry.md`](./geometry.md) | WKB parser, GeoArrow table builder, geometry-column detection |
|
|
35
35
|
| [`cog.md`](./cog.md) | Cloud-Optimized GeoTIFF helpers (pipeline selection, band configs, color ramps, bounds clamping) |
|
|
36
36
|
| [`parquet-metadata.md`](./parquet-metadata.md) | `readParquetMetadata` + CRS / bounds / geometry-types extractors |
|
|
37
|
+
| [`stac-geoparquet.md`](./stac-geoparquet.md) | stac-geoparquet detection (`isStacGeoparquetSchema`) and row-to-Item transforms (`stacRowToItem`, `flattenStacBbox`, `pickStacPrimaryAsset`, `resolveStacAssetHref`) |
|
|
37
38
|
| [`storage.md`](./storage.md) | URL parsing (`parseStorageUrl`, `resolveCloudUrl`), provider registry, `StorageAdapter` interface, `UrlAdapter` |
|
|
38
39
|
| [`query-engine.md`](./query-engine.md) | `QueryEngine` interface and associated result/handle types |
|
|
39
40
|
| [`file-types.md`](./file-types.md) | File-type registry (`getFileTypeInfo`, `getViewerKind`, `getDuckDbReadFn`, ...) |
|
package/docs/cog.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Cloud-Optimized GeoTIFF (COG) helpers
|
|
2
2
|
|
|
3
|
-
Pure helpers
|
|
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.
|
|
3
|
+
Pure, framework-agnostic helpers for working with Cloud-Optimized GeoTIFF metadata and bounds. No Svelte, MapLibre, deck.gl, or GeoTIFF library dependency.
|
|
6
4
|
|
|
7
5
|
Source: `src/lib/utils/cog.ts`.
|
|
8
6
|
|
|
7
|
+
> The render-pipeline helpers (`selectCogPipeline`, `createConfigurableGetTileData`, `normalizeCogGeotiff`, `createEpsgResolver`, `fitCogBounds`, `renderNonTiledBitmap`, etc.) live in the same source file but are **not** re-exported from `@walkthru-earth/objex-utils` because they pull in `@developmentseed/deck.gl-geotiff`, `@developmentseed/geotiff`, `@developmentseed/proj`, `proj4`, and `maplibre-gl`. If you need them, depend on the full Svelte package [`@walkthru-earth/objex`](https://www.npmjs.com/package/@walkthru-earth/objex) (they are re-exported from `src/lib/index.ts`) and install those optional peers yourself.
|
|
8
|
+
|
|
9
9
|
## Types
|
|
10
10
|
|
|
11
11
|
### `GeoBounds`
|
|
@@ -32,33 +32,7 @@ interface CogInfo {
|
|
|
32
32
|
}
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
|
|
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)
|
|
35
|
+
## Constants
|
|
62
36
|
|
|
63
37
|
### `SF_LABELS`
|
|
64
38
|
|
|
@@ -71,6 +45,8 @@ const SF_LABELS: Record<number, string> = {
|
|
|
71
45
|
|
|
72
46
|
Human labels for the GeoTIFF SampleFormat tag.
|
|
73
47
|
|
|
48
|
+
## Functions
|
|
49
|
+
|
|
74
50
|
### `safeClamp(v, lo, hi, fallback)`
|
|
75
51
|
|
|
76
52
|
```ts
|
|
@@ -98,206 +74,19 @@ function buildDataTypeLabel(
|
|
|
98
74
|
|
|
99
75
|
Combine the GeoTIFF `SampleFormat` tag with `BitsPerSample` into `'uint8'`, `'int16'`, `'float32'`, etc.
|
|
100
76
|
|
|
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
77
|
## Usage outline
|
|
284
78
|
|
|
285
79
|
```ts
|
|
286
80
|
import {
|
|
287
|
-
|
|
288
|
-
selectCogPipeline,
|
|
289
|
-
normalizeCogGeotiff,
|
|
290
|
-
createEpsgResolver,
|
|
291
|
-
fitCogBounds,
|
|
81
|
+
buildDataTypeLabel,
|
|
292
82
|
clampBounds,
|
|
83
|
+
safeClamp,
|
|
84
|
+
SF_LABELS,
|
|
85
|
+
type CogInfo,
|
|
86
|
+
type GeoBounds,
|
|
293
87
|
} from '@walkthru-earth/objex-utils';
|
|
294
88
|
|
|
295
|
-
|
|
296
|
-
const
|
|
297
|
-
const
|
|
298
|
-
const resolver = createEpsgResolver();
|
|
299
|
-
|
|
300
|
-
// feed into COGLayer({ ...pipeline, epsgResolver: resolver })
|
|
301
|
-
|
|
302
|
-
fitCogBounds(map, clampBounds(bounds));
|
|
89
|
+
const label = buildDataTypeLabel(sampleFormat, bitsPerSample); // 'float32'
|
|
90
|
+
const safeBounds = clampBounds(bounds);
|
|
91
|
+
const nice = safeClamp(value, 0, 1, 0);
|
|
303
92
|
```
|
package/docs/parquet-metadata.md
CHANGED
|
@@ -36,7 +36,14 @@ interface GeoParquetMeta {
|
|
|
36
36
|
```ts
|
|
37
37
|
interface ParquetFileMetadata {
|
|
38
38
|
rowCount: number;
|
|
39
|
+
/** Leaf columns only — struct parents are flattened into their child paths. */
|
|
39
40
|
schema: { name: string; type: string }[];
|
|
41
|
+
/**
|
|
42
|
+
* Top-level column names as written, including struct/group parents
|
|
43
|
+
* (e.g. `assets`, `bbox`) that `schema` flattens away. Required for
|
|
44
|
+
* stac-geoparquet detection, which keys on the `assets` struct parent.
|
|
45
|
+
*/
|
|
46
|
+
topLevelColumns: string[];
|
|
40
47
|
geo: GeoParquetMeta | null; // null for non-geo Parquet
|
|
41
48
|
legacyGeoParquet: boolean; // true for pre-1.0 (schema_version without "version" field)
|
|
42
49
|
createdBy: string | null;
|
|
@@ -121,7 +128,8 @@ const meta = await readParquetMetadata(
|
|
|
121
128
|
console.log({
|
|
122
129
|
rows: meta.rowCount,
|
|
123
130
|
compression: meta.compression,
|
|
124
|
-
schema: meta.schema,
|
|
131
|
+
schema: meta.schema, // leaf columns only
|
|
132
|
+
topLevel: meta.topLevelColumns, // includes struct parents like `assets`, `bbox`
|
|
125
133
|
});
|
|
126
134
|
|
|
127
135
|
if (meta.geo) {
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# stac-geoparquet
|
|
2
|
+
|
|
3
|
+
Pure transforms and detection for the [stac-geoparquet](https://github.com/stac-utils/stac-geoparquet) format. Zero Svelte / DuckDB / deck.gl dependencies, framework-agnostic. Decoupled from any single WKB library via a caller-supplied `wkbParser`, so consumers can plug in `parseWKB` from this package, `geoarrow-wasm`, `wkx`, or anything else.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import {
|
|
7
|
+
STAC_GEOPARQUET_REQUIRED_COLUMNS,
|
|
8
|
+
isStacGeoparquetSchema,
|
|
9
|
+
flattenStacBbox,
|
|
10
|
+
resolveStacAssetHref,
|
|
11
|
+
pickStacPrimaryAsset,
|
|
12
|
+
stacRowToItem,
|
|
13
|
+
parseWKB,
|
|
14
|
+
} from '@walkthru-earth/objex-utils';
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Types
|
|
18
|
+
|
|
19
|
+
### `StacGeoparquetSchemaColumn`
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
interface StacGeoparquetSchemaColumn {
|
|
23
|
+
name: string;
|
|
24
|
+
type?: string;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Minimal shape for a column descriptor. Works with hyparquet's leaf array, DuckDB's `DESCRIBE` rows, Arrow `Field`s, or any other source. `type` is optional because detection only keys on `name`.
|
|
29
|
+
|
|
30
|
+
### `StacBboxStruct`
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
interface StacBboxStruct {
|
|
34
|
+
xmin: number;
|
|
35
|
+
ymin: number;
|
|
36
|
+
xmax: number;
|
|
37
|
+
ymax: number;
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Bbox in struct form, as DuckDB returns it for the recommended `bbox struct(xmin double, ymin double, xmax double, ymax double)` column.
|
|
42
|
+
|
|
43
|
+
### `StacGeoparquetRow`
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
type StacGeoparquetRow = Record<string, unknown>;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Generic row shape after DuckDB / Arrow / hyparquet decoding. Pass directly into `stacRowToItem`.
|
|
50
|
+
|
|
51
|
+
### `StacRowToItemOptions`
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
interface StacRowToItemOptions {
|
|
55
|
+
/** WKB decoder, e.g. `parseWKB` from this package. */
|
|
56
|
+
wkbParser?: (bytes: Uint8Array) => unknown;
|
|
57
|
+
/** Column holding the WKB bytes. Default `"geom_wkb"`. */
|
|
58
|
+
wkbColumn?: string;
|
|
59
|
+
/** Column holding pre-decoded GeoJSON geometry. Default `"geometry"`. */
|
|
60
|
+
geometryColumn?: string;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The default `wkbColumn` of `"geom_wkb"` matches the recommended SQL projection `ST_AsWKB(geometry) AS geom_wkb`, which avoids DuckDB's GEOMETRY type hitting Arrow's WASM serializer.
|
|
65
|
+
|
|
66
|
+
## Constants
|
|
67
|
+
|
|
68
|
+
### `STAC_GEOPARQUET_REQUIRED_COLUMNS`
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
const STAC_GEOPARQUET_REQUIRED_COLUMNS = [
|
|
72
|
+
'stac_version',
|
|
73
|
+
'type',
|
|
74
|
+
'geometry',
|
|
75
|
+
'assets',
|
|
76
|
+
] as const;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Columns every stac-geoparquet file MUST carry per the spec. `isStacGeoparquetSchema` checks that all four are present.
|
|
80
|
+
|
|
81
|
+
## Functions
|
|
82
|
+
|
|
83
|
+
### `isStacGeoparquetSchema(schema)`
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
function isStacGeoparquetSchema(
|
|
87
|
+
schema: StacGeoparquetSchemaColumn[]
|
|
88
|
+
): boolean
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Returns `true` when every required STAC column is present in `schema`. Type-agnostic on purpose: some pipelines know the column type (DuckDB `DESCRIBE`, Arrow `Field`), others only have the name list (hyparquet schema walk). The set of names is sufficient for routing.
|
|
92
|
+
|
|
93
|
+
**Important** when used with `readParquetMetadata`: pass `meta.topLevelColumns.map((name) => ({ name }))`, not `meta.schema`. `meta.schema` flattens struct parents away and would hide the `assets` column.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { readParquetMetadata, isStacGeoparquetSchema } from '@walkthru-earth/objex-utils';
|
|
97
|
+
|
|
98
|
+
const meta = await readParquetMetadata(url);
|
|
99
|
+
const isStac = isStacGeoparquetSchema(
|
|
100
|
+
meta.topLevelColumns.map((name) => ({ name }))
|
|
101
|
+
);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### `flattenStacBbox(bbox)`
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
function flattenStacBbox(
|
|
108
|
+
bbox: StacBboxStruct | number[] | null | undefined
|
|
109
|
+
): [number, number, number, number] | null
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Normalize a DuckDB `struct(xmin,ymin,xmax,ymax)` bbox to the `[minX, minY, maxX, maxY]` array shape that STAC Items and deck.gl-geotiff `MosaicLayer` expect. Pass-through for inputs that are already arrays. Returns `null` when any component is non-finite or the input is missing.
|
|
113
|
+
|
|
114
|
+
### `resolveStacAssetHref(href, baseUrl)`
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
function resolveStacAssetHref(href: string, baseUrl: string): string
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Resolve a possibly-relative STAC asset href against a base URL. `./foo.tif` and `foo.tif` become absolute against `baseUrl`. URLs that already carry a scheme (`http(s)://`, `s3://`, `gs://`, …) are returned unchanged.
|
|
121
|
+
|
|
122
|
+
### `pickStacPrimaryAsset(assets, preferredKeys?)`
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
function pickStacPrimaryAsset(
|
|
126
|
+
assets: Record<string, StacAsset> | null | undefined,
|
|
127
|
+
preferredKeys?: readonly string[]
|
|
128
|
+
): { key: string; asset: StacAsset } | null
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Pick the "primary" asset from a STAC Item's `assets` map. Priority order:
|
|
132
|
+
|
|
133
|
+
1. The first key listed in `preferredKeys` that exists.
|
|
134
|
+
2. The asset under the conventional `data` key.
|
|
135
|
+
3. The first asset whose `roles` array contains `'data'`.
|
|
136
|
+
4. The first asset.
|
|
137
|
+
|
|
138
|
+
Returns `null` when the map is empty or the input is not an object.
|
|
139
|
+
|
|
140
|
+
### `stacRowToItem(row, baseUrl, opts?)`
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
function stacRowToItem(
|
|
144
|
+
row: StacGeoparquetRow,
|
|
145
|
+
baseUrl: string,
|
|
146
|
+
opts?: StacRowToItemOptions
|
|
147
|
+
): StacItem
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Convert one stac-geoparquet row into a standard STAC Item JSON object. Handles:
|
|
151
|
+
|
|
152
|
+
- `assets` named-struct flattening + relative href resolution against `baseUrl`
|
|
153
|
+
- `bbox` struct → `[minX, minY, maxX, maxY]` array via `flattenStacBbox`
|
|
154
|
+
- Optional WKB geometry → GeoJSON via `opts.wkbParser`
|
|
155
|
+
- `datetime` → ISO string (passes through already-string values)
|
|
156
|
+
- Promotes `properties.*` columns (`proj:*`, `raster:*`, `eo:*`, `bands`, `datetime`) onto `item.properties`
|
|
157
|
+
|
|
158
|
+
Asset hrefs in stac-geoparquet are typically written relative to each item's original `self` URL, **not** the parquet URL. The stactools default layout places each item JSON at `{catalog_dir}/{item.id}/{item.id}.json`, so callers should compute a per-row base of `{parquet_dir}/{item.id}/` and pass that as `baseUrl`. Resolving against the bare parquet URL strips the item-id subfolder and every COG 404s.
|
|
159
|
+
|
|
160
|
+
## End-to-end example
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import {
|
|
164
|
+
isStacGeoparquetSchema,
|
|
165
|
+
parseWKB,
|
|
166
|
+
readParquetMetadata,
|
|
167
|
+
stacRowToItem,
|
|
168
|
+
} from '@walkthru-earth/objex-utils';
|
|
169
|
+
|
|
170
|
+
const parquetUrl = 'https://example.com/catalog.parquet';
|
|
171
|
+
|
|
172
|
+
// 1. Detect — use topLevelColumns, not schema, so the `assets` struct parent is visible.
|
|
173
|
+
const meta = await readParquetMetadata(parquetUrl);
|
|
174
|
+
const isStac = isStacGeoparquetSchema(
|
|
175
|
+
meta.topLevelColumns.map((name) => ({ name }))
|
|
176
|
+
);
|
|
177
|
+
if (!isStac) throw new Error('Not stac-geoparquet');
|
|
178
|
+
|
|
179
|
+
// 2. Materialize via your DuckDB / Arrow / hyparquet pipeline. Recommended SQL:
|
|
180
|
+
// SELECT id, type, stac_version, assets, bbox, links, datetime,
|
|
181
|
+
// ST_AsWKB(geometry) AS geom_wkb
|
|
182
|
+
// FROM 'catalog.parquet'
|
|
183
|
+
const rows: Record<string, unknown>[] = await runYourQuery();
|
|
184
|
+
|
|
185
|
+
// 3. Build STAC Items. Per-row base = {parquet_dir}/{item.id}/.
|
|
186
|
+
const parquetDir = parquetUrl.replace(/[^/]*(?:\?.*)?$/, '');
|
|
187
|
+
const items = rows.map((row) => {
|
|
188
|
+
const id = String(row.id ?? '');
|
|
189
|
+
const itemBase = id ? `${parquetDir}${id}/` : parquetUrl;
|
|
190
|
+
return stacRowToItem(row, itemBase, { wkbParser: parseWKB });
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const featureCollection = { type: 'FeatureCollection', features: items };
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Peer dependencies
|
|
197
|
+
|
|
198
|
+
None. The functions are pure and runtime-agnostic. The `wkbParser` option lets callers plug in any WKB decoder, including `parseWKB` from this same package.
|
package/docs/storage.md
CHANGED
|
@@ -154,6 +154,7 @@ const PROVIDER_IDS: ProviderId[];
|
|
|
154
154
|
```ts
|
|
155
155
|
function getProvider(id: string): ProviderDef;
|
|
156
156
|
function buildEndpointFromTemplate(id: ProviderId, region: string): string;
|
|
157
|
+
function resolveProviderEndpoint(provider: string, region?: string): string;
|
|
157
158
|
function buildProviderBaseUrl(
|
|
158
159
|
provider: ProviderId,
|
|
159
160
|
endpoint: string,
|
|
@@ -167,6 +168,7 @@ function isGcsProvider(provider: string, endpoint: string): boolean;
|
|
|
167
168
|
|----------|-----------|
|
|
168
169
|
| `getProvider` | Unknown IDs fall back to the S3 entry (never throws). |
|
|
169
170
|
| `buildEndpointFromTemplate` | Substitute `{region}` in the provider's template. |
|
|
171
|
+
| `resolveProviderEndpoint` | Same as `buildEndpointFromTemplate` but accepts an untyped `provider` string and falls back to the provider's `defaultRegion` when `region` is omitted. Returns `''` for providers without a template (plain S3, MinIO). |
|
|
170
172
|
| `buildProviderBaseUrl` | Produce the HTTPS base URL for API requests (endpoint + bucket, correctly interleaved for virtual-host vs path-style). |
|
|
171
173
|
| `isGcsProvider` | `true` when the connection uses the GCS JSON API rather than S3 XML — used to pick adapter implementation. |
|
|
172
174
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walkthru-earth/objex-utils",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Pure TypeScript utilities from objex — WKB parser, GeoArrow builder, storage URL parser, file type registry",
|
|
5
5
|
"author": "Youssef Harby <yharby@walkthru.earth>",
|
|
6
6
|
"license": "CC-BY-4.0",
|
|
@@ -15,9 +15,14 @@
|
|
|
15
15
|
"types": "./dist/index.d.ts",
|
|
16
16
|
"exports": {
|
|
17
17
|
".": {
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
"import": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"require": {
|
|
23
|
+
"types": "./dist/index.d.cts",
|
|
24
|
+
"default": "./dist/index.cjs"
|
|
25
|
+
}
|
|
21
26
|
}
|
|
22
27
|
},
|
|
23
28
|
"files": [
|