-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathconverter.js
More file actions
125 lines (118 loc) · 3.99 KB
/
Copy pathconverter.js
File metadata and controls
125 lines (118 loc) · 3.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/**
* Conversion controller (main thread)
*
* Spawns the converter worker, forwards a conversion request, streams status
* updates back to the caller, and triggers the file download when done.
* Cancellation is implemented by terminating the worker.
*/
import ConverterWorker from './converter.worker.js?worker';
export const FORMATS = [
{
id: 'geojson',
label: 'GeoJSON (FeatureCollection, EPSG:4326)',
ext: 'geojson',
geoCapable: true,
requiresGeo: true
},
{ id: 'csv', label: 'CSV (geometry as WKT EPSG:4326)', ext: 'csv', geoCapable: false },
{ id: 'json', label: 'NDJSON (geometry as WKT EPSG:4326)', ext: 'ndjson', geoCapable: false }
];
/**
* Start a conversion. Returns a handle with `cancel()` and `promise`.
*
* @param {Object} opts
* @param {string} opts.source - Source identifier (URL or registered file name).
* @param {ArrayBuffer|null} opts.sourceBuffer - Local file buffer (when source is local).
* @param {string} opts.format - Target format id.
* @param {string} opts.outputName - Suggested output filename (without extension).
* @param {Array} opts.schema - Schema columns ({name, type, ...}).
* @param {Array<string>} opts.geoColumns - Names of geometry columns.
* @param {string|null} opts.primaryGeoColumn - Primary geometry column name.
* @param {string|null} opts.encoding - Geometry encoding ('wkb', 'wkt', or 'native geoArrow').
* @param {string|null} opts.sourceCrs - Source CRS as PROJJSON string (null = WGS84).
* @param {Function} opts.onStatus - status(message) callback.
* @returns {{ promise: Promise<{filename:string}>, cancel: Function }}
*/
export function startConversion({
source,
sourceBuffer = null,
format,
outputName,
schema,
geoColumns = [],
primaryGeoColumn = null,
encoding = null,
sourceCrs = null,
onStatus = () => {}
}) {
const worker = new ConverterWorker();
let cancelled = false;
let rejectFn;
const promise = new Promise((resolve, reject) => {
rejectFn = reject;
worker.onmessage = (ev) => {
const m = ev.data;
if (!m) return;
if (m.type === 'status') {
onStatus(m.message);
} else if (m.type === 'done') {
try {
const blob = new Blob([m.buffer], { type: m.mime });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
const filename = `${outputName}.${m.ext}`;
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
// Revoke later to give the browser time to start the download.
setTimeout(() => URL.revokeObjectURL(url), 60_000);
worker.terminate();
resolve({ filename });
} catch (e) {
worker.terminate();
reject(e);
}
} else if (m.type === 'error') {
worker.terminate();
reject(new Error(m.error));
}
};
worker.onerror = (e) => {
if (cancelled) return;
worker.terminate();
reject(new Error(e?.message || 'Worker error'));
};
// Strip Vue reactive Proxies before structured-cloning into the worker.
// JSON round-trip is the simplest way to get plain serializable values.
const plainSchema = JSON.parse(JSON.stringify(schema ?? []));
const plainGeoColumns = JSON.parse(JSON.stringify(geoColumns ?? []));
// Fire the request. Transfer the buffer when present to avoid a copy.
const transfer = sourceBuffer ? [sourceBuffer] : [];
worker.postMessage(
{
type: 'convert',
source,
encoding,
sourceBuffer,
sourceName: sourceBuffer ? `convert_src_${Date.now()}.parquet` : null,
format,
schema: plainSchema,
geoColumns: plainGeoColumns,
primaryGeoColumn,
sourceCrs
},
transfer
);
});
return {
promise,
cancel() {
if (cancelled) return;
cancelled = true;
worker.terminate();
rejectFn(new DOMException('Conversion cancelled', 'AbortError'));
}
};
}