@loaders.gl/json
Version:
Framework-independent loader for JSON and streaming JSON formats
1,554 lines (1,539 loc) • 77.7 kB
JavaScript
"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 "