UNPKG

@loaders.gl/json

Version:

Framework-independent loader for JSON and streaming JSON formats

1,554 lines (1,539 loc) 77.7 kB
"use strict"; (() => { var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => { __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; // ../worker-utils/src/lib/node/worker_threads-browser.ts var parentPort = null; // ../worker-utils/src/lib/worker-utils/get-transfer-list.ts function getTransferList(object, recursive = true, transfers) { const transfersSet = transfers || /* @__PURE__ */ new Set(); if (!object) { } else if (isTransferable(object)) { transfersSet.add(object); } else if (isTransferable(object.buffer)) { transfersSet.add(object.buffer); } else if (ArrayBuffer.isView(object)) { } else if (recursive && typeof object === "object") { for (const key in object) { getTransferList(object[key], recursive, transfersSet); } } return transfers === void 0 ? Array.from(transfersSet) : []; } function isTransferable(object) { if (!object) { return false; } if (object instanceof ArrayBuffer) { return true; } if (typeof MessagePort !== "undefined" && object instanceof MessagePort) { return true; } if (typeof ImageBitmap !== "undefined" && object instanceof ImageBitmap) { return true; } if (typeof OffscreenCanvas !== "undefined" && object instanceof OffscreenCanvas) { return true; } return false; } // ../worker-utils/src/lib/worker-farm/worker-body.ts async function getParentPort() { return parentPort; } var onMessageWrapperMap = /* @__PURE__ */ new Map(); var WorkerBody = class { /** Check that we are actually in a worker thread */ static async inWorkerThread() { return typeof self !== "undefined" || Boolean(await getParentPort()); } /* * (type: WorkerMessageType, payload: WorkerMessagePayload) => any */ static set onmessage(onMessage) { async function handleMessage(message) { const parentPort2 = await getParentPort(); const { type, payload } = parentPort2 ? message : message.data; onMessage(type, payload); } getParentPort().then((parentPort2) => { if (parentPort2) { parentPort2.on("message", (message) => { handleMessage(message); }); parentPort2.on("exit", () => console.debug("Node worker closing")); } else { globalThis.onmessage = handleMessage; } }); } static async addEventListener(onMessage) { let onMessageWrapper = onMessageWrapperMap.get(onMessage); if (!onMessageWrapper) { onMessageWrapper = async (message) => { if (!isKnownMessage(message)) { return; } const parentPort3 = await getParentPort(); const { type, payload } = parentPort3 ? message : message.data; onMessage(type, payload); }; } const parentPort2 = await getParentPort(); if (parentPort2) { console.error("not implemented"); } else { globalThis.addEventListener("message", onMessageWrapper); } } static async removeEventListener(onMessage) { const onMessageWrapper = onMessageWrapperMap.get(onMessage); onMessageWrapperMap.delete(onMessage); const parentPort2 = await getParentPort(); if (parentPort2) { console.error("not implemented"); } else { globalThis.removeEventListener("message", onMessageWrapper); } } /** * Send a message from a worker to creating thread (main thread) * @param type * @param payload */ static async postMessage(type, payload) { const data = { source: "loaders.gl", type, payload }; const transferList = getTransferList(payload); const parentPort2 = await getParentPort(); if (parentPort2) { parentPort2.postMessage(data, transferList); } else { globalThis.postMessage(data, transferList); } } }; function isKnownMessage(message) { const { type, data } = message; return type === "message" && data && typeof data.source === "string" && data.source.startsWith("loaders.gl"); } // ../loader-utils/src/lib/worker-loader-utils/create-loader-worker.ts var requestId = 0; async function createLoaderWorker(loader) { if (!await WorkerBody.inWorkerThread()) { return; } WorkerBody.onmessage = async (type, payload) => { switch (type) { case "process": try { const { input, options = {}, context = {} } = payload; const result = await parseData({ loader, arrayBuffer: input, options, // @ts-expect-error fetch missing context: { ...context, _parse: parseOnMainThread } }); WorkerBody.postMessage("done", { result }); } catch (error) { const message = error instanceof Error ? error.message : ""; WorkerBody.postMessage("error", { error: message }); } break; default: } }; } function parseOnMainThread(arrayBuffer, loader, options, context) { return new Promise((resolve, reject) => { const id = requestId++; const onMessage = (type, payload2) => { if (payload2.id !== id) { return; } switch (type) { case "done": WorkerBody.removeEventListener(onMessage); resolve(payload2.result); break; case "error": WorkerBody.removeEventListener(onMessage); reject(payload2.error); break; default: } }; WorkerBody.addEventListener(onMessage); const payload = { id, input: arrayBuffer, options }; WorkerBody.postMessage("process", payload); }); } async function parseData({ loader, arrayBuffer, options, context }) { let data; let parser; if (loader.parseSync || loader.parse) { data = arrayBuffer; parser = loader.parseSync || loader.parse; } else if (loader.parseTextSync) { const textDecoder = new TextDecoder(); data = textDecoder.decode(arrayBuffer); parser = loader.parseTextSync; } else { throw new Error(`Could not load data with ${loader.name} loader`); } options = { ...options, modules: loader && loader.options && loader.options.modules || {}, worker: false }; return await parser(data, { ...options }, context, loader); } // ../loader-utils/src/lib/iterators/text-iterators.ts async function* makeTextDecoderIterator(arrayBufferIterator, options = {}) { const textDecoder = new TextDecoder(void 0, options); for await (const arrayBuffer of arrayBufferIterator) { yield typeof arrayBuffer === "string" ? arrayBuffer : textDecoder.decode(arrayBuffer, { stream: true }); } } // ../schema/src/lib/table/batches/base-table-batch-aggregator.ts var DEFAULT_ROW_COUNT = 100; var BaseTableBatchAggregator = class { schema; options; shape; length = 0; rows = null; cursor = 0; _headers = []; constructor(schema, options) { this.options = options; this.schema = schema; if (!Array.isArray(schema)) { this._headers = []; for (const key in schema) { this._headers[schema[key].index] = schema[key].name; } } } rowCount() { return this.length; } addArrayRow(row, cursor) { if (Number.isFinite(cursor)) { this.cursor = cursor; } this.shape = "array-row-table"; this.rows = this.rows || new Array(DEFAULT_ROW_COUNT); this.rows[this.length] = row; this.length++; } addObjectRow(row, cursor) { if (Number.isFinite(cursor)) { this.cursor = cursor; } this.shape = "object-row-table"; this.rows = this.rows || new Array(DEFAULT_ROW_COUNT); this.rows[this.length] = row; this.length++; } getBatch() { let rows = this.rows; if (!rows) { return null; } rows = rows.slice(0, this.length); this.rows = null; const batch = { shape: this.shape || "array-row-table", batchType: "data", data: rows, length: this.length, schema: this.schema, cursor: this.cursor }; return batch; } }; // ../schema/src/lib/table/simple-table/row-utils.ts function convertToObjectRow(arrayRow, headers) { if (!arrayRow) { throw new Error("null row"); } const objectRow = {}; if (headers) { for (let i = 0; i < headers.length; i++) { objectRow[headers[i]] = arrayRow[i]; } } else { for (let i = 0; i < arrayRow.length; i++) { const columnName = `column-${i}`; objectRow[columnName] = arrayRow[i]; } } return objectRow; } function convertToArrayRow(objectRow, headers) { if (!objectRow) { throw new Error("null row"); } if (headers) { const arrayRow = new Array(headers.length); for (let i = 0; i < headers.length; i++) { arrayRow[i] = objectRow[headers[i]]; } return arrayRow; } return Object.values(objectRow); } function inferHeadersFromArrayRow(arrayRow) { const headers = []; for (let i = 0; i < arrayRow.length; i++) { const columnName = `column-${i}`; headers.push(columnName); } return headers; } function inferHeadersFromObjectRow(row) { return Object.keys(row); } // ../schema/src/lib/table/batches/row-table-batch-aggregator.ts var DEFAULT_ROW_COUNT2 = 100; var RowTableBatchAggregator = class { schema; options; length = 0; objectRows = null; arrayRows = null; cursor = 0; _headers = null; constructor(schema, options) { this.options = options; this.schema = schema; if (schema) { this._headers = []; for (const key in schema) { this._headers[schema[key].index] = schema[key].name; } } } rowCount() { return this.length; } addArrayRow(row, cursor) { if (Number.isFinite(cursor)) { this.cursor = cursor; } this._headers ||= inferHeadersFromArrayRow(row); switch (this.options.shape) { case "object-row-table": const rowObject = convertToObjectRow(row, this._headers); this.addObjectRow(rowObject, cursor); break; case "array-row-table": this.arrayRows = this.arrayRows || new Array(DEFAULT_ROW_COUNT2); this.arrayRows[this.length] = row; this.length++; break; } } addObjectRow(row, cursor) { if (Number.isFinite(cursor)) { this.cursor = cursor; } this._headers ||= inferHeadersFromObjectRow(row); switch (this.options.shape) { case "array-row-table": const rowArray = convertToArrayRow(row, this._headers); this.addArrayRow(rowArray, cursor); break; case "object-row-table": this.objectRows = this.objectRows || new Array(DEFAULT_ROW_COUNT2); this.objectRows[this.length] = row; this.length++; break; } } getBatch() { let rows = this.arrayRows || this.objectRows; if (!rows) { return null; } rows = rows.slice(0, this.length); this.arrayRows = null; this.objectRows = null; return { shape: this.options.shape, batchType: "data", data: rows, length: this.length, // @ts-expect-error we should infer a schema schema: this.schema, cursor: this.cursor }; } }; // ../schema/src/lib/table/batches/columnar-table-batch-aggregator.ts var DEFAULT_ROW_COUNT3 = 100; var ColumnarTableBatchAggregator = class { schema; length = 0; allocated = 0; columns = {}; constructor(schema, options) { this.schema = schema; this._reallocateColumns(); } rowCount() { return this.length; } addArrayRow(row) { this._reallocateColumns(); let i = 0; for (const fieldName in this.columns) { this.columns[fieldName][this.length] = row[i++]; } this.length++; } addObjectRow(row) { this._reallocateColumns(); for (const fieldName in row) { this.columns[fieldName][this.length] = row[fieldName]; } this.length++; } getBatch() { this._pruneColumns(); const columns = Array.isArray(this.schema) ? this.columns : {}; if (!Array.isArray(this.schema)) { for (const fieldName in this.schema) { const field = this.schema[fieldName]; columns[field.name] = this.columns[field.index]; } } this.columns = {}; const batch = { shape: "columnar-table", batchType: "data", data: columns, schema: this.schema, length: this.length }; return batch; } // HELPERS _reallocateColumns() { if (this.length < this.allocated) { return; } this.allocated = this.allocated > 0 ? this.allocated *= 2 : DEFAULT_ROW_COUNT3; this.columns = {}; for (const fieldName in this.schema) { const field = this.schema[fieldName]; const ArrayType = field.type || Float32Array; const oldColumn = this.columns[field.index]; if (oldColumn && ArrayBuffer.isView(oldColumn)) { const typedArray = new ArrayType(this.allocated); typedArray.set(oldColumn); this.columns[field.index] = typedArray; } else if (oldColumn) { oldColumn.length = this.allocated; this.columns[field.index] = oldColumn; } else { this.columns[field.index] = new ArrayType(this.allocated); } } } _pruneColumns() { for (const [columnName, column] of Object.entries(this.columns)) { this.columns[columnName] = column.slice(0, this.length); } } }; // ../schema/src/lib/table/batches/table-batch-builder.ts var DEFAULT_OPTIONS = { shape: void 0, batchSize: "auto", batchDebounceMs: 0, limit: 0, _limitMB: 0 }; var ERR_MESSAGE = "TableBatchBuilder"; var _TableBatchBuilder = class { schema; options; aggregator = null; batchCount = 0; bytesUsed = 0; isChunkComplete = false; lastBatchEmittedMs = Date.now(); totalLength = 0; totalBytes = 0; rowBytes = 0; constructor(schema, options) { this.schema = schema; this.options = { ...DEFAULT_OPTIONS, ...options }; } limitReached() { if (Boolean(this.options?.limit) && this.totalLength >= this.options.limit) { return true; } if (Boolean(this.options?._limitMB) && this.totalBytes / 1e6 >= this.options._limitMB) { return true; } return false; } /** @deprecated Use addArrayRow or addObjectRow */ addRow(row) { if (this.limitReached()) { return; } this.totalLength++; this.rowBytes = this.rowBytes || this._estimateRowMB(row); this.totalBytes += this.rowBytes; if (Array.isArray(row)) { this.addArrayRow(row); } else { this.addObjectRow(row); } } /** Add one row to the batch */ addArrayRow(row) { if (!this.aggregator) { const TableBatchType = this._getTableBatchType(); this.aggregator = new TableBatchType(this.schema, this.options); } this.aggregator.addArrayRow(row); } /** Add one row to the batch */ addObjectRow(row) { if (!this.aggregator) { const TableBatchType = this._getTableBatchType(); this.aggregator = new TableBatchType(this.schema, this.options); } this.aggregator.addObjectRow(row); } /** Mark an incoming raw memory chunk has completed */ chunkComplete(chunk) { if (chunk instanceof ArrayBuffer) { this.bytesUsed += chunk.byteLength; } if (typeof chunk === "string") { this.bytesUsed += chunk.length; } this.isChunkComplete = true; } getFullBatch(options) { return this._isFull() ? this._getBatch(options) : null; } getFinalBatch(options) { return this._getBatch(options); } // INTERNAL _estimateRowMB(row) { return Array.isArray(row) ? row.length * 8 : Object.keys(row).length * 8; } _isFull() { if (!this.aggregator || this.aggregator.rowCount() === 0) { return false; } if (this.options.batchSize === "auto") { if (!this.isChunkComplete) { return false; } } else if (this.options.batchSize > this.aggregator.rowCount()) { return false; } if (this.options.batchDebounceMs > Date.now() - this.lastBatchEmittedMs) { return false; } this.isChunkComplete = false; this.lastBatchEmittedMs = Date.now(); return true; } /** * bytesUsed can be set via chunkComplete or via getBatch* */ _getBatch(options) { if (!this.aggregator) { return null; } if (options?.bytesUsed) { this.bytesUsed = options.bytesUsed; } const normalizedBatch = this.aggregator.getBatch(); normalizedBatch.count = this.batchCount; normalizedBatch.bytesUsed = this.bytesUsed; Object.assign(normalizedBatch, options); this.batchCount++; this.aggregator = null; return normalizedBatch; } _getTableBatchType() { switch (this.options.shape) { case "array-row-table": case "object-row-table": return RowTableBatchAggregator; case "columnar-table": return ColumnarTableBatchAggregator; case "arrow-table": if (!_TableBatchBuilder.ArrowBatch) { throw new Error(ERR_MESSAGE); } return _TableBatchBuilder.ArrowBatch; default: return BaseTableBatchAggregator; } } }; var TableBatchBuilder = _TableBatchBuilder; __publicField(TableBatchBuilder, "ArrowBatch"); // ../../node_modules/@math.gl/polygon/dist/polygon-utils.js var DimIndex = { x: 0, y: 1, z: 2 }; function getPolygonSignedArea(points, options = {}) { const { start = 0, end = points.length, plane = "xy" } = options; const dim = options.size || 2; let area2 = 0; const i0 = DimIndex[plane[0]]; const i1 = DimIndex[plane[1]]; for (let i = start, j = end - dim; i < end; i += dim) { area2 += (points[i + i0] - points[j + i0]) * (points[i + i1] + points[j + i1]); j = i; } return area2 / 2; } // ../../node_modules/@math.gl/polygon/dist/earcut.js function earcut(positions, holeIndices, dim = 2, areas, plane = "xy") { const hasHoles = holeIndices && holeIndices.length; const outerLen = hasHoles ? holeIndices[0] * dim : positions.length; let outerNode = linkedList(positions, 0, outerLen, dim, true, areas && areas[0], plane); const triangles = []; if (!outerNode || outerNode.next === outerNode.prev) return triangles; let invSize; let maxX; let maxY; let minX; let minY; let x; let y; if (hasHoles) outerNode = eliminateHoles(positions, holeIndices, outerNode, dim, areas, plane); if (positions.length > 80 * dim) { minX = maxX = positions[0]; minY = maxY = positions[1]; for (let i = dim; i < outerLen; i += dim) { x = positions[i]; y = positions[i + 1]; if (x < minX) minX = x; if (y < minY) minY = y; if (x > maxX) maxX = x; if (y > maxY) maxY = y; } invSize = Math.max(maxX - minX, maxY - minY); invSize = invSize !== 0 ? 32767 / invSize : 0; } earcutLinked(outerNode, triangles, dim, minX, minY, invSize, 0); return triangles; } function linkedList(data, start, end, dim, clockwise, area2, plane) { let i; let last; if (area2 === void 0) { area2 = getPolygonSignedArea(data, { start, end, size: dim, plane }); } let i0 = DimIndex[plane[0]]; let i1 = DimIndex[plane[1]]; if (clockwise === area2 < 0) { for (i = start; i < end; i += dim) last = insertNode(i, data[i + i0], data[i + i1], last); } else { for (i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i + i0], data[i + i1], last); } if (last && equals(last, last.next)) { removeNode(last); last = last.next; } return last; } function filterPoints(start, end) { if (!start) return start; if (!end) end = start; let p = start; let again; do { again = false; if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) { removeNode(p); p = end = p.prev; if (p === p.next) break; again = true; } else { p = p.next; } } while (again || p !== end); return end; } function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) { if (!ear) return; if (!pass && invSize) indexCurve(ear, minX, minY, invSize); let stop = ear; let prev; let next; while (ear.prev !== ear.next) { prev = ear.prev; next = ear.next; if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) { triangles.push(prev.i / dim | 0); triangles.push(ear.i / dim | 0); triangles.push(next.i / dim | 0); removeNode(ear); ear = next.next; stop = next.next; continue; } ear = next; if (ear === stop) { if (!pass) { earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1); } else if (pass === 1) { ear = cureLocalIntersections(filterPoints(ear), triangles, dim); earcutLinked(ear, triangles, dim, minX, minY, invSize, 2); } else if (pass === 2) { splitEarcut(ear, triangles, dim, minX, minY, invSize); } break; } } } function isEar(ear) { const a = ear.prev; const b = ear; const c = ear.next; if (area(a, b, c) >= 0) return false; const ax = a.x; const bx = b.x; const cx = c.x; const ay = a.y; const by = b.y; const cy = c.y; const x0 = ax < bx ? ax < cx ? ax : cx : bx < cx ? bx : cx; const y0 = ay < by ? ay < cy ? ay : cy : by < cy ? by : cy; const x1 = ax > bx ? ax > cx ? ax : cx : bx > cx ? bx : cx; const y1 = ay > by ? ay > cy ? ay : cy : by > cy ? by : cy; let p = c.next; while (p !== a) { if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; p = p.next; } return true; } function isEarHashed(ear, minX, minY, invSize) { const a = ear.prev; const b = ear; const c = ear.next; if (area(a, b, c) >= 0) return false; const ax = a.x; const bx = b.x; const cx = c.x; const ay = a.y; const by = b.y; const cy = c.y; const x0 = ax < bx ? ax < cx ? ax : cx : bx < cx ? bx : cx; const y0 = ay < by ? ay < cy ? ay : cy : by < cy ? by : cy; const x1 = ax > bx ? ax > cx ? ax : cx : bx > cx ? bx : cx; const y1 = ay > by ? ay > cy ? ay : cy : by > cy ? by : cy; const minZ = zOrder(x0, y0, minX, minY, invSize); const maxZ = zOrder(x1, y1, minX, minY, invSize); let p = ear.prevZ; let n = ear.nextZ; while (p && p.z >= minZ && n && n.z <= maxZ) { if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; p = p.prevZ; if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && pointInTriangle(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false; n = n.nextZ; } while (p && p.z >= minZ) { if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; p = p.prevZ; } while (n && n.z <= maxZ) { if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && pointInTriangle(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false; n = n.nextZ; } return true; } function cureLocalIntersections(start, triangles, dim) { let p = start; do { const a = p.prev; const b = p.next.next; if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) { triangles.push(a.i / dim | 0); triangles.push(p.i / dim | 0); triangles.push(b.i / dim | 0); removeNode(p); removeNode(p.next); p = start = b; } p = p.next; } while (p !== start); return filterPoints(p); } function splitEarcut(start, triangles, dim, minX, minY, invSize) { let a = start; do { let b = a.next.next; while (b !== a.prev) { if (a.i !== b.i && isValidDiagonal(a, b)) { let c = splitPolygon(a, b); a = filterPoints(a, a.next); c = filterPoints(c, c.next); earcutLinked(a, triangles, dim, minX, minY, invSize, 0); earcutLinked(c, triangles, dim, minX, minY, invSize, 0); return; } b = b.next; } a = a.next; } while (a !== start); } function eliminateHoles(data, holeIndices, outerNode, dim, areas, plane) { const queue = []; let i; let len; let start; let end; let list; for (i = 0, len = holeIndices.length; i < len; i++) { start = holeIndices[i] * dim; end = i < len - 1 ? holeIndices[i + 1] * dim : data.length; list = linkedList(data, start, end, dim, false, areas && areas[i + 1], plane); if (list === list.next) list.steiner = true; queue.push(getLeftmost(list)); } queue.sort(compareX); for (i = 0; i < queue.length; i++) { outerNode = eliminateHole(queue[i], outerNode); } return outerNode; } function compareX(a, b) { return a.x - b.x; } function eliminateHole(hole, outerNode) { const bridge = findHoleBridge(hole, outerNode); if (!bridge) { return outerNode; } const bridgeReverse = splitPolygon(bridge, hole); filterPoints(bridgeReverse, bridgeReverse.next); return filterPoints(bridge, bridge.next); } function findHoleBridge(hole, outerNode) { let p = outerNode; const hx = hole.x; const hy = hole.y; let qx = -Infinity; let m; do { if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) { const x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y); if (x <= hx && x > qx) { qx = x; m = p.x < p.next.x ? p : p.next; if (x === hx) return m; } } p = p.next; } while (p !== outerNode); if (!m) return null; const stop = m; const mx = m.x; const my = m.y; let tanMin = Infinity; let tan; p = m; do { if (hx >= p.x && p.x >= mx && hx !== p.x && pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) { tan = Math.abs(hy - p.y) / (hx - p.x); if (locallyInside(p, hole) && (tan < tanMin || tan === tanMin && (p.x > m.x || p.x === m.x && sectorContainsSector(m, p)))) { m = p; tanMin = tan; } } p = p.next; } while (p !== stop); return m; } function sectorContainsSector(m, p) { return area(m.prev, m, p.prev) < 0 && area(p.next, m, m.next) < 0; } function indexCurve(start, minX, minY, invSize) { let p = start; do { if (p.z === 0) p.z = zOrder(p.x, p.y, minX, minY, invSize); p.prevZ = p.prev; p.nextZ = p.next; p = p.next; } while (p !== start); p.prevZ.nextZ = null; p.prevZ = null; sortLinked(p); } function sortLinked(list) { let e; let i; let inSize = 1; let numMerges; let p; let pSize; let q; let qSize; let tail; do { p = list; list = null; tail = null; numMerges = 0; while (p) { numMerges++; q = p; pSize = 0; for (i = 0; i < inSize; i++) { pSize++; q = q.nextZ; if (!q) break; } qSize = inSize; while (pSize > 0 || qSize > 0 && q) { if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) { e = p; p = p.nextZ; pSize--; } else { e = q; q = q.nextZ; qSize--; } if (tail) tail.nextZ = e; else list = e; e.prevZ = tail; tail = e; } p = q; } tail.nextZ = null; inSize *= 2; } while (numMerges > 1); return list; } function zOrder(x, y, minX, minY, invSize) { x = (x - minX) * invSize | 0; y = (y - minY) * invSize | 0; x = (x | x << 8) & 16711935; x = (x | x << 4) & 252645135; x = (x | x << 2) & 858993459; x = (x | x << 1) & 1431655765; y = (y | y << 8) & 16711935; y = (y | y << 4) & 252645135; y = (y | y << 2) & 858993459; y = (y | y << 1) & 1431655765; return x | y << 1; } function getLeftmost(start) { let p = start; let leftmost = start; do { if (p.x < leftmost.x || p.x === leftmost.x && p.y < leftmost.y) leftmost = p; p = p.next; } while (p !== start); return leftmost; } function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) { return (cx - px) * (ay - py) >= (ax - px) * (cy - py) && (ax - px) * (by - py) >= (bx - px) * (ay - py) && (bx - px) * (cy - py) >= (cx - px) * (by - py); } function isValidDiagonal(a, b) { return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // dones't intersect other edges (locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible (area(a.prev, a, b.prev) || area(a, b.prev, b)) || // does not create opposite-facing sectors equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0); } function area(p, q, r) { return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); } function equals(p1, p2) { return p1.x === p2.x && p1.y === p2.y; } function intersects(p1, q1, p2, q2) { const o1 = sign(area(p1, q1, p2)); const o2 = sign(area(p1, q1, q2)); const o3 = sign(area(p2, q2, p1)); const o4 = sign(area(p2, q2, q1)); if (o1 !== o2 && o3 !== o4) return true; if (o1 === 0 && onSegment(p1, p2, q1)) return true; if (o2 === 0 && onSegment(p1, q2, q1)) return true; if (o3 === 0 && onSegment(p2, p1, q2)) return true; if (o4 === 0 && onSegment(p2, q1, q2)) return true; return false; } function onSegment(p, q, r) { return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y); } function sign(num) { return num > 0 ? 1 : num < 0 ? -1 : 0; } function intersectsPolygon(a, b) { let p = a; do { if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && intersects(p, p.next, a, b)) return true; p = p.next; } while (p !== a); return false; } function locallyInside(a, b) { return area(a.prev, a, a.next) < 0 ? area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 : area(a, b, a.prev) < 0 || area(a, a.next, b) < 0; } function middleInside(a, b) { let p = a; let inside = false; const px = (a.x + b.x) / 2; const py = (a.y + b.y) / 2; do { if (p.y > py !== p.next.y > py && p.next.y !== p.y && px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x) inside = !inside; p = p.next; } while (p !== a); return inside; } function splitPolygon(a, b) { const a2 = new Vertex(a.i, a.x, a.y); const b2 = new Vertex(b.i, b.x, b.y); const an = a.next; const bp = b.prev; a.next = b; b.prev = a; a2.next = an; an.prev = a2; b2.next = a2; a2.prev = b2; bp.next = b2; b2.prev = bp; return b2; } function insertNode(i, x, y, last) { const p = new Vertex(i, x, y); if (!last) { p.prev = p; p.next = p; } else { p.next = last.next; p.prev = last; last.next.prev = p; last.next = p; } return p; } function removeNode(p) { p.next.prev = p.prev; p.prev.next = p.next; if (p.prevZ) p.prevZ.nextZ = p.nextZ; if (p.nextZ) p.nextZ.prevZ = p.prevZ; } var Vertex = class { constructor(i, x, y) { this.prev = null; this.next = null; this.z = 0; this.prevZ = null; this.nextZ = null; this.steiner = false; this.i = i; this.x = x; this.y = y; } }; // ../gis/src/lib/binary-features/flat-geojson-to-binary.ts function flatGeojsonToBinary(features, geometryInfo, options) { const propArrayTypes = extractNumericPropTypes(features); const numericPropKeys = Object.keys(propArrayTypes).filter((k) => propArrayTypes[k] !== Array); return fillArrays( features, { propArrayTypes, ...geometryInfo }, { numericPropKeys: options && options.numericPropKeys || numericPropKeys, PositionDataType: options ? options.PositionDataType : Float32Array, triangulate: options ? options.triangulate : true } ); } function extractNumericPropTypes(features) { const propArrayTypes = {}; for (const feature of features) { if (feature.properties) { for (const key in feature.properties) { const val = feature.properties[key]; propArrayTypes[key] = deduceArrayType(val, propArrayTypes[key]); } } } return propArrayTypes; } function fillArrays(features, geometryInfo, options) { const { pointPositionsCount, pointFeaturesCount, linePositionsCount, linePathsCount, lineFeaturesCount, polygonPositionsCount, polygonObjectsCount, polygonRingsCount, polygonFeaturesCount, propArrayTypes, coordLength } = geometryInfo; const { numericPropKeys = [], PositionDataType = Float32Array, triangulate = true } = options; const hasGlobalId = features[0] && "id" in features[0]; const GlobalFeatureIdsDataType = features.length > 65535 ? Uint32Array : Uint16Array; const points = { type: "Point", positions: new PositionDataType(pointPositionsCount * coordLength), globalFeatureIds: new GlobalFeatureIdsDataType(pointPositionsCount), featureIds: pointFeaturesCount > 65535 ? new Uint32Array(pointPositionsCount) : new Uint16Array(pointPositionsCount), numericProps: {}, properties: [], fields: [] }; const lines = { type: "LineString", pathIndices: linePositionsCount > 65535 ? new Uint32Array(linePathsCount + 1) : new Uint16Array(linePathsCount + 1), positions: new PositionDataType(linePositionsCount * coordLength), globalFeatureIds: new GlobalFeatureIdsDataType(linePositionsCount), featureIds: lineFeaturesCount > 65535 ? new Uint32Array(linePositionsCount) : new Uint16Array(linePositionsCount), numericProps: {}, properties: [], fields: [] }; const polygons = { type: "Polygon", polygonIndices: polygonPositionsCount > 65535 ? new Uint32Array(polygonObjectsCount + 1) : new Uint16Array(polygonObjectsCount + 1), primitivePolygonIndices: polygonPositionsCount > 65535 ? new Uint32Array(polygonRingsCount + 1) : new Uint16Array(polygonRingsCount + 1), positions: new PositionDataType(polygonPositionsCount * coordLength), globalFeatureIds: new GlobalFeatureIdsDataType(polygonPositionsCount), featureIds: polygonFeaturesCount > 65535 ? new Uint32Array(polygonPositionsCount) : new Uint16Array(polygonPositionsCount), numericProps: {}, properties: [], fields: [] }; if (triangulate) { polygons.triangles = []; } for (const object of [points, lines, polygons]) { for (const propName of numericPropKeys) { const T = propArrayTypes[propName]; object.numericProps[propName] = new T(object.positions.length / coordLength); } } lines.pathIndices[linePathsCount] = linePositionsCount; polygons.polygonIndices[polygonObjectsCount] = polygonPositionsCount; polygons.primitivePolygonIndices[polygonRingsCount] = polygonPositionsCount; const indexMap = { pointPosition: 0, pointFeature: 0, linePosition: 0, linePath: 0, lineFeature: 0, polygonPosition: 0, polygonObject: 0, polygonRing: 0, polygonFeature: 0, feature: 0 }; for (const feature of features) { const geometry = feature.geometry; const properties = feature.properties || {}; switch (geometry.type) { case "Point": handlePoint(geometry, points, indexMap, coordLength, properties); points.properties.push(keepStringProperties(properties, numericPropKeys)); if (hasGlobalId) { points.fields.push({ id: feature.id }); } indexMap.pointFeature++; break; case "LineString": handleLineString(geometry, lines, indexMap, coordLength, properties); lines.properties.push(keepStringProperties(properties, numericPropKeys)); if (hasGlobalId) { lines.fields.push({ id: feature.id }); } indexMap.lineFeature++; break; case "Polygon": handlePolygon(geometry, polygons, indexMap, coordLength, properties); polygons.properties.push(keepStringProperties(properties, numericPropKeys)); if (hasGlobalId) { polygons.fields.push({ id: feature.id }); } indexMap.polygonFeature++; break; default: throw new Error("Invalid geometry type"); } indexMap.feature++; } return makeAccessorObjects(points, lines, polygons, coordLength); } function handlePoint(geometry, points, indexMap, coordLength, properties) { points.positions.set(geometry.data, indexMap.pointPosition * coordLength); const nPositions = geometry.data.length / coordLength; fillNumericProperties(points, properties, indexMap.pointPosition, nPositions); points.globalFeatureIds.fill( indexMap.feature, indexMap.pointPosition, indexMap.pointPosition + nPositions ); points.featureIds.fill( indexMap.pointFeature, indexMap.pointPosition, indexMap.pointPosition + nPositions ); indexMap.pointPosition += nPositions; } function handleLineString(geometry, lines, indexMap, coordLength, properties) { lines.positions.set(geometry.data, indexMap.linePosition * coordLength); const nPositions = geometry.data.length / coordLength; fillNumericProperties(lines, properties, indexMap.linePosition, nPositions); lines.globalFeatureIds.fill( indexMap.feature, indexMap.linePosition, indexMap.linePosition + nPositions ); lines.featureIds.fill( indexMap.lineFeature, indexMap.linePosition, indexMap.linePosition + nPositions ); for (let i = 0, il = geometry.indices.length; i < il; ++i) { const start = geometry.indices[i]; const end = i === il - 1 ? geometry.data.length : geometry.indices[i + 1]; lines.pathIndices[indexMap.linePath++] = indexMap.linePosition; indexMap.linePosition += (end - start) / coordLength; } } function handlePolygon(geometry, polygons, indexMap, coordLength, properties) { polygons.positions.set(geometry.data, indexMap.polygonPosition * coordLength); const nPositions = geometry.data.length / coordLength; fillNumericProperties(polygons, properties, indexMap.polygonPosition, nPositions); polygons.globalFeatureIds.fill( indexMap.feature, indexMap.polygonPosition, indexMap.polygonPosition + nPositions ); polygons.featureIds.fill( indexMap.polygonFeature, indexMap.polygonPosition, indexMap.polygonPosition + nPositions ); for (let l = 0, ll = geometry.indices.length; l < ll; ++l) { const startPosition = indexMap.polygonPosition; polygons.polygonIndices[indexMap.polygonObject++] = startPosition; const areas = geometry.areas[l]; const indices = geometry.indices[l]; const nextIndices = geometry.indices[l + 1]; for (let i = 0, il = indices.length; i < il; ++i) { const start = indices[i]; const end = i === il - 1 ? ( // last line, so either read to: nextIndices === void 0 ? geometry.data.length : nextIndices[0] ) : indices[i + 1]; polygons.primitivePolygonIndices[indexMap.polygonRing++] = indexMap.polygonPosition; indexMap.polygonPosition += (end - start) / coordLength; } const endPosition = indexMap.polygonPosition; triangulatePolygon(polygons, areas, indices, { startPosition, endPosition, coordLength }); } } function triangulatePolygon(polygons, areas, indices, { startPosition, endPosition, coordLength }) { if (!polygons.triangles) { return; } const start = startPosition * coordLength; const end = endPosition * coordLength; const polygonPositions = polygons.positions.subarray(start, end); const offset = indices[0]; const holes = indices.slice(1).map((n) => (n - offset) / coordLength); const triangles = earcut(polygonPositions, holes, coordLength, areas); for (let t = 0, tl = triangles.length; t < tl; ++t) { polygons.triangles.push(startPosition + triangles[t]); } } function wrapProps(obj, size) { const returnObj = {}; for (const key in obj) { returnObj[key] = { value: obj[key], size }; } return returnObj; } function makeAccessorObjects(points, lines, polygons, coordLength) { const binaryFeatures = { shape: "binary-feature-collection", points: { ...points, positions: { value: points.positions, size: coordLength }, globalFeatureIds: { value: points.globalFeatureIds, size: 1 }, featureIds: { value: points.featureIds, size: 1 }, numericProps: wrapProps(points.numericProps, 1) }, lines: { ...lines, positions: { value: lines.positions, size: coordLength }, pathIndices: { value: lines.pathIndices, size: 1 }, globalFeatureIds: { value: lines.globalFeatureIds, size: 1 }, featureIds: { value: lines.featureIds, size: 1 }, numericProps: wrapProps(lines.numericProps, 1) }, polygons: { ...polygons, positions: { value: polygons.positions, size: coordLength }, polygonIndices: { value: polygons.polygonIndices, size: 1 }, primitivePolygonIndices: { value: polygons.primitivePolygonIndices, size: 1 }, globalFeatureIds: { value: polygons.globalFeatureIds, size: 1 }, featureIds: { value: polygons.featureIds, size: 1 }, numericProps: wrapProps(polygons.numericProps, 1) } // triangles not expected }; if (binaryFeatures.polygons && polygons.triangles) { binaryFeatures.polygons.triangles = { value: new Uint32Array(polygons.triangles), size: 1 }; } return binaryFeatures; } function fillNumericProperties(object, properties, index, length) { for (const numericPropName in object.numericProps) { if (numericPropName in properties) { const value = properties[numericPropName]; object.numericProps[numericPropName].fill(value, index, index + length); } } } function keepStringProperties(properties, numericKeys) { const props = {}; for (const key in properties) { if (!numericKeys.includes(key)) { props[key] = properties[key]; } } return props; } function deduceArrayType(x, constructor) { if (constructor === Array || !Number.isFinite(x)) { return Array; } return constructor === Float64Array || Math.fround(x) !== x ? Float64Array : Float32Array; } // ../gis/src/lib/binary-features/extract-geometry-info.ts function extractGeometryInfo(features) { let pointPositionsCount = 0; let pointFeaturesCount = 0; let linePositionsCount = 0; let linePathsCount = 0; let lineFeaturesCount = 0; let polygonPositionsCount = 0; let polygonObjectsCount = 0; let polygonRingsCount = 0; let polygonFeaturesCount = 0; const coordLengths = /* @__PURE__ */ new Set(); for (const feature of features) { const geometry = feature.geometry; switch (geometry.type) { case "Point": pointFeaturesCount++; pointPositionsCount++; coordLengths.add(geometry.coordinates.length); break; case "MultiPoint": pointFeaturesCount++; pointPositionsCount += geometry.coordinates.length; for (const point of geometry.coordinates) { coordLengths.add(point.length); } break; case "LineString": lineFeaturesCount++; linePositionsCount += geometry.coordinates.length; linePathsCount++; for (const coord of geometry.coordinates) { coordLengths.add(coord.length); } break; case "MultiLineString": lineFeaturesCount++; for (const line of geometry.coordinates) { linePositionsCount += line.length; linePathsCount++; for (const coord of line) { coordLengths.add(coord.length); } } break; case "Polygon": polygonFeaturesCount++; polygonObjectsCount++; polygonRingsCount += geometry.coordinates.length; const flattened = geometry.coordinates.flat(); polygonPositionsCount += flattened.length; for (const coord of flattened) { coordLengths.add(coord.length); } break; case "MultiPolygon": polygonFeaturesCount++; for (const polygon of geometry.coordinates) { polygonObjectsCount++; polygonRingsCount += polygon.length; const flattened2 = polygon.flat(); polygonPositionsCount += flattened2.length; for (const coord of flattened2) { coordLengths.add(coord.length); } } break; default: throw new Error(`Unsupported geometry type: ${geometry.type}`); } } return { coordLength: coordLengths.size > 0 ? Math.max(...coordLengths) : 2, pointPositionsCount, pointFeaturesCount, linePositionsCount, linePathsCount, lineFeaturesCount, polygonPositionsCount, polygonObjectsCount, polygonRingsCount, polygonFeaturesCount }; } // ../gis/src/lib/binary-features/geojson-to-flat-geojson.ts function geojsonToFlatGeojson(features, options = { coordLength: 2, fixRingWinding: true }) { return features.map((feature) => flattenFeature(feature, options)); } function flattenPoint(coordinates, data, indices, options) { indices.push(data.length); data.push(...coordinates); for (let i = coordinates.length; i < options.coordLength; i++) { data.push(0); } } function flattenLineString(coordinates, data, indices, options) { indices.push(data.length); for (const c of coordinates) { data.push(...c); for (let i = c.length; i < options.coordLength; i++) { data.push(0); } } } function flattenPolygon(coordinates, data, indices, areas, options) { let count = 0; const ringAreas = []; const polygons = []; for (const lineString of coordinates) { const lineString2d = lineString.map((p) => p.slice(0, 2)); let area2 = getPolygonSignedArea(lineString2d.flat()); const ccw = area2 < 0; if (options.fixRingWinding && (count === 0 && !ccw || count > 0 && ccw)) { lineString.reverse(); area2 = -area2; } ringAreas.push(area2); flattenLineString(lineString, data, polygons, options); count++; } if (count > 0) { areas.push(ringAreas); indices.push(polygons); } } function flattenFeature(feature, options) { const { geometry } = feature; if (geometry.type === "GeometryCollection") { throw new Error("GeometryCollection type not supported"); } const data = []; const indices = []; let areas; let type; switch (geometry.type) { case "Point": type = "Point"; flattenPoint(geometry.coordinates, data, indices, options); break; case "MultiPoint": type = "Point"; geometry.coordinates.map((c) => flattenPoint(c, data, indices, options)); break; case "LineString": type = "LineString"; flattenLineString(geometry.coordinates, data, indices, options); break; case "