@walkthru-earth/objex 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 +11 -2
- package/dist/components/browser/FileBrowser.svelte +41 -54
- package/dist/components/browser/FileTreeSidebar.svelte +43 -7
- package/dist/components/layout/ConnectionDialog.svelte +100 -1
- package/dist/components/layout/Sidebar.svelte +43 -25
- package/dist/components/viewers/CodeViewer.svelte +23 -0
- package/dist/components/viewers/CogControls.svelte +208 -0
- package/dist/components/viewers/CogControls.svelte.d.ts +12 -0
- package/dist/components/viewers/CogViewer.svelte +353 -1160
- package/dist/components/viewers/CogViewer.svelte.d.ts +1 -1
- package/dist/components/viewers/DatabaseViewer.svelte +345 -37
- package/dist/components/viewers/MarkdownViewer.svelte +1 -1
- package/dist/components/viewers/TableViewer.svelte +123 -41
- package/dist/components/viewers/ZarrMapViewer.svelte +29 -0
- package/dist/components/viewers/ZarrViewer.svelte +1 -4
- package/dist/constants.d.ts +6 -2
- package/dist/constants.js +6 -2
- package/dist/file-icons/index.d.ts +1 -1
- package/dist/file-icons/index.js +12 -2
- package/dist/i18n/ar.js +24 -0
- package/dist/i18n/en.js +24 -0
- package/dist/i18n/index.svelte.d.ts +0 -1
- package/dist/i18n/index.svelte.js +0 -3
- package/dist/index.d.ts +11 -0
- package/dist/index.js +10 -0
- package/dist/query/engine.d.ts +20 -4
- package/dist/query/index.d.ts +2 -1
- package/dist/query/index.js +1 -0
- package/dist/query/source.d.ts +30 -0
- package/dist/query/source.js +37 -0
- package/dist/query/wasm.d.ts +7 -5
- package/dist/query/wasm.js +138 -85
- package/dist/storage/providers.d.ts +47 -0
- package/dist/storage/providers.js +160 -0
- package/dist/stores/connections.svelte.js +5 -31
- package/dist/stores/files.svelte.d.ts +2 -8
- package/dist/stores/files.svelte.js +5 -38
- package/dist/stores/query-history.svelte.js +3 -25
- package/dist/stores/settings.svelte.d.ts +1 -0
- package/dist/stores/settings.svelte.js +10 -30
- package/dist/stores/tabs.svelte.d.ts +9 -2
- package/dist/stores/tabs.svelte.js +11 -2
- package/dist/types.d.ts +11 -0
- package/dist/utils/cloud-url.d.ts +27 -0
- package/dist/utils/cloud-url.js +61 -0
- package/dist/utils/cog.d.ts +244 -0
- package/dist/utils/cog.js +1039 -0
- package/dist/utils/deck.d.ts +0 -18
- package/dist/utils/deck.js +0 -36
- package/dist/utils/export.d.ts +22 -2
- package/dist/utils/export.js +35 -10
- package/dist/utils/file-sort.d.ts +20 -0
- package/dist/utils/file-sort.js +41 -0
- package/dist/utils/geometry-type.d.ts +52 -0
- package/dist/utils/geometry-type.js +76 -0
- package/dist/utils/local-storage.d.ts +16 -0
- package/dist/utils/local-storage.js +37 -0
- package/dist/utils/markdown-sql.d.ts +1 -1
- package/dist/utils/markdown-sql.js +3 -4
- package/dist/utils/pmtiles-tile.d.ts +0 -2
- package/dist/utils/pmtiles-tile.js +0 -8
- package/dist/utils/url-state.d.ts +6 -0
- package/dist/utils/url-state.js +34 -26
- package/dist/utils/url.d.ts +13 -25
- package/dist/utils/url.js +17 -78
- package/dist/utils/zarr-tab.d.ts +22 -0
- package/dist/utils/zarr-tab.js +30 -0
- package/dist/utils/zarr.d.ts +0 -2
- package/dist/utils/zarr.js +73 -44
- package/package.json +50 -46
- package/dist/components/ui/tabs/index.d.ts +0 -5
- package/dist/components/ui/tabs/index.js +0 -7
- package/dist/components/ui/tabs/tabs-content.svelte +0 -17
- package/dist/components/ui/tabs/tabs-content.svelte.d.ts +0 -4
- package/dist/components/ui/tabs/tabs-list.svelte +0 -16
- package/dist/components/ui/tabs/tabs-list.svelte.d.ts +0 -4
- package/dist/components/ui/tabs/tabs-trigger.svelte +0 -20
- package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +0 -4
- package/dist/components/ui/tabs/tabs.svelte +0 -19
- package/dist/components/ui/tabs/tabs.svelte.d.ts +0 -4
- package/dist/components/viewers/MapViewer.svelte +0 -234
- package/dist/components/viewers/MapViewer.svelte.d.ts +0 -7
- package/dist/components/viewers/StyleEditorOverlay.svelte +0 -27
- package/dist/components/viewers/StyleEditorOverlay.svelte.d.ts +0 -7
package/dist/utils/deck.d.ts
CHANGED
|
@@ -33,24 +33,6 @@ export declare function loadDeckModules(): Promise<{
|
|
|
33
33
|
MapboxOverlay: typeof import("@deck.gl/mapbox").MapboxOverlay;
|
|
34
34
|
GeoJsonLayer: typeof import("@deck.gl/layers").GeoJsonLayer;
|
|
35
35
|
}>;
|
|
36
|
-
export interface DeckOverlayOptions {
|
|
37
|
-
layerId: string;
|
|
38
|
-
data: GeoJSON.FeatureCollection;
|
|
39
|
-
/** Called with properties and the full GeoJSON Feature (for selection highlight). */
|
|
40
|
-
onClick?: (properties: Record<string, any>, feature: GeoJSON.Feature) => void;
|
|
41
|
-
/** Layer-level onHover — use hoverCursor(map) to toggle pointer on MapLibre canvas. */
|
|
42
|
-
onHover?: (info: {
|
|
43
|
-
picked?: boolean;
|
|
44
|
-
}) => void;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Create a MapboxOverlay with a single GeoJsonLayer.
|
|
48
|
-
* Colors are assigned per-feature based on geometry type.
|
|
49
|
-
*/
|
|
50
|
-
export declare function createDeckOverlay(modules: {
|
|
51
|
-
MapboxOverlay: any;
|
|
52
|
-
GeoJsonLayer: any;
|
|
53
|
-
}, options: DeckOverlayOptions): any;
|
|
54
36
|
/** Lazy-load GeoArrow deck.gl layers + MapboxOverlay + GeoJsonLayer (for selection). */
|
|
55
37
|
export declare function loadGeoArrowModules(): Promise<{
|
|
56
38
|
GeoArrowArcLayer: typeof import("@geoarrow/deck.gl-layers").GeoArrowArcLayer;
|
package/dist/utils/deck.js
CHANGED
|
@@ -46,42 +46,6 @@ export async function loadDeckModules() {
|
|
|
46
46
|
]);
|
|
47
47
|
return { MapboxOverlay, GeoJsonLayer };
|
|
48
48
|
}
|
|
49
|
-
/**
|
|
50
|
-
* Create a MapboxOverlay with a single GeoJsonLayer.
|
|
51
|
-
* Colors are assigned per-feature based on geometry type.
|
|
52
|
-
*/
|
|
53
|
-
export function createDeckOverlay(modules, options) {
|
|
54
|
-
const { MapboxOverlay, GeoJsonLayer } = modules;
|
|
55
|
-
const { layerId, data, onClick, onHover } = options;
|
|
56
|
-
return new MapboxOverlay({
|
|
57
|
-
interleaved: false,
|
|
58
|
-
layers: [
|
|
59
|
-
new GeoJsonLayer({
|
|
60
|
-
id: layerId,
|
|
61
|
-
data,
|
|
62
|
-
pickable: true,
|
|
63
|
-
stroked: true,
|
|
64
|
-
filled: true,
|
|
65
|
-
pointType: 'circle',
|
|
66
|
-
getFillColor: geojsonFillColor,
|
|
67
|
-
getLineColor: geojsonLineColor,
|
|
68
|
-
getPointRadius: 6,
|
|
69
|
-
getLineWidth: 2.5,
|
|
70
|
-
lineWidthMinPixels: 1.5,
|
|
71
|
-
pointRadiusMinPixels: 4,
|
|
72
|
-
pointRadiusMaxPixels: 12,
|
|
73
|
-
autoHighlight: true,
|
|
74
|
-
highlightColor: [255, 255, 255, 100],
|
|
75
|
-
onHover,
|
|
76
|
-
onClick: (info) => {
|
|
77
|
-
if (info.object?.properties && onClick) {
|
|
78
|
-
onClick({ ...info.object.properties }, info.object);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
})
|
|
82
|
-
]
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
49
|
// ─── GeoArrow overlay (GeoParquetMapViewer) ──────────────────────────
|
|
86
50
|
/** Lazy-load GeoArrow deck.gl layers + MapboxOverlay + GeoJsonLayer (for selection). */
|
|
87
51
|
export async function loadGeoArrowModules() {
|
package/dist/utils/export.d.ts
CHANGED
|
@@ -1,2 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Escape a CSV field value per RFC 4180.
|
|
3
|
+
*/
|
|
4
|
+
export declare function escapeCsvField(value: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Serialize column/row data to a CSV string.
|
|
7
|
+
* Pure function — no browser APIs, works in Node.js.
|
|
8
|
+
*/
|
|
9
|
+
export declare function serializeToCsv(columns: string[], rows: Record<string, unknown>[]): string;
|
|
10
|
+
/**
|
|
11
|
+
* Serialize column/row data to a formatted JSON string.
|
|
12
|
+
* Pure function — no browser APIs, works in Node.js.
|
|
13
|
+
*/
|
|
14
|
+
export declare function serializeToJson(columns: string[], rows: Record<string, unknown>[]): string;
|
|
15
|
+
/**
|
|
16
|
+
* Export data as CSV file (triggers browser download).
|
|
17
|
+
*/
|
|
18
|
+
export declare function exportToCsv(columns: string[], rows: Record<string, unknown>[], filename: string): void;
|
|
19
|
+
/**
|
|
20
|
+
* Export data as JSON file (triggers browser download).
|
|
21
|
+
*/
|
|
22
|
+
export declare function exportToJson(columns: string[], rows: Record<string, unknown>[], filename: string): void;
|
package/dist/utils/export.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { jsonReplacerBigInt } from './format.js';
|
|
1
2
|
function triggerDownload(content, filename, mimeType) {
|
|
2
3
|
const blob = new Blob([content], { type: mimeType });
|
|
3
4
|
const url = URL.createObjectURL(blob);
|
|
@@ -9,30 +10,43 @@ function triggerDownload(content, filename, mimeType) {
|
|
|
9
10
|
document.body.removeChild(a);
|
|
10
11
|
URL.revokeObjectURL(url);
|
|
11
12
|
}
|
|
12
|
-
|
|
13
|
+
/** Format a cell value for export (empty string for null/undefined). */
|
|
14
|
+
function formatCellValue(value) {
|
|
13
15
|
if (value === null || value === undefined)
|
|
14
16
|
return '';
|
|
15
17
|
if (value instanceof Date)
|
|
16
18
|
return value.toISOString();
|
|
19
|
+
if (typeof value === 'bigint')
|
|
20
|
+
return value.toString();
|
|
17
21
|
if (typeof value === 'object')
|
|
18
|
-
return JSON.stringify(value);
|
|
22
|
+
return JSON.stringify(value, jsonReplacerBigInt);
|
|
19
23
|
return String(value);
|
|
20
24
|
}
|
|
21
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Escape a CSV field value per RFC 4180.
|
|
27
|
+
*/
|
|
28
|
+
export function escapeCsvField(value) {
|
|
22
29
|
if (value.includes(',') || value.includes('"') || value.includes('\n') || value.includes('\r')) {
|
|
23
30
|
return `"${value.replace(/"/g, '""')}"`;
|
|
24
31
|
}
|
|
25
32
|
return value;
|
|
26
33
|
}
|
|
27
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Serialize column/row data to a CSV string.
|
|
36
|
+
* Pure function — no browser APIs, works in Node.js.
|
|
37
|
+
*/
|
|
38
|
+
export function serializeToCsv(columns, rows) {
|
|
28
39
|
const header = columns.map(escapeCsvField).join(',');
|
|
29
40
|
const body = rows
|
|
30
|
-
.map((row) => columns.map((col) => escapeCsvField(
|
|
41
|
+
.map((row) => columns.map((col) => escapeCsvField(formatCellValue(row[col]))).join(','))
|
|
31
42
|
.join('\n');
|
|
32
|
-
|
|
33
|
-
triggerDownload(csv, filename.endsWith('.csv') ? filename : `${filename}.csv`, 'text/csv;charset=utf-8;');
|
|
43
|
+
return `${header}\n${body}`;
|
|
34
44
|
}
|
|
35
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Serialize column/row data to a formatted JSON string.
|
|
47
|
+
* Pure function — no browser APIs, works in Node.js.
|
|
48
|
+
*/
|
|
49
|
+
export function serializeToJson(columns, rows) {
|
|
36
50
|
const data = rows.map((row) => {
|
|
37
51
|
const obj = {};
|
|
38
52
|
for (const col of columns) {
|
|
@@ -46,6 +60,17 @@ export function exportToJson(columns, rows, filename) {
|
|
|
46
60
|
}
|
|
47
61
|
return obj;
|
|
48
62
|
});
|
|
49
|
-
|
|
50
|
-
|
|
63
|
+
return JSON.stringify(data, jsonReplacerBigInt, 2);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Export data as CSV file (triggers browser download).
|
|
67
|
+
*/
|
|
68
|
+
export function exportToCsv(columns, rows, filename) {
|
|
69
|
+
triggerDownload(serializeToCsv(columns, rows), filename.endsWith('.csv') ? filename : `${filename}.csv`, 'text/csv;charset=utf-8;');
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Export data as JSON file (triggers browser download).
|
|
73
|
+
*/
|
|
74
|
+
export function exportToJson(columns, rows, filename) {
|
|
75
|
+
triggerDownload(serializeToJson(columns, rows), filename.endsWith('.json') ? filename : `${filename}.json`, 'application/json');
|
|
51
76
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure file entry sorting — framework-agnostic, works in Node.js.
|
|
3
|
+
*/
|
|
4
|
+
import type { FileEntry } from '../types.js';
|
|
5
|
+
export type SortField = 'name' | 'size' | 'modified' | 'extension';
|
|
6
|
+
export type SortDirection = 'asc' | 'desc';
|
|
7
|
+
export interface SortConfig {
|
|
8
|
+
field: SortField;
|
|
9
|
+
direction: SortDirection;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Sort file entries by the given config.
|
|
13
|
+
* Directories always sort before files regardless of sort field.
|
|
14
|
+
* Returns a new array (does not mutate the input).
|
|
15
|
+
*/
|
|
16
|
+
export declare function sortFileEntries(entries: FileEntry[], config: SortConfig): FileEntry[];
|
|
17
|
+
/**
|
|
18
|
+
* Toggle sort config: same field flips direction, new field starts ascending.
|
|
19
|
+
*/
|
|
20
|
+
export declare function toggleSortField(current: SortConfig, field: SortField): SortConfig;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure file entry sorting — framework-agnostic, works in Node.js.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Sort file entries by the given config.
|
|
6
|
+
* Directories always sort before files regardless of sort field.
|
|
7
|
+
* Returns a new array (does not mutate the input).
|
|
8
|
+
*/
|
|
9
|
+
export function sortFileEntries(entries, config) {
|
|
10
|
+
const sorted = [...entries];
|
|
11
|
+
const dir = config.direction === 'asc' ? 1 : -1;
|
|
12
|
+
sorted.sort((a, b) => {
|
|
13
|
+
// Directories always come first
|
|
14
|
+
if (a.is_dir && !b.is_dir)
|
|
15
|
+
return -1;
|
|
16
|
+
if (!a.is_dir && b.is_dir)
|
|
17
|
+
return 1;
|
|
18
|
+
switch (config.field) {
|
|
19
|
+
case 'name':
|
|
20
|
+
return dir * a.name.localeCompare(b.name, undefined, { sensitivity: 'base' });
|
|
21
|
+
case 'size':
|
|
22
|
+
return dir * (a.size - b.size);
|
|
23
|
+
case 'modified':
|
|
24
|
+
return dir * (a.modified - b.modified);
|
|
25
|
+
case 'extension':
|
|
26
|
+
return dir * a.extension.localeCompare(b.extension, undefined, { sensitivity: 'base' });
|
|
27
|
+
default:
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return sorted;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Toggle sort config: same field flips direction, new field starts ascending.
|
|
35
|
+
*/
|
|
36
|
+
export function toggleSortField(current, field) {
|
|
37
|
+
if (current.field === field) {
|
|
38
|
+
return { field, direction: current.direction === 'asc' ? 'desc' : 'asc' };
|
|
39
|
+
}
|
|
40
|
+
return { field, direction: 'asc' };
|
|
41
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for parsing DuckDB v1.5 parameterized GEOMETRY type strings.
|
|
3
|
+
*
|
|
4
|
+
* DuckDB v1.5 made GEOMETRY a core type with an optional CRS parameter:
|
|
5
|
+
* GEOMETRY — no CRS attached
|
|
6
|
+
* GEOMETRY('EPSG:4326') — EPSG form
|
|
7
|
+
* GEOMETRY('OGC:CRS84') — OGC form (canonical for GeoParquet 1.1+)
|
|
8
|
+
* GEOMETRY('EPSG:27700') — projected CRS
|
|
9
|
+
*
|
|
10
|
+
* Type strings may come from `DESCRIBE`, from the Arrow schema, or from a
|
|
11
|
+
* legacy code path that still reports `BLOB`. Use these helpers everywhere
|
|
12
|
+
* instead of ad-hoc regex so behaviour stays consistent.
|
|
13
|
+
*/
|
|
14
|
+
export interface GeometryTypeInfo {
|
|
15
|
+
/** True if the type is some form of GEOMETRY (with or without CRS). */
|
|
16
|
+
isGeometry: boolean;
|
|
17
|
+
/** True if the type carries a CRS parameter, e.g. GEOMETRY('EPSG:4326'). */
|
|
18
|
+
hasCrs: boolean;
|
|
19
|
+
/** The CRS string if present, otherwise null. Raw value, including WGS84. */
|
|
20
|
+
rawCrs: string | null;
|
|
21
|
+
/**
|
|
22
|
+
* The CRS string if present and NOT a WGS84 variant (EPSG:4326, EPSG:4979,
|
|
23
|
+
* OGC:CRS84). Returns null for WGS84 so callers can skip ST_Transform.
|
|
24
|
+
*/
|
|
25
|
+
nonWgs84Crs: string | null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Parse a DuckDB type string and report whether it is a GEOMETRY type, and
|
|
29
|
+
* whether a CRS parameter is attached.
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseGeometryTypeCrs(typeStr: string | null | undefined): GeometryTypeInfo;
|
|
32
|
+
/** True for EPSG:4326, EPSG:4979, OGC:CRS84 and equivalent strings. */
|
|
33
|
+
export declare function isWgs84Crs(crs: string | null | undefined): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Build a `ST_Transform(...)` SQL expression choosing the 2-arg form when the
|
|
36
|
+
* input already carries its CRS in the GEOMETRY type (DuckDB v1.5), and the
|
|
37
|
+
* 3-arg form otherwise.
|
|
38
|
+
*
|
|
39
|
+
* `geometry_always_xy` is set globally at DB init, so no per-call `always_xy`
|
|
40
|
+
* argument is needed.
|
|
41
|
+
*/
|
|
42
|
+
export declare function buildTransformExpr(innerExpr: string, sourceType: string, sourceCrs: string, targetCrs: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* Wrap a bare WKB expression with `ST_SetCRS(ST_GeomFromWKB(...))` so that the
|
|
45
|
+
* resulting GEOMETRY value carries a CRS through the rest of the pipeline.
|
|
46
|
+
* Used in the legacy GeoParquet fallback where we read the geometry column as
|
|
47
|
+
* BLOB but still know the source CRS from hyparquet metadata or the GeoParquet
|
|
48
|
+
* footer.
|
|
49
|
+
*
|
|
50
|
+
* If `sourceCrs` is null/empty, returns a plain `ST_GeomFromWKB(...)`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function wrapWkbWithCrs(wkbExpr: string, sourceCrs: string | null | undefined): string;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for parsing DuckDB v1.5 parameterized GEOMETRY type strings.
|
|
3
|
+
*
|
|
4
|
+
* DuckDB v1.5 made GEOMETRY a core type with an optional CRS parameter:
|
|
5
|
+
* GEOMETRY — no CRS attached
|
|
6
|
+
* GEOMETRY('EPSG:4326') — EPSG form
|
|
7
|
+
* GEOMETRY('OGC:CRS84') — OGC form (canonical for GeoParquet 1.1+)
|
|
8
|
+
* GEOMETRY('EPSG:27700') — projected CRS
|
|
9
|
+
*
|
|
10
|
+
* Type strings may come from `DESCRIBE`, from the Arrow schema, or from a
|
|
11
|
+
* legacy code path that still reports `BLOB`. Use these helpers everywhere
|
|
12
|
+
* instead of ad-hoc regex so behaviour stays consistent.
|
|
13
|
+
*/
|
|
14
|
+
import { WGS84_CODES } from '../constants.js';
|
|
15
|
+
const GEOMETRY_PREFIX = /^GEOMETRY(\s*\(\s*'?([^')]+)'?\s*\))?/i;
|
|
16
|
+
/**
|
|
17
|
+
* Parse a DuckDB type string and report whether it is a GEOMETRY type, and
|
|
18
|
+
* whether a CRS parameter is attached.
|
|
19
|
+
*/
|
|
20
|
+
export function parseGeometryTypeCrs(typeStr) {
|
|
21
|
+
if (!typeStr)
|
|
22
|
+
return { isGeometry: false, hasCrs: false, rawCrs: null, nonWgs84Crs: null };
|
|
23
|
+
const match = typeStr.match(GEOMETRY_PREFIX);
|
|
24
|
+
if (!match)
|
|
25
|
+
return { isGeometry: false, hasCrs: false, rawCrs: null, nonWgs84Crs: null };
|
|
26
|
+
const rawCrs = match[2]?.trim() ?? null;
|
|
27
|
+
if (!rawCrs)
|
|
28
|
+
return { isGeometry: true, hasCrs: false, rawCrs: null, nonWgs84Crs: null };
|
|
29
|
+
return {
|
|
30
|
+
isGeometry: true,
|
|
31
|
+
hasCrs: true,
|
|
32
|
+
rawCrs,
|
|
33
|
+
nonWgs84Crs: isWgs84Crs(rawCrs) ? null : rawCrs
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** True for EPSG:4326, EPSG:4979, OGC:CRS84 and equivalent strings. */
|
|
37
|
+
export function isWgs84Crs(crs) {
|
|
38
|
+
if (!crs)
|
|
39
|
+
return true;
|
|
40
|
+
const trimmed = crs.trim();
|
|
41
|
+
if (trimmed === 'OGC:CRS84' || trimmed === 'OGC:CRS83')
|
|
42
|
+
return true;
|
|
43
|
+
const epsgMatch = trimmed.match(/^EPSG:(\d+)$/i);
|
|
44
|
+
if (epsgMatch && WGS84_CODES.has(Number(epsgMatch[1])))
|
|
45
|
+
return true;
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Build a `ST_Transform(...)` SQL expression choosing the 2-arg form when the
|
|
50
|
+
* input already carries its CRS in the GEOMETRY type (DuckDB v1.5), and the
|
|
51
|
+
* 3-arg form otherwise.
|
|
52
|
+
*
|
|
53
|
+
* `geometry_always_xy` is set globally at DB init, so no per-call `always_xy`
|
|
54
|
+
* argument is needed.
|
|
55
|
+
*/
|
|
56
|
+
export function buildTransformExpr(innerExpr, sourceType, sourceCrs, targetCrs) {
|
|
57
|
+
const info = parseGeometryTypeCrs(sourceType);
|
|
58
|
+
if (info.hasCrs) {
|
|
59
|
+
return `ST_Transform(${innerExpr}, '${targetCrs}')`;
|
|
60
|
+
}
|
|
61
|
+
return `ST_Transform(${innerExpr}, '${sourceCrs}', '${targetCrs}')`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Wrap a bare WKB expression with `ST_SetCRS(ST_GeomFromWKB(...))` so that the
|
|
65
|
+
* resulting GEOMETRY value carries a CRS through the rest of the pipeline.
|
|
66
|
+
* Used in the legacy GeoParquet fallback where we read the geometry column as
|
|
67
|
+
* BLOB but still know the source CRS from hyparquet metadata or the GeoParquet
|
|
68
|
+
* footer.
|
|
69
|
+
*
|
|
70
|
+
* If `sourceCrs` is null/empty, returns a plain `ST_GeomFromWKB(...)`.
|
|
71
|
+
*/
|
|
72
|
+
export function wrapWkbWithCrs(wkbExpr, sourceCrs) {
|
|
73
|
+
if (!sourceCrs)
|
|
74
|
+
return `ST_GeomFromWKB(${wkbExpr})`;
|
|
75
|
+
return `ST_SetCRS(ST_GeomFromWKB(${wkbExpr}), '${sourceCrs}')`;
|
|
76
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic localStorage helpers with SSR safety.
|
|
3
|
+
*
|
|
4
|
+
* Used by connection, settings, and query-history stores to avoid
|
|
5
|
+
* repeating the same load/persist/try-catch/SSR-guard pattern.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Load a JSON value from localStorage.
|
|
9
|
+
* Returns `defaultValue` on SSR, missing key, or parse error.
|
|
10
|
+
*/
|
|
11
|
+
export declare function loadFromStorage<T>(key: string, defaultValue: T): T;
|
|
12
|
+
/**
|
|
13
|
+
* Persist a JSON-serializable value to localStorage.
|
|
14
|
+
* Silently no-ops on SSR or storage errors (quota, private browsing).
|
|
15
|
+
*/
|
|
16
|
+
export declare function persistToStorage(key: string, value: unknown): void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic localStorage helpers with SSR safety.
|
|
3
|
+
*
|
|
4
|
+
* Used by connection, settings, and query-history stores to avoid
|
|
5
|
+
* repeating the same load/persist/try-catch/SSR-guard pattern.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Load a JSON value from localStorage.
|
|
9
|
+
* Returns `defaultValue` on SSR, missing key, or parse error.
|
|
10
|
+
*/
|
|
11
|
+
export function loadFromStorage(key, defaultValue) {
|
|
12
|
+
if (typeof window === 'undefined')
|
|
13
|
+
return defaultValue;
|
|
14
|
+
try {
|
|
15
|
+
const raw = localStorage.getItem(key);
|
|
16
|
+
if (raw)
|
|
17
|
+
return JSON.parse(raw);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// ignore parse errors
|
|
21
|
+
}
|
|
22
|
+
return defaultValue;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Persist a JSON-serializable value to localStorage.
|
|
26
|
+
* Silently no-ops on SSR or storage errors (quota, private browsing).
|
|
27
|
+
*/
|
|
28
|
+
export function persistToStorage(key, value) {
|
|
29
|
+
if (typeof window === 'undefined')
|
|
30
|
+
return;
|
|
31
|
+
try {
|
|
32
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// ignore storage errors
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -17,7 +17,7 @@ export interface ParsedMarkdownDocument {
|
|
|
17
17
|
* SELECT * FROM table
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
|
-
export declare function parseMarkdownDocument(markdown: string): ParsedMarkdownDocument
|
|
20
|
+
export declare function parseMarkdownDocument(markdown: string): Promise<ParsedMarkdownDocument>;
|
|
21
21
|
/**
|
|
22
22
|
* Interpolate template variables in markdown text.
|
|
23
23
|
* Supports {queryName.rows[0].columnName} syntax.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import YAML from 'yaml';
|
|
2
1
|
/**
|
|
3
2
|
* Parse a markdown document with YAML frontmatter and SQL code blocks.
|
|
4
3
|
*
|
|
@@ -7,17 +6,17 @@ import YAML from 'yaml';
|
|
|
7
6
|
* SELECT * FROM table
|
|
8
7
|
* ```
|
|
9
8
|
*/
|
|
10
|
-
export function parseMarkdownDocument(markdown) {
|
|
9
|
+
export async function parseMarkdownDocument(markdown) {
|
|
11
10
|
let frontmatter = {};
|
|
12
11
|
let content = markdown;
|
|
13
|
-
// Extract YAML frontmatter
|
|
14
12
|
const fmMatch = markdown.match(/^---\n([\s\S]*?)\n---\n/);
|
|
15
13
|
if (fmMatch) {
|
|
16
14
|
try {
|
|
15
|
+
const { default: YAML } = await import('yaml');
|
|
17
16
|
frontmatter = YAML.parse(fmMatch[1]) || {};
|
|
18
17
|
}
|
|
19
18
|
catch {
|
|
20
|
-
// Invalid YAML
|
|
19
|
+
// Invalid YAML or yaml peer dep not installed, ignore frontmatter
|
|
21
20
|
}
|
|
22
21
|
content = markdown.slice(fmMatch[0].length);
|
|
23
22
|
}
|
|
@@ -30,8 +30,6 @@ export interface DecodedFeature {
|
|
|
30
30
|
* Returns null if the tile does not exist or for raster archives.
|
|
31
31
|
*/
|
|
32
32
|
export declare function decodeMvtTile(pmtiles: PMTiles, z: number, x: number, y: number): Promise<DecodedTile | null>;
|
|
33
|
-
/** Convert tile bytes to a Blob URL for raster tile preview. */
|
|
34
|
-
export declare function tileToImageUrl(pmtiles: PMTiles, z: number, x: number, y: number, mimeType: string): Promise<string | null>;
|
|
35
33
|
/** MIME type for a PMTiles tile format string. */
|
|
36
34
|
export declare function tileMimeType(format: string): string;
|
|
37
35
|
/** Compute the hue for layer index i (same palette as buildPmtilesLayers). */
|
|
@@ -41,14 +41,6 @@ export async function decodeMvtTile(pmtiles, z, x, y) {
|
|
|
41
41
|
}
|
|
42
42
|
return { z, x, y, layers, rawSize };
|
|
43
43
|
}
|
|
44
|
-
/** Convert tile bytes to a Blob URL for raster tile preview. */
|
|
45
|
-
export async function tileToImageUrl(pmtiles, z, x, y, mimeType) {
|
|
46
|
-
const resp = await pmtiles.getZxy(z, x, y);
|
|
47
|
-
if (!resp)
|
|
48
|
-
return null;
|
|
49
|
-
const blob = new Blob([resp.data], { type: mimeType });
|
|
50
|
-
return URL.createObjectURL(blob);
|
|
51
|
-
}
|
|
52
44
|
/** MIME type for a PMTiles tile format string. */
|
|
53
45
|
export function tileMimeType(format) {
|
|
54
46
|
const map = {
|
|
@@ -34,6 +34,12 @@ export declare function getUrlView(): string;
|
|
|
34
34
|
* Read the prefix (file/folder path) from the ?url= param.
|
|
35
35
|
*/
|
|
36
36
|
export declare function getUrlPrefix(): string;
|
|
37
|
+
/**
|
|
38
|
+
* True when a `?url=` param is present. Single source of truth, used by
|
|
39
|
+
* the tab-sync effect and Sidebar auto-detection to decide whether an
|
|
40
|
+
* auto-migration is in progress (see `+page.svelte` tab-sync effect).
|
|
41
|
+
*/
|
|
42
|
+
export declare function hasUrlParam(): boolean;
|
|
37
43
|
/**
|
|
38
44
|
* Clear all URL state params.
|
|
39
45
|
*/
|
package/dist/utils/url-state.js
CHANGED
|
@@ -28,43 +28,43 @@ export function buildUrlParam(conn, prefix) {
|
|
|
28
28
|
return `${base}/${prefix.replace(/^\//, '')}`;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
31
|
+
* Apply a URL mutation, skipping `replaceState` if nothing changed.
|
|
32
|
+
* Every public mutator below funnels through this to avoid `replaceState`
|
|
33
|
+
* thrash when the tab-sync effect re-fires on unrelated reactive changes.
|
|
32
34
|
*/
|
|
33
|
-
|
|
35
|
+
function writeLocation(mutate) {
|
|
34
36
|
try {
|
|
35
37
|
const url = new URL(window.location.href);
|
|
36
|
-
url.
|
|
37
|
-
|
|
38
|
+
const before = url.pathname + url.search + url.hash;
|
|
39
|
+
mutate(url);
|
|
40
|
+
const after = url.pathname + url.search + url.hash;
|
|
41
|
+
if (before === after)
|
|
42
|
+
return;
|
|
43
|
+
replaceState(after, {});
|
|
38
44
|
}
|
|
39
45
|
catch {
|
|
40
46
|
/* ignore */
|
|
41
47
|
}
|
|
42
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Set the ?url= param to a raw URL string (for direct URL tabs).
|
|
51
|
+
*/
|
|
52
|
+
export function setRawUrlParam(rawUrl) {
|
|
53
|
+
writeLocation((url) => url.searchParams.set('url', rawUrl));
|
|
54
|
+
}
|
|
43
55
|
/**
|
|
44
56
|
* Sync the ?url= param in the browser URL.
|
|
45
57
|
*/
|
|
46
58
|
export function syncUrlParam(conn, prefix) {
|
|
47
|
-
|
|
48
|
-
const url = new URL(window.location.href);
|
|
49
|
-
url.searchParams.set('url', buildUrlParam(conn, prefix));
|
|
50
|
-
replaceState(url.pathname + url.search + url.hash, {});
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
/* ignore */
|
|
54
|
-
}
|
|
59
|
+
writeLocation((url) => url.searchParams.set('url', buildUrlParam(conn, prefix)));
|
|
55
60
|
}
|
|
56
61
|
/**
|
|
57
62
|
* Update the #hash in the URL to reflect the current view mode.
|
|
58
63
|
*/
|
|
59
64
|
export function updateUrlView(view) {
|
|
60
|
-
|
|
61
|
-
const url = new URL(window.location.href);
|
|
65
|
+
writeLocation((url) => {
|
|
62
66
|
url.hash = view || '';
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
/* ignore */
|
|
67
|
-
}
|
|
67
|
+
});
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
70
70
|
* Read the current #hash view mode from the URL.
|
|
@@ -93,16 +93,24 @@ export function getUrlPrefix() {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
/**
|
|
96
|
-
*
|
|
96
|
+
* True when a `?url=` param is present. Single source of truth, used by
|
|
97
|
+
* the tab-sync effect and Sidebar auto-detection to decide whether an
|
|
98
|
+
* auto-migration is in progress (see `+page.svelte` tab-sync effect).
|
|
97
99
|
*/
|
|
98
|
-
export function
|
|
100
|
+
export function hasUrlParam() {
|
|
99
101
|
try {
|
|
100
|
-
|
|
101
|
-
url.searchParams.delete('url');
|
|
102
|
-
url.hash = '';
|
|
103
|
-
replaceState(url.pathname + url.search, {});
|
|
102
|
+
return new URL(window.location.href).searchParams.has('url');
|
|
104
103
|
}
|
|
105
104
|
catch {
|
|
106
|
-
|
|
105
|
+
return false;
|
|
107
106
|
}
|
|
108
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Clear all URL state params.
|
|
110
|
+
*/
|
|
111
|
+
export function clearUrlState() {
|
|
112
|
+
writeLocation((url) => {
|
|
113
|
+
url.searchParams.delete('url');
|
|
114
|
+
url.hash = '';
|
|
115
|
+
});
|
|
116
|
+
}
|
package/dist/utils/url.d.ts
CHANGED
|
@@ -4,38 +4,26 @@ import type { Tab } from '../types.js';
|
|
|
4
4
|
* Works for any viewer that needs an HTTP-accessible URL (COG, PMTiles, Zarr, etc.)
|
|
5
5
|
*/
|
|
6
6
|
export declare function buildHttpsUrl(tab: Tab): string;
|
|
7
|
-
/**
|
|
8
|
-
* Map provider to its native URI scheme prefix.
|
|
9
|
-
* Derived from the registry's `schemes` array (first entry is the primary scheme).
|
|
10
|
-
* Falls back to 's3' for providers without a scheme (S3-compatible).
|
|
11
|
-
*/
|
|
12
|
-
export declare function getNativeScheme(provider: string): string;
|
|
13
7
|
/**
|
|
14
8
|
* Build a provider-native protocol URL (s3://bucket/path, sj://bucket/path, etc.).
|
|
15
9
|
*/
|
|
16
10
|
export declare function buildStorageUrl(tab: Tab): string;
|
|
17
11
|
/**
|
|
18
|
-
* Build the URL
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* -
|
|
23
|
-
* -
|
|
12
|
+
* Build the URL DuckDB should query. Derived from the connection's access mode:
|
|
13
|
+
*
|
|
14
|
+
* | Access mode | DuckDB URL | Why |
|
|
15
|
+
* |-----------------|-----------------------------------|-------------------------------------------|
|
|
16
|
+
* | `sas-https` | HTTPS with SAS token | No DuckDB Azure support; SAS in URL works |
|
|
17
|
+
* | `public-https` | HTTPS (no auth) | httpfs fetches directly, no signing needed|
|
|
18
|
+
* | `signed-s3` | `s3://bucket/key` | DuckDB signs with configured S3 settings |
|
|
19
|
+
*
|
|
20
|
+
* Path is percent-decoded so DuckDB's httpfs doesn't double-encode
|
|
21
|
+
* (e.g. Arabic filenames `%D9%85` → `%25D9%2585`).
|
|
24
22
|
*/
|
|
25
23
|
export declare function buildDuckDbUrl(tab: Tab): string;
|
|
26
24
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
25
|
+
* True when any HTTP client (fetch/img/video/deck.gl/COG/Zarr/PMTiles) can
|
|
26
|
+
* load the tab's file directly via its HTTPS URL. False when SigV4 signing
|
|
27
|
+
* is required and the viewer must go through the storage adapter instead.
|
|
30
28
|
*/
|
|
31
29
|
export declare function canStreamDirectly(tab: Tab): boolean;
|
|
32
|
-
/**
|
|
33
|
-
* Convert a cloud storage protocol URL (s3://, gs://) to an HTTPS URL
|
|
34
|
-
* for browser access. Returns the original URL if already HTTP(S) or unknown.
|
|
35
|
-
*
|
|
36
|
-
* Supported:
|
|
37
|
-
* - `s3://bucket/key` → `https://s3.{region}.amazonaws.com/{bucket}/{key}`
|
|
38
|
-
* (region auto-detected from bucket name when possible, e.g. "us-west-2.opendata.source.coop")
|
|
39
|
-
* - `gs://bucket/key` → `https://storage.googleapis.com/{bucket}/{key}`
|
|
40
|
-
*/
|
|
41
|
-
export declare function resolveCloudUrl(url: string): string;
|