@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.
Files changed (76) hide show
  1. package/README.md +6 -3
  2. package/dist/components/browser/FileTreeSidebar.svelte +1 -1
  3. package/dist/components/layout/ConnectionDialog.svelte +35 -3
  4. package/dist/components/layout/Sidebar.svelte +28 -2
  5. package/dist/components/viewers/ArchiveViewer.svelte +4 -4
  6. package/dist/components/viewers/CodeViewer.svelte +72 -19
  7. package/dist/components/viewers/CodeViewer.svelte.d.ts +11 -1
  8. package/dist/components/viewers/CogControls.svelte +151 -22
  9. package/dist/components/viewers/CogControls.svelte.d.ts +5 -1
  10. package/dist/components/viewers/CogViewer.svelte +45 -10
  11. package/dist/components/viewers/CopcViewer.svelte +20 -2
  12. package/dist/components/viewers/FlatGeobufViewer.svelte +15 -9
  13. package/dist/components/viewers/MultiCogViewer.svelte +416 -0
  14. package/dist/components/viewers/MultiCogViewer.svelte.d.ts +9 -0
  15. package/dist/components/viewers/PmtilesViewer.svelte +2 -2
  16. package/dist/components/viewers/StacMapViewer.svelte +34 -12
  17. package/dist/components/viewers/StacMapViewer.svelte.d.ts +1 -0
  18. package/dist/components/viewers/StacMosaicViewer.svelte +699 -0
  19. package/dist/components/viewers/StacMosaicViewer.svelte.d.ts +9 -0
  20. package/dist/components/viewers/StacTabViewer.svelte +254 -0
  21. package/dist/components/viewers/StacTabViewer.svelte.d.ts +13 -0
  22. package/dist/components/viewers/TableViewer.svelte +50 -21
  23. package/dist/components/viewers/ViewerRouter.svelte +155 -2
  24. package/dist/components/viewers/ViewerRouter.svelte.d.ts +1 -1
  25. package/dist/components/viewers/ZarrMapViewer.svelte +147 -8
  26. package/dist/components/viewers/ZarrMapViewer.svelte.d.ts +8 -2
  27. package/dist/components/viewers/ZarrViewer.svelte +3 -2
  28. package/dist/components/viewers/pmtiles/PmtilesMapView.svelte +0 -1
  29. package/dist/i18n/ar.js +28 -0
  30. package/dist/i18n/en.js +28 -0
  31. package/dist/index.d.ts +4 -0
  32. package/dist/index.js +2 -0
  33. package/dist/query/index.d.ts +1 -1
  34. package/dist/query/index.js +1 -1
  35. package/dist/query/source.d.ts +12 -0
  36. package/dist/query/source.js +25 -8
  37. package/dist/query/stac-geoparquet.d.ts +31 -0
  38. package/dist/query/stac-geoparquet.js +136 -0
  39. package/dist/query/wasm.js +130 -23
  40. package/dist/storage/adapter.d.ts +9 -0
  41. package/dist/storage/adapter.js +13 -1
  42. package/dist/storage/browser-azure.d.ts +1 -1
  43. package/dist/storage/browser-azure.js +4 -0
  44. package/dist/storage/browser-cloud.d.ts +1 -1
  45. package/dist/storage/browser-cloud.js +7 -0
  46. package/dist/storage/presign.d.ts +13 -0
  47. package/dist/storage/presign.js +55 -0
  48. package/dist/storage/providers.d.ts +6 -0
  49. package/dist/storage/providers.js +13 -2
  50. package/dist/stores/browser.svelte.d.ts +2 -0
  51. package/dist/stores/browser.svelte.js +17 -1
  52. package/dist/stores/connections.svelte.d.ts +38 -23
  53. package/dist/stores/connections.svelte.js +105 -114
  54. package/dist/utils/cog.d.ts +80 -18
  55. package/dist/utils/cog.js +187 -125
  56. package/dist/utils/colormap-sprite.d.ts +39 -0
  57. package/dist/utils/colormap-sprite.js +77 -0
  58. package/dist/utils/connection-identity.d.ts +51 -0
  59. package/dist/utils/connection-identity.js +97 -0
  60. package/dist/utils/host-detection.js +48 -302
  61. package/dist/utils/parquet-metadata.d.ts +7 -1
  62. package/dist/utils/parquet-metadata.js +35 -1
  63. package/dist/utils/stac-geoparquet.d.ts +90 -0
  64. package/dist/utils/stac-geoparquet.js +223 -0
  65. package/dist/utils/stac-hydrate.d.ts +38 -0
  66. package/dist/utils/stac-hydrate.js +243 -0
  67. package/dist/utils/stac.d.ts +136 -0
  68. package/dist/utils/stac.js +176 -0
  69. package/dist/utils/storage-url.d.ts +26 -0
  70. package/dist/utils/storage-url.js +164 -28
  71. package/dist/utils/url.d.ts +13 -0
  72. package/dist/utils/url.js +36 -0
  73. package/dist/utils/wkb.js +22 -8
  74. package/dist/utils/zarr.d.ts +34 -0
  75. package/dist/utils/zarr.js +94 -0
  76. package/package.json +14 -13
package/dist/utils/wkb.js CHANGED
@@ -190,8 +190,22 @@ const GEO_TYPE_KEYWORDS = [
190
190
  'geometrycollection',
191
191
  'sdo_geometry'
192
192
  ];
193
- /** Substrings in column names that hint at geometry content. */
194
- const GEO_NAME_HINTS = ['geom', 'geometry', 'geo_', '_geo', 'wkb', 'wkt', 'shape', 'spatial'];
193
+ /**
194
+ * Tokens in column names that hint at geometry content. Matched against
195
+ * snake_case / kebab-case / camelCase tokens only — never as loose substrings
196
+ * (e.g. `_geographic_` must not match `geo` via the `_geo` substring, because
197
+ * count columns like `n_geographic_entities` are INT, not geometry).
198
+ */
199
+ const GEO_NAME_HINTS = ['geom', 'geometry', 'wkb', 'wkt', 'shape', 'spatial', 'geo'];
200
+ /** Split a column name into lowercase tokens for hint matching. */
201
+ function tokenizeColumnName(name) {
202
+ return name
203
+ .replace(/([a-z])([A-Z])/g, '$1_$2')
204
+ .replace(/([a-z])([0-9])/g, '$1_$2')
205
+ .toLowerCase()
206
+ .split(/[^a-z0-9]+/)
207
+ .filter(Boolean);
208
+ }
195
209
  /** Valid GeoJSON geometry type names. */
196
210
  const GEOJSON_TYPES = [
197
211
  'Point',
@@ -242,18 +256,18 @@ export function findGeoColumn(schema) {
242
256
  if (GEO_NAMES.includes(f.name.toLowerCase()))
243
257
  return f.name;
244
258
  }
245
- // Priority 4: name contains geo hint with binary type
259
+ // Priority 4: name token matches geo hint with binary type
246
260
  for (const f of schema) {
247
- const n = f.name.toLowerCase();
261
+ const tokens = tokenizeColumnName(f.name);
248
262
  const t = f.type.toLowerCase();
249
263
  const isBinary = t.includes('blob') || t.includes('binary') || t.includes('bytea');
250
- if (isBinary && GEO_NAME_HINTS.some((hint) => n.includes(hint)))
264
+ if (isBinary && tokens.some((tok) => GEO_NAME_HINTS.includes(tok)))
251
265
  return f.name;
252
266
  }
253
- // Priority 5: name contains geo hint, any type
267
+ // Priority 5: name token matches geo hint, any type
254
268
  for (const f of schema) {
255
- const n = f.name.toLowerCase();
256
- if (GEO_NAME_HINTS.some((hint) => n.includes(hint)))
269
+ const tokens = tokenizeColumnName(f.name);
270
+ if (tokens.some((tok) => GEO_NAME_HINTS.includes(tok)))
257
271
  return f.name;
258
272
  }
259
273
  return null;
@@ -44,6 +44,40 @@ export interface ZarrHierarchy {
44
44
  storeAttrs: Record<string, any>;
45
45
  spatialRefAttrs: Record<string, any> | null;
46
46
  }
47
+ export interface GeoZarrInfo {
48
+ /** Relative path within the store to the group carrying GeoZarr attrs. */
49
+ variantPath: string;
50
+ /** Raw attributes object that parses as GeoZarr (for caller re-use). */
51
+ attrs: Record<string, any>;
52
+ }
53
+ /**
54
+ * Walk the hierarchy looking for a node whose attributes satisfy the core
55
+ * GeoZarr convention: `multiscales` + a spatial convention (`spatial` /
56
+ * `spatial:dimensions`) + CRS info (`geo-proj`, `proj:code`, or `crs_wkt`).
57
+ *
58
+ * Check is shape-only (no zarrita I/O), so it's safe to call synchronously
59
+ * after `fetchHierarchy`. A non-null return indicates the store should be
60
+ * rendered via `@developmentseed/deck.gl-zarr`; a null return sends the
61
+ * caller to the `@carbonplan/zarr-layer` fallback.
62
+ */
63
+ export declare function detectGeoZarr(hierarchy: ZarrHierarchy): GeoZarrInfo | null;
64
+ /**
65
+ * Convert a decoded GeoZarr tile (band-planar or packed RGB) into RGBA
66
+ * `ImageData` for deck.gl-zarr's `renderTile` callback. Input is expected to
67
+ * be a Uint8 or Uint16 typed array with either 3 interleaved bytes per pixel
68
+ * or 3 planar bands of width*height values.
69
+ *
70
+ * For 16-bit data the caller supplies the rescale range so the CPU
71
+ * normalization matches what the GPU `LinearRescale` module would produce.
72
+ */
73
+ export declare function zarrTileToImageData(raw: ArrayLike<number> & {
74
+ length: number;
75
+ }, width: number, height: number, opts?: {
76
+ layout?: 'packed' | 'planar';
77
+ bands?: 1 | 3;
78
+ rescaleMin?: number;
79
+ rescaleMax?: number;
80
+ }): ImageData;
47
81
  /** Dimension-like variable names treated as coordinates. */
48
82
  export declare const DIM_LIKE_NAMES: Set<string>;
49
83
  /** Guess dimension names from shape length when metadata is absent. */
@@ -113,6 +113,100 @@ export function extractZarrStoreUrl(url) {
113
113
  }
114
114
  return null;
115
115
  }
116
+ /**
117
+ * Walk the hierarchy looking for a node whose attributes satisfy the core
118
+ * GeoZarr convention: `multiscales` + a spatial convention (`spatial` /
119
+ * `spatial:dimensions`) + CRS info (`geo-proj`, `proj:code`, or `crs_wkt`).
120
+ *
121
+ * Check is shape-only (no zarrita I/O), so it's safe to call synchronously
122
+ * after `fetchHierarchy`. A non-null return indicates the store should be
123
+ * rendered via `@developmentseed/deck.gl-zarr`; a null return sends the
124
+ * caller to the `@carbonplan/zarr-layer` fallback.
125
+ */
126
+ export function detectGeoZarr(hierarchy) {
127
+ const candidate = findGeoZarrNode(hierarchy.root);
128
+ if (candidate)
129
+ return candidate;
130
+ // Also check the top-level storeAttrs — some stores put GeoZarr metadata on
131
+ // the outer zarr.json where the root node wasn't re-attrs'd.
132
+ if (isGeoZarrAttrs(hierarchy.storeAttrs)) {
133
+ return { variantPath: '', attrs: hierarchy.storeAttrs };
134
+ }
135
+ return null;
136
+ }
137
+ function findGeoZarrNode(node) {
138
+ if (isGeoZarrAttrs(node.attributes)) {
139
+ return {
140
+ variantPath: node.path === '/' ? '' : node.path.replace(/^\//, ''),
141
+ attrs: node.attributes
142
+ };
143
+ }
144
+ for (const child of node.children) {
145
+ const match = findGeoZarrNode(child);
146
+ if (match)
147
+ return match;
148
+ }
149
+ return null;
150
+ }
151
+ function isGeoZarrAttrs(attrs) {
152
+ if (!attrs)
153
+ return false;
154
+ const hasMultiscales = Array.isArray(attrs.multiscales) || Boolean(attrs.multiscales?.layout);
155
+ const hasSpatial = Boolean(attrs.spatial) ||
156
+ Boolean(attrs['spatial:dimensions']) ||
157
+ Boolean(attrs['spatial:shape']);
158
+ const hasCrs = Boolean(attrs['geo-proj']) ||
159
+ Boolean(attrs['proj:code']) ||
160
+ Boolean(attrs['proj:wkt2']) ||
161
+ Boolean(attrs['proj:projjson']) ||
162
+ Boolean(attrs.crs) ||
163
+ Boolean(attrs.crs_wkt);
164
+ return hasMultiscales && hasSpatial && hasCrs;
165
+ }
166
+ /**
167
+ * Convert a decoded GeoZarr tile (band-planar or packed RGB) into RGBA
168
+ * `ImageData` for deck.gl-zarr's `renderTile` callback. Input is expected to
169
+ * be a Uint8 or Uint16 typed array with either 3 interleaved bytes per pixel
170
+ * or 3 planar bands of width*height values.
171
+ *
172
+ * For 16-bit data the caller supplies the rescale range so the CPU
173
+ * normalization matches what the GPU `LinearRescale` module would produce.
174
+ */
175
+ export function zarrTileToImageData(raw, width, height, opts = {}) {
176
+ const bands = opts.bands ?? 3;
177
+ const layout = opts.layout ?? 'packed';
178
+ const min = opts.rescaleMin ?? 0;
179
+ const max = opts.rescaleMax ?? 255;
180
+ const range = max - min || 1;
181
+ const pixelCount = width * height;
182
+ const rgba = new Uint8ClampedArray(pixelCount * 4);
183
+ for (let i = 0; i < pixelCount; i++) {
184
+ let r = 0;
185
+ let g = 0;
186
+ let b = 0;
187
+ if (bands === 1) {
188
+ const v = Number(raw[i]);
189
+ r = g = b = (v - min) / range;
190
+ }
191
+ else if (layout === 'planar') {
192
+ r = (Number(raw[i]) - min) / range;
193
+ g = (Number(raw[pixelCount + i]) - min) / range;
194
+ b = (Number(raw[2 * pixelCount + i]) - min) / range;
195
+ }
196
+ else {
197
+ const base = i * 3;
198
+ r = (Number(raw[base]) - min) / range;
199
+ g = (Number(raw[base + 1]) - min) / range;
200
+ b = (Number(raw[base + 2]) - min) / range;
201
+ }
202
+ const idx = i * 4;
203
+ rgba[idx] = Math.round(Math.max(0, Math.min(1, r)) * 255);
204
+ rgba[idx + 1] = Math.round(Math.max(0, Math.min(1, g)) * 255);
205
+ rgba[idx + 2] = Math.round(Math.max(0, Math.min(1, b)) * 255);
206
+ rgba[idx + 3] = 255;
207
+ }
208
+ return new ImageData(rgba, width, height);
209
+ }
116
210
  // ---------------------------------------------------------------------------
117
211
  // Kept helpers
118
212
  // ---------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walkthru-earth/objex",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Svelte 5 components and utilities for exploring geospatial object storage — S3, GCS, Azure, R2",
5
5
  "author": "Youssef Harby <yharby@walkthru.earth>",
6
6
  "license": "CC-BY-4.0",
@@ -123,14 +123,14 @@
123
123
  }
124
124
  },
125
125
  "devDependencies": {
126
- "@biomejs/biome": "^2.4.12",
126
+ "@biomejs/biome": "^2.4.13",
127
127
  "@changesets/changelog-github": "^0.5.2",
128
128
  "@changesets/cli": "^2.31.0",
129
129
  "@fontsource/cairo": "^5.2.7",
130
130
  "@internationalized/date": "^3.12.1",
131
131
  "@lucide/svelte": "^0.561.0",
132
132
  "@sveltejs/adapter-static": "^3.0.10",
133
- "@sveltejs/kit": "^2.57.1",
133
+ "@sveltejs/kit": "^2.58.0",
134
134
  "@sveltejs/package": "^2.5.7",
135
135
  "@sveltejs/vite-plugin-svelte": "^6.2.4",
136
136
  "@tailwindcss/forms": "^0.5.11",
@@ -138,12 +138,12 @@
138
138
  "@tailwindcss/vite": "^4.2.4",
139
139
  "bits-ui": "^2.18.0",
140
140
  "clsx": "^2.1.1",
141
- "knip": "^6.6.0",
141
+ "knip": "^6.6.2",
142
142
  "lefthook": "^2.1.6",
143
143
  "paneforge": "^1.0.2",
144
- "posthog-js": "^1.369.5",
144
+ "posthog-js": "^1.371.3",
145
145
  "publint": "^0.3.18",
146
- "svelte": "^5.55.4",
146
+ "svelte": "^5.55.5",
147
147
  "svelte-check": "^4.4.6",
148
148
  "tailwind-merge": "^3.5.0",
149
149
  "tailwind-variants": "^3.2.2",
@@ -168,11 +168,12 @@
168
168
  "@deck.gl/layers": "^9.3.1",
169
169
  "@deck.gl/mapbox": "^9.3.1",
170
170
  "@deck.gl/mesh-layers": "^9.3.1",
171
- "@developmentseed/deck.gl-geotiff": "^0.5.0",
172
- "@developmentseed/deck.gl-raster": "^0.5.0",
173
- "@developmentseed/epsg": "^0.5.0",
174
- "@developmentseed/geotiff": "^0.5.0",
175
- "@developmentseed/proj": "^0.5.0",
171
+ "@developmentseed/deck.gl-geotiff": "0.6.0-alpha.1",
172
+ "@developmentseed/deck.gl-raster": "0.6.0-alpha.1",
173
+ "@developmentseed/deck.gl-zarr": "0.6.0-alpha.1",
174
+ "@developmentseed/epsg": "0.6.0-alpha.1",
175
+ "@developmentseed/geotiff": "0.6.0-alpha.1",
176
+ "@developmentseed/proj": "0.6.0-alpha.1",
176
177
  "@duckdb/duckdb-wasm": "1.33.1-dev53.0",
177
178
  "@geoarrow/deck.gl-layers": "^0.3.2",
178
179
  "@luma.gl/core": "^9.3.3",
@@ -190,7 +191,7 @@
190
191
  "hyparquet": "^1.25.6",
191
192
  "hyparquet-compressors": "^1.1.1",
192
193
  "lz-string": "^1.5.0",
193
- "maplibre-gl": "^5.23.0",
194
+ "maplibre-gl": "^5.24.0",
194
195
  "marked": "^17.0.6",
195
196
  "mermaid": "^11.14.0",
196
197
  "pbf": "^4.0.1",
@@ -200,7 +201,7 @@
200
201
  "shiki": "^3.23.0",
201
202
  "sql-formatter": "^15.7.3",
202
203
  "yaml": "^2.8.3",
203
- "zarrita": "^0.6.2"
204
+ "zarrita": "^0.7.2"
204
205
  },
205
206
  "scripts": {
206
207
  "dev": "vite dev",