@maplat/tin
Version:
JavaScript library which performs homeomorphic conversion mutually between the coordinate systems of two planes based on the control points.
115 lines (95 loc) • 3.54 kB
text/typescript
import type {
PropertyTriKey,
TinsBD,
Tri,
WeightBufferBD,
} from "@maplat/transform";
interface WeightBufferOptions {
tins: TinsBD;
targets: Array<keyof TinsBD>;
includeReciprocals: boolean;
/** Number of boundary vertices (b0..b{N-1}). Defaults to 4 for v2 format. */
numBoundaryVertices?: number;
}
/**
* Calculate per-point stretch ratios used for non-linear interpolation.
*/
export function buildPointsWeightBuffer(
options: WeightBufferOptions,
): WeightBufferBD {
const { tins, targets, includeReciprocals, numBoundaryVertices = 4 } = options;
const edgeRatios: WeightBufferBD = {};
targets.forEach((target) => {
const triCollection = tins[target];
if (!triCollection || !triCollection.features) return;
edgeRatios[target as keyof WeightBufferBD] = {};
const seenEdges: Record<string, boolean> = {};
triCollection.features.forEach((tri: Tri) => {
const props = ["a", "b", "c"] as PropertyTriKey[];
for (let i = 0; i < 3; i++) {
const j = (i + 1) % 3;
const prop_i = props[i];
const prop_j = props[j];
const index_i = tri.properties![prop_i].index;
const index_j = tri.properties![prop_j].index;
const key = [index_i, index_j].sort().join("-");
if (seenEdges[key]) continue;
seenEdges[key] = true;
const i_xy = tri.geometry!.coordinates[0][i];
const j_xy = tri.geometry!.coordinates[0][j];
const i_merc = tri.properties![prop_i].geom;
const j_merc = tri.properties![prop_j].geom;
const ratio = Math.sqrt(
Math.pow(i_merc[0] - j_merc[0], 2) +
Math.pow(i_merc[1] - j_merc[1], 2),
) / Math.sqrt(
Math.pow(i_xy[0] - j_xy[0], 2) + Math.pow(i_xy[1] - j_xy[1], 2),
);
const targetBuffer = edgeRatios[target as keyof WeightBufferBD]!;
targetBuffer[`${index_i}:${key}`] = ratio;
targetBuffer[`${index_j}:${key}`] = ratio;
}
});
});
const pointsWeightBuffer: WeightBufferBD = {};
if (includeReciprocals) {
pointsWeightBuffer.bakw = {};
}
targets.forEach((target) => {
const targetBuffer = edgeRatios[target as keyof WeightBufferBD];
pointsWeightBuffer[target as keyof WeightBufferBD] = {};
if (!targetBuffer) {
return;
}
const pointWeights: Record<string, number[]> = {};
Object.keys(targetBuffer).forEach((key) => {
const [pointId] = key.split(":");
if (!pointWeights[pointId]) {
pointWeights[pointId] = [];
}
pointWeights[pointId].push(targetBuffer[key]);
});
Object.keys(pointWeights).forEach((pointId) => {
const weights = pointWeights[pointId];
const avgWeight = weights.reduce((sum, w) => sum + w, 0) / weights.length;
pointsWeightBuffer[target as keyof WeightBufferBD]![pointId] = avgWeight;
if (includeReciprocals && pointsWeightBuffer.bakw) {
pointsWeightBuffer.bakw[pointId] = 1 / avgWeight;
}
});
let centroidSum = 0;
for (let i = 0; i < numBoundaryVertices; i++) {
const key = `b${i}`;
const weight =
pointsWeightBuffer[target as keyof WeightBufferBD]![key] || 0;
centroidSum += weight;
}
pointsWeightBuffer[target as keyof WeightBufferBD]!["c"] =
centroidSum / numBoundaryVertices;
if (includeReciprocals && pointsWeightBuffer.bakw) {
pointsWeightBuffer.bakw["c"] = 1 /
pointsWeightBuffer[target as keyof WeightBufferBD]!["c"];
}
});
return pointsWeightBuffer;
}