@maplat/transform
Version:
A JavaScript library that performs coordinate transformation between two plane coordinate systems using transformation definitions generated by Maplat.
214 lines (200 loc) • 5.95 kB
text/typescript
import { featureCollection, point } from "@turf/turf";
import type { Position } from "geojson";
import { normalizeEdges } from "./edgeutils.ts";
import { indexesToTri, normalizeNodeKey } from "./triangulation.ts";
import type { PropertyTriKey } from "./geometry.ts";
import type {
BiDirectionKey,
Compiled,
CompiledLegacy,
LegacyStatePayload,
ModernStatePayload,
PointSet,
StrictStatus,
TinsBD,
VerticesParamsBD,
WeightBufferBD
} from "./types.ts";
import type { WeightBuffer } from "./geometry.ts";
export const FORMAT_VERSION = 2.00703;
export const FORMAT_VERSION_V3 = 3;
/**
* Type guard for discriminating modern compiled payloads.
*/
export function isModernCompiled(
compiled: Compiled | CompiledLegacy
): compiled is Compiled {
return Boolean(
compiled.version !== undefined ||
(!(compiled as CompiledLegacy).tins && compiled.points && compiled.tins_points)
);
}
/**
* Restore the in-memory state produced by modern compiled payloads.
*/
export function restoreModernState(compiled: Compiled): ModernStatePayload {
return {
points: compiled.points,
pointsWeightBuffer: normalizeWeightBuffer(compiled),
strictStatus: deriveStrictStatus(compiled),
verticesParams: buildVerticesParams(compiled),
centroid: buildCentroid(compiled),
edges: normalizeEdges(compiled.edges || []),
edgeNodes: compiled.edgeNodes || [],
tins: buildTins(compiled),
kinks: buildKinks(compiled.kinks_points),
yaxisMode: compiled.yaxisMode ?? "invert",
strictMode: compiled.strictMode ?? "auto",
vertexMode: compiled.vertexMode,
bounds: compiled.bounds,
boundsPolygon: compiled.boundsPolygon,
wh: compiled.wh,
xy: compiled.xy ?? [0, 0]
};
}
/**
* Restore the in-memory state produced by the legacy payloads.
*/
export function restoreLegacyState(
rawCompiled: CompiledLegacy
): LegacyStatePayload {
const normalized = normalizeLegacyStructure(rawCompiled);
const tins = normalized.tins!;
return {
compiled: normalized,
tins,
points: rebuildLegacyPoints(tins),
strictStatus: normalized.strict_status,
pointsWeightBuffer: normalized.weight_buffer,
verticesParams: normalized.vertices_params as VerticesParamsBD,
centroid: normalized.centroid,
kinks: normalized.kinks
};
}
function normalizeWeightBuffer(compiled: Compiled): WeightBufferBD {
if (!compiled.version || compiled.version < FORMAT_VERSION) {
return (["forw", "bakw"] as BiDirectionKey[]).reduce((bd, forb) => {
const base = compiled.weight_buffer[forb];
if (base) {
bd[forb] = Object.keys(base).reduce((buffer, key) => {
const normKey = normalizeNodeKey(key);
buffer[normKey] = base[key];
return buffer;
}, {} as WeightBuffer);
}
return bd;
}, {} as WeightBufferBD);
}
return compiled.weight_buffer;
}
function deriveStrictStatus(compiled: Compiled): StrictStatus {
if (compiled.strict_status) return compiled.strict_status;
if (compiled.kinks_points) return "strict_error";
if (compiled.tins_points.length === 2) return "loose";
return "strict";
}
function buildVerticesParams(compiled: Compiled): VerticesParamsBD {
const params: VerticesParamsBD = {
forw: [compiled.vertices_params[0]],
bakw: [compiled.vertices_params[1]]
};
params.forw![1] = buildVertexTins(compiled, false);
params.bakw![1] = buildVertexTins(compiled, true);
return params;
}
function buildVertexTins(compiled: Compiled, bakw: boolean) {
const N = compiled.vertices_points.length;
return Array.from({ length: N }, (_, idx) => {
const idxNxt = (idx + 1) % N;
const tri = indexesToTri(
["c", `b${idx}`, `b${idxNxt}`],
compiled.points,
compiled.edgeNodes || [],
compiled.centroid_point,
compiled.vertices_points,
bakw,
FORMAT_VERSION
);
return featureCollection([tri]);
});
}
function buildCentroid(compiled: Compiled) {
return {
forw: point(compiled.centroid_point[0], {
target: {
geom: compiled.centroid_point[1],
index: "c"
}
}),
bakw: point(compiled.centroid_point[1], {
target: {
geom: compiled.centroid_point[0],
index: "c"
}
})
};
}
function buildTins(compiled: Compiled) {
const bakwIndex = compiled.tins_points.length === 1 ? 0 : 1;
return {
forw: featureCollection(
compiled.tins_points[0].map(idxes =>
indexesToTri(
idxes,
compiled.points,
compiled.edgeNodes || [],
compiled.centroid_point,
compiled.vertices_points,
false,
compiled.version
)
)
),
bakw: featureCollection(
compiled.tins_points[bakwIndex].map(idxes =>
indexesToTri(
idxes,
compiled.points,
compiled.edgeNodes || [],
compiled.centroid_point,
compiled.vertices_points,
true,
compiled.version
)
)
)
};
}
function buildKinks(kinksPoints?: Position[]) {
if (!kinksPoints) return undefined;
return {
bakw: featureCollection(
kinksPoints.map((coord: Position) => point(coord))
)
};
}
function normalizeLegacyStructure(
compiled: CompiledLegacy
): CompiledLegacy {
return JSON.parse(
JSON.stringify(compiled)
.replace('"cent"', '"c"')
.replace(/"bbox(\d+)"/g, '"b$1"')
);
}
function rebuildLegacyPoints(tins: TinsBD): PointSet[] {
const points: PointSet[] = [];
const features = tins.forw!.features;
for (let i = 0; i < features.length; i++) {
const tri = features[i];
(["a", "b", "c"] as PropertyTriKey[]).forEach((key, idx) => {
const forw = tri.geometry!.coordinates[0][idx];
const bakw = tri.properties![key].geom;
const pIdx = tri.properties![key].index;
if (typeof pIdx === "number") {
points[pIdx] = [forw, bakw];
}
});
}
return points;
}