@maplat/tin
Version:
JavaScript library which performs homeomorphic conversion mutually between the coordinate systems of two planes based on the control points.
1,028 lines (905 loc) • 30.2 kB
text/typescript
/**
* Tin (Triangulated Irregular Network) クラス
* 2つの平面座標系間の同相変換を実現します。
*/
import booleanPointInPolygon from "@turf/boolean-point-in-polygon";
import turfCentroid from "@turf/centroid";
import convex from "@turf/convex";
import { featureCollection, lineString, point, polygon } from "@turf/helpers";
import lineIntersect from "@turf/line-intersect";
import type { Feature, Point, Position } from "geojson";
import {
counterTri,
format_version,
normalizeEdges,
rotateVerticesTriangle,
Transform,
transformArr,
} from "@maplat/transform";
// Ensure format_version is available
const FALLBACK_FORMAT_VERSION = 2.00703;
const safeFormatVersion = typeof format_version !== "undefined"
? format_version
: FALLBACK_FORMAT_VERSION;
import type {
Compiled,
Edge,
EdgeSet,
EdgeSetLegacy,
PointSet,
PropertyTriKey,
StrictMode,
Tins,
TinsBD,
Tri,
VertexMode,
WeightBufferBD,
YaxisMode,
} from "@maplat/transform";
import constrainedTin from "./constrained-tin.ts";
import findIntersections from "./kinks.ts";
import { insertSearchIndex } from "./searchutils.ts";
import { counterPoint, createPoint, vertexCalc } from "./vertexutils.ts";
import type { SearchIndex } from "./searchutils.ts";
import type { PointsSetBD } from "./types/tin.d.ts";
/**
* Tinクラスの初期化オプション
*/
export interface Options {
bounds?: Position[];
wh?: number[];
vertexMode?: VertexMode;
strictMode?: StrictMode;
yaxisMode?: YaxisMode;
importance?: number;
priority?: number;
stateFull?: boolean;
points?: PointSet[];
edges?: EdgeSet[];
}
/**
* Tin (Triangulated Irregular Network) クラス
* Transformクラスを拡張し、TINネットワークの生成機能を追加
*/
export class Tin extends Transform {
importance: number;
priority: number;
pointsSet: PointsSetBD | undefined;
/**
* Tinクラスのインスタンスを生成します
* @param options - 初期化オプション
*/
constructor(options: Options = {}) {
super();
if (options.bounds) {
this.setBounds(options.bounds);
} else {
this.setWh(options.wh);
this.vertexMode = options.vertexMode || Tin.VERTEX_PLAIN;
}
this.strictMode = options.strictMode || Tin.MODE_AUTO;
this.yaxisMode = options.yaxisMode || Tin.YAXIS_INVERT;
this.importance = options.importance || 0;
this.priority = options.priority || 0;
this.stateFull = options.stateFull || false;
if (options.points) {
this.setPoints(options.points);
}
if (options.edges) {
this.setEdges(options.edges);
}
}
/**
* フォーマットバージョンを取得します
*/
getFormatVersion(): number {
return safeFormatVersion;
}
/**
* 制御点(GCP: Ground Control Points)を設定します
*/
setPoints(points: PointSet[]): void {
if (this.yaxisMode === Tin.YAXIS_FOLLOW) {
points = points.map((point) => [
point[0],
[point[1][0], -1 * point[1][1]],
]);
}
this.points = points;
this.tins = undefined;
this.indexedTins = undefined;
}
/**
* エッジ(制約線)を設定します
*/
setEdges(edges: EdgeSet[] | EdgeSetLegacy[] = []): void {
this.edges = normalizeEdges(edges);
this.edgeNodes = undefined;
this.tins = undefined;
this.indexedTins = undefined;
}
/**
* 境界ポリゴンを設定します
*/
setBounds(bounds: Position[]): void {
this.bounds = bounds;
let minx = bounds[0][0];
let maxx = minx;
let miny = bounds[0][1];
let maxy = miny;
const coords = [bounds[0]];
for (let i = 1; i < bounds.length; i++) {
const bound = bounds[i];
if (bound[0] < minx) minx = bound[0];
if (bound[0] > maxx) maxx = bound[0];
if (bound[1] < miny) miny = bound[1];
if (bound[1] > maxy) maxy = bound[1];
coords.push(bound);
}
coords.push(bounds[0]);
this.boundsPolygon = polygon([coords]);
this.xy = [minx, miny];
this.wh = [maxx - minx, maxy - miny];
this.vertexMode = Tin.VERTEX_PLAIN;
this.tins = undefined;
this.indexedTins = undefined;
}
/**
* 現在の設定を永続化可能な形式にコンパイルします
*/
getCompiled(): Compiled {
const compiled: any = {};
compiled.version = safeFormatVersion;
compiled.points = this.points;
compiled.weight_buffer = this.pointsWeightBuffer;
compiled.centroid_point = [
this.centroid!.forw!.geometry!.coordinates,
this.centroid!.forw!.properties!.target.geom,
];
compiled.vertices_params = [
this.vertices_params!.forw![0],
this.vertices_params!.bakw![0],
];
compiled.vertices_points = [];
const vertices = this.vertices_params!.forw![1];
if (vertices) {
[0, 1, 2, 3].map((i) => {
const vertex = vertices[i].features[0];
const forw = vertex.geometry!.coordinates[0][1];
const bakw = vertex.properties!.b.geom;
compiled.vertices_points[i] = [forw, bakw];
});
}
compiled.strict_status = this.strict_status;
compiled.tins_points = [[]];
this.tins!.forw!.features.map((tin: Tri) => {
compiled.tins_points[0].push(
(["a", "b", "c"] as PropertyTriKey[]).map((key) =>
tin.properties![key].index
),
);
});
if (this.strict_status === Tin.STATUS_LOOSE) {
compiled.tins_points[1] = [];
this.tins!.bakw!.features.map((tin: Tri) => {
compiled.tins_points[1].push(
(["a", "b", "c"] as PropertyTriKey[]).map((key) =>
tin.properties![key].index
),
);
});
} else if (this.strict_status === Tin.STATUS_ERROR && this.kinks?.bakw) {
compiled.kinks_points = this.kinks.bakw.features.map(
(kink) => kink.geometry!.coordinates,
);
}
compiled.yaxisMode = this.yaxisMode;
compiled.vertexMode = this.vertexMode;
compiled.strictMode = this.strictMode;
if (this.bounds) {
compiled.bounds = this.bounds;
compiled.boundsPolygon = this.boundsPolygon;
compiled.xy = this.xy;
compiled.wh = this.wh;
} else {
compiled.wh = this.wh;
}
compiled.edges = this.edges;
compiled.edgeNodes = this.edgeNodes;
return compiled;
}
/**
* 幅と高さを設定します
*/
setWh(wh?: number[]): void {
this.wh = wh || [100, 100]; // デフォルト値を設定
this.xy = [0, 0];
this.bounds = undefined;
this.boundsPolygon = undefined;
this.tins = undefined;
this.indexedTins = undefined;
}
/**
* 頂点モードを設定します
*/
setVertexMode(mode: VertexMode): void {
this.vertexMode = mode;
this.tins = undefined;
this.indexedTins = undefined;
}
/**
* 厳密性モードを設定します
*/
setStrictMode(mode: StrictMode): void {
this.strictMode = mode;
this.tins = undefined;
this.indexedTins = undefined;
}
/**
* 厳密なTINを計算します
*/
calcurateStrictTin(): void {
const bakTins = this.tins!.forw!.features.map((tri: Tri) =>
counterTri(tri)
);
this.tins!.bakw = featureCollection(bakTins);
const searchIndex: SearchIndex = {};
this.tins!.forw!.features.forEach((forTri: Tri, index: number) => {
const bakTri = this.tins!.bakw!.features[index];
insertSearchIndex(searchIndex, { forw: forTri, bakw: bakTri });
});
const kinks = ["forw", "bakw"].map((direction) => {
const tins = this.tins![direction as keyof TinsBD]!.features.map(
(tin: Tri) => tin.geometry!.coordinates[0],
);
return findIntersections(tins);
});
if (kinks[0].length === 0 && kinks[1].length === 0) {
this.strict_status = Tin.STATUS_STRICT;
delete this.kinks;
} else {
this.strict_status = Tin.STATUS_ERROR;
this.kinks = {};
if (kinks[0].length > 0) {
this.kinks.forw = featureCollection(kinks[0]);
}
if (kinks[1].length > 0) {
this.kinks.bakw = featureCollection(kinks[1]);
}
}
}
/**
* 点群セットを生成します
*/
generatePointsSet(): {
forw: Feature<Point>[];
bakw: Feature<Point>[];
edges: Edge[];
} {
const pointsSet: { forw: Feature<Point>[]; bakw: Feature<Point>[] } = {
forw: [],
bakw: [],
};
// Generate points
for (let i = 0; i < this.points.length; i++) {
const forw = this.points[i][0];
const bakw = this.points[i][1];
const forPoint = createPoint(forw, bakw, i);
pointsSet.forw.push(forPoint);
pointsSet.bakw.push(counterPoint(forPoint));
}
// Generate edge nodes
const edges: Edge[] = [];
let edgeNodeIndex = 0;
this.edgeNodes = [];
if (!this.edges) this.edges = [];
for (let i = 0; i < this.edges.length; i++) {
const edge = this.edges[i][2];
const illstNodes = Object.assign([], this.edges[i][0]);
const mercNodes = Object.assign([], this.edges[i][1]);
if (illstNodes.length === 0 && mercNodes.length === 0) {
edges.push(edge);
continue;
}
// Add start and end points
illstNodes.unshift(this.points[edge[0]][0]);
illstNodes.push(this.points[edge[1]][0]);
mercNodes.unshift(this.points[edge[0]][1]);
mercNodes.push(this.points[edge[1]][1]);
// Calculate edge segments
const segments = [illstNodes, mercNodes].map((nodes) => {
const lengths = nodes.map((node, index, arr) => {
if (index === 0) return 0;
const prev = arr[index - 1];
return Math.sqrt(
Math.pow(node[0] - prev[0], 2) + Math.pow(node[1] - prev[1], 2),
);
});
const accumLengths = lengths.reduce((acc, len, idx) => {
if (idx === 0) return [0];
acc.push(acc[idx - 1] + len);
return acc;
}, [] as number[]);
return accumLengths.map((accum, idx, arr) => {
const ratio = accum / arr[arr.length - 1];
return [nodes[idx], lengths[idx], accumLengths[idx], ratio];
});
});
// Generate edge nodes
segments
.map((segment, idx) => {
const otherSegment = segments[idx ? 0 : 1];
return segment
.filter((item, index) => {
return !(
index === 0 ||
index === segment.length - 1 ||
(item as any)[4] === "handled"
);
})
.map((item) => {
const node = item[0] as unknown as Position;
const ratio = item[3] as number;
const counterpart = otherSegment.reduce(
(prev, curr, currIdx, arr) => {
if (prev) return prev;
const nextItem = arr[currIdx + 1];
if ((curr[3] as number) === ratio) {
(curr as any)[4] = "handled";
return [curr];
}
if (
(curr[3] as number) < ratio && nextItem &&
(nextItem[3] as number) > ratio
) {
return [curr, nextItem];
}
return undefined;
},
undefined as any,
);
if (counterpart && counterpart.length === 1) {
return idx === 0
? [node, counterpart[0][0], ratio]
: [counterpart[0][0], node, ratio];
}
if (counterpart && counterpart.length === 2) {
const curr = counterpart[0];
const next = counterpart[1];
const ratioInSegment = (ratio - (curr[3] as number)) /
((next[3] as number) - (curr[3] as number));
const interpNode: Position = [
((next[0] as Position)[0] - (curr[0] as Position)[0]) *
ratioInSegment + (curr[0] as Position)[0],
((next[0] as Position)[1] - (curr[0] as Position)[1]) *
ratioInSegment + (curr[0] as Position)[1],
];
return idx === 0
? [node, interpNode, ratio]
: [interpNode, node, ratio];
}
return [];
});
})
.reduce((prev, curr) => prev.concat(curr), [])
.sort((a, b) => (a[2] as number) < (b[2] as number) ? -1 : 1)
.map((item, index, arr) => {
this.edgeNodes![edgeNodeIndex] = [
item[0] as Position,
item[1] as Position,
];
const forPoint = createPoint(
item[0] as Position,
item[1] as Position,
`e${edgeNodeIndex}`,
);
edgeNodeIndex++;
pointsSet.forw!.push(forPoint);
pointsSet.bakw!.push(counterPoint(forPoint));
if (index === 0) {
edges.push([edge[0], pointsSet.forw!.length - 1]);
} else {
edges.push([
pointsSet.forw!.length - 2,
pointsSet.forw!.length - 1,
]);
}
if (index === arr.length - 1) {
edges.push([pointsSet.forw!.length - 1, edge[1]]);
}
});
}
return {
forw: pointsSet.forw,
bakw: pointsSet.bakw,
edges,
};
}
/**
* 入力データの検証と初期データの準備
*/
private validateAndPrepareInputs() {
const minx = this.xy![0] - 0.05 * this.wh![0];
const maxx = this.xy![0] + 1.05 * this.wh![0];
const miny = this.xy![1] - 0.05 * this.wh![1];
const maxy = this.xy![1] + 1.05 * this.wh![1];
const allPointsInside = this.points.reduce((prev, point) => {
return prev &&
(this.bounds
? booleanPointInPolygon(point[0] as any, this.boundsPolygon!)
: point[0][0] >= minx && point[0][0] <= maxx &&
point[0][1] >= miny && point[0][1] <= maxy);
}, true);
if (!allPointsInside) {
throw "SOME POINTS OUTSIDE";
}
let bbox: Position[] = [];
if (this.wh) {
bbox = [[minx, miny], [maxx, miny], [minx, maxy], [maxx, maxy]];
}
return {
pointsSet: this.generatePointsSet(),
bbox,
minx,
maxx,
miny,
maxy,
};
}
/**
* TINネットワークを更新し、座標変換の準備を行います
*/
updateTin(): void {
let strict = this.strictMode;
if (strict !== Tin.MODE_STRICT && strict !== Tin.MODE_LOOSE) {
strict = Tin.MODE_AUTO;
}
const { pointsSet: rawPointsSet, bbox, minx, maxx, miny, maxy } = this
.validateAndPrepareInputs();
// Create FeatureCollections for use in calculations
const pointsSetFC = {
forw: featureCollection(rawPointsSet.forw),
bakw: featureCollection(rawPointsSet.bakw),
};
const tinForw = constrainedTin(
pointsSetFC.forw,
rawPointsSet.edges,
"target",
);
const tinBakw = constrainedTin(
pointsSetFC.bakw,
rawPointsSet.edges,
"target",
);
if (tinForw.features.length === 0 || tinBakw.features.length === 0) {
throw "TOO LINEAR1";
}
const forCentroid = turfCentroid(pointsSetFC.forw);
const forwConvex = convex(pointsSetFC.forw);
if (!forwConvex) throw "TOO LINEAR2";
const convexBuf: Record<string, { forw: Position; bakw: Position }> = {};
const forwCoords = forwConvex.geometry!.coordinates[0];
// Calculate forward convex hull transformation
let convexCalc;
try {
convexCalc = forwCoords.map((coord: Position) => ({
forw: coord,
bakw: transformArr(point(coord), tinForw as any) as Position,
}));
convexCalc.forEach((item: any) => {
convexBuf[`${item.forw[0]}:${item.forw[1]}`] = item;
});
} catch {
throw "TOO LINEAR2";
}
// Calculate backward convex hull transformation
const bakwConvex = convex(pointsSetFC.bakw);
if (!bakwConvex) throw "TOO LINEAR2";
const bakwCoords = bakwConvex.geometry!.coordinates[0];
try {
convexCalc = bakwCoords.map((coord: Position) => ({
bakw: coord,
forw: transformArr(point(coord), tinBakw as any) as Position,
}));
convexCalc.forEach((item: any) => {
convexBuf[`${item.forw[0]}:${item.forw[1]}`] = item;
});
} catch {
throw "TOO LINEAR2";
}
// Set centroids
const centCalc = {
forw: forCentroid.geometry!.coordinates,
bakw: transformArr(forCentroid, tinForw as any) as Position,
};
const centroidPoint = createPoint(centCalc.forw, centCalc.bakw, "c");
this.centroid = {
forw: centroidPoint,
bakw: counterPoint(centroidPoint),
};
// Calculate vertices
const verticesSet = this.vertexMode === Tin.VERTEX_BIRDEYE
? this.calculateBirdeyeVertices(
convexBuf,
centCalc,
bbox,
minx,
maxx,
miny,
maxy,
)
: this.calculatePlainVertices(
convexBuf,
centCalc,
bbox,
minx,
maxx,
miny,
maxy,
);
// Add vertices to points set
const verticesList = {
forw: [] as Feature<Point>[],
bakw: [] as Feature<Point>[],
};
for (let i = 0; i < verticesSet.length; i++) {
const forw = verticesSet[i].forw;
const bakw = verticesSet[i].bakw;
const forPoint = createPoint(forw, bakw, `b${i}`);
const bakPoint = counterPoint(forPoint);
rawPointsSet.forw.push(forPoint);
rawPointsSet.bakw.push(bakPoint);
verticesList.forw.push(forPoint);
verticesList.bakw.push(bakPoint);
}
this.pointsSet = {
forw: featureCollection(rawPointsSet.forw),
bakw: featureCollection(rawPointsSet.bakw),
edges: [],
};
// Generate forward TIN
this.tins = {
forw: rotateVerticesTriangle(
constrainedTin(
this.pointsSet!.forw,
rawPointsSet.edges,
"target",
) as Tins,
),
};
// Calculate strict TIN if needed
if (strict === Tin.MODE_STRICT || strict === Tin.MODE_AUTO) {
this.calcurateStrictTin();
}
// Generate backward TIN if needed
if (
strict === Tin.MODE_LOOSE ||
(strict === Tin.MODE_AUTO && this.strict_status === Tin.STATUS_ERROR)
) {
this.tins!.bakw = rotateVerticesTriangle(
constrainedTin(
this.pointsSet!.bakw,
rawPointsSet.edges,
"target",
) as Tins,
);
delete this.kinks;
this.strict_status = Tin.STATUS_LOOSE;
}
// Calculate vertices parameters
this.vertices_params = {
forw: vertexCalc(verticesList.forw, this.centroid.forw!),
bakw: vertexCalc(verticesList.bakw, this.centroid.bakw!),
};
this.addIndexedTin();
this.calculatePointsWeight();
}
/**
* 通常の頂点を計算
*/
private calculatePlainVertices(
convexBuf: Record<string, { forw: Position; bakw: Position }>,
centCalc: { forw: Position; bakw: Position },
bbox: Position[],
minx: number,
maxx: number,
miny: number,
maxy: number,
): Array<{ forw: Position; bakw: Position }> {
// Calculate edge vertices
const edgeNodes = Object.keys(convexBuf).reduce((prev, key) => {
const item = convexBuf[key];
const forw = item.forw;
const bakw = item.bakw;
const vec = {
forw: [forw[0] - centCalc.forw[0], forw[1] - centCalc.forw[1]],
bakw: [bakw[0] - centCalc.bakw[0], bakw[1] - centCalc.bakw[1]],
};
const xRate = vec.forw[0] === 0
? Infinity
: ((vec.forw[0] < 0 ? minx : maxx) - centCalc.forw[0]) / vec.forw[0];
const yRate = vec.forw[1] === 0
? Infinity
: ((vec.forw[1] < 0 ? miny : maxy) - centCalc.forw[1]) / vec.forw[1];
if (Math.abs(xRate) / Math.abs(yRate) < 1.1) {
const node = {
forw: [
vec.forw[0] * xRate + centCalc.forw[0],
vec.forw[1] * xRate + centCalc.forw[1],
] as Position,
bakw: [
vec.bakw[0] * xRate + centCalc.bakw[0],
vec.bakw[1] * xRate + centCalc.bakw[1],
] as Position,
};
if (vec.forw[0] < 0) {
prev[3].push(node);
} else {
prev[1].push(node);
}
}
if (Math.abs(yRate) / Math.abs(xRate) < 1.1) {
const node = {
forw: [
vec.forw[0] * yRate + centCalc.forw[0],
vec.forw[1] * yRate + centCalc.forw[1],
] as Position,
bakw: [
vec.bakw[0] * yRate + centCalc.bakw[0],
vec.bakw[1] * yRate + centCalc.bakw[1],
] as Position,
};
if (vec.forw[1] < 0) {
prev[0].push(node);
} else {
prev[2].push(node);
}
}
return prev;
}, [[], [], [], []] as Array<Array<{ forw: Position; bakw: Position }>>);
// Calculate vertex angles
const vertexCalc = Object.keys(convexBuf).reduce(
(prev, key, index, arr) => {
const item = convexBuf[key];
const forw = item.forw;
const bakw = item.bakw;
const vec = {
forw: [forw[0] - centCalc.forw[0], forw[1] - centCalc.forw[1]],
bakw: [bakw[0] - centCalc.bakw[0], centCalc.bakw[1] - bakw[1]],
};
if (vec.forw[0] === 0 || vec.forw[1] === 0) return prev;
let quad = 0;
if (vec.forw[0] > 0) quad += 1;
if (vec.forw[1] > 0) quad += 2;
prev[quad].push([vec.forw, vec.bakw]);
if (index === arr.length - 1) {
return prev.map((quadArr) => {
return quadArr.reduce(
(
qprev: number[] | undefined,
curr: number[][],
qindex: number,
qarr: number[][][],
) => {
if (!qprev) qprev = [Infinity, 0, 0];
let ratio =
Math.sqrt(Math.pow(curr[0][0], 2) + Math.pow(curr[0][1], 2)) /
Math.sqrt(Math.pow(curr[1][0], 2) + Math.pow(curr[1][1], 2));
ratio = ratio < qprev[0] ? ratio : qprev[0];
const theta = Math.atan2(curr[0][0], curr[0][1]) -
Math.atan2(curr[1][0], curr[1][1]);
const sumcos = qprev[1] + Math.cos(theta);
const sumsin = qprev[2] + Math.sin(theta);
if (qindex === qarr.length - 1) {
return [ratio, Math.atan2(sumsin, sumcos)];
}
return [ratio, sumcos, sumsin];
},
null as any,
);
});
}
return prev;
},
[[], [], [], []] as any[],
);
let vertexRatio: any[] = vertexCalc;
if (vertexRatio.length === 1) {
vertexRatio = [
vertexRatio[0],
vertexRatio[0],
vertexRatio[0],
vertexRatio[0],
];
}
// Calculate vertices
const vertices = vertexRatio.map((ratio: any, index: number) => {
const forVertex = bbox[index];
const forDelta = [
forVertex[0] - centCalc.forw[0],
forVertex[1] - centCalc.forw[1],
];
const forDistance = Math.sqrt(
Math.pow(forDelta[0], 2) + Math.pow(forDelta[1], 2),
);
const bakDistance = forDistance / ratio[0];
const bakTheta = Math.atan2(forDelta[0], forDelta[1]) - ratio[1];
const bakw: Position = [
centCalc.bakw[0] + bakDistance * Math.sin(bakTheta),
centCalc.bakw[1] - bakDistance * Math.cos(bakTheta),
];
return { forw: forVertex, bakw };
});
// Swap vertices 2 and 3 for correct ordering
const swap = vertices[2];
vertices[2] = vertices[3];
vertices[3] = swap;
// Check edge intersections
this.checkAndAdjustVertices(vertices, edgeNodes, centCalc);
return vertices;
}
/**
* 鳥瞰図モードの頂点を計算
*/
private calculateBirdeyeVertices(
convexBuf: Record<string, { forw: Position; bakw: Position }>,
centCalc: { forw: Position; bakw: Position },
bbox: Position[],
minx: number,
maxx: number,
miny: number,
maxy: number,
): Array<{ forw: Position; bakw: Position }> {
// For birdeye mode, implement special vertex calculation
// This is a simplified version, actual implementation might be more complex
return this.calculatePlainVertices(
convexBuf,
centCalc,
bbox,
minx,
maxx,
miny,
maxy,
);
}
/**
* 頂点の位置を調整
*/
private checkAndAdjustVertices(
vertices: Array<{ forw: Position; bakw: Position }>,
edgeNodes: Array<Array<{ forw: Position; bakw: Position }>>,
centCalc: { forw: Position; bakw: Position },
): void {
const expandRatio = [1, 1, 1, 1];
for (let i = 0; i < 4; i++) {
const j = (i + 1) % 4;
const side = lineString([vertices[i].bakw, vertices[j].bakw]);
edgeNodes[i].map((node) => {
const line = lineString([centCalc.bakw, node.bakw]);
const intersect = lineIntersect(side, line);
if (intersect.features.length > 0 && intersect.features[0].geometry) {
const intersectPt = intersect.features[0];
const distance = Math.sqrt(
Math.pow(node.bakw[0] - centCalc.bakw[0], 2) +
Math.pow(node.bakw[1] - centCalc.bakw[1], 2),
);
const intDistance = Math.sqrt(
Math.pow(
intersectPt.geometry.coordinates[0] - centCalc.bakw[0],
2,
) +
Math.pow(
intersectPt.geometry.coordinates[1] - centCalc.bakw[1],
2,
),
);
const ratio = distance / intDistance;
if (ratio > expandRatio[i]) expandRatio[i] = ratio;
if (ratio > expandRatio[j]) expandRatio[j] = ratio;
}
});
}
vertices.forEach((vertex, i) => {
const ratio = expandRatio[i];
const bakw: Position = [
(vertex.bakw[0] - centCalc.bakw[0]) * ratio + centCalc.bakw[0],
(vertex.bakw[1] - centCalc.bakw[1]) * ratio + centCalc.bakw[1],
];
vertex.bakw = bakw;
});
}
/**
* 点の重み付けを計算します
*/
calculatePointsWeight(): void {
const calcTargets = ["forw"];
if (this.strict_status === Tin.STATUS_LOOSE) {
calcTargets.push("bakw");
}
const weightBuffer: WeightBufferBD = {};
calcTargets.forEach((target) => {
weightBuffer[target as keyof WeightBufferBD] = {};
const tin_points: Record<string, number> = {};
this.tins![target as keyof TinsBD]!.features.map((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 (!tin_points[key]) {
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;
tin_points[key] = 1;
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),
);
if (!weightBuffer[target as keyof WeightBufferBD]) {
weightBuffer[target as keyof WeightBufferBD] = {};
}
const targetBuffer = weightBuffer[target as keyof WeightBufferBD]!;
targetBuffer[`${index_i}:${key}`] = ratio;
targetBuffer[`${index_j}:${key}`] = ratio;
}
}
});
});
// Calculate average weights
const pointsWeightBuffer: WeightBufferBD = {};
calcTargets.map((target) => {
pointsWeightBuffer[target as keyof WeightBufferBD] = {};
if (this.strict_status === Tin.STATUS_STRICT) {
pointsWeightBuffer.bakw = {};
}
const targetBuffer = weightBuffer[target as keyof WeightBufferBD]!;
const pointWeights: { [pointId: string]: number[] } = {};
// Group weights by point ID
Object.keys(targetBuffer).forEach((key) => {
const [pointId] = key.split(":");
if (!pointWeights[pointId]) {
pointWeights[pointId] = [];
}
pointWeights[pointId].push(targetBuffer[key]);
});
// Calculate average weight for each point
Object.keys(pointWeights).forEach((pointId) => {
const weights = pointWeights[pointId];
const avgWeight = weights.reduce((sum, w) => sum + w, 0) /
weights.length;
if (!pointsWeightBuffer[target as keyof WeightBufferBD]) {
pointsWeightBuffer[target as keyof WeightBufferBD] = {};
}
pointsWeightBuffer[target as keyof WeightBufferBD]![pointId] =
avgWeight;
if (this.strict_status === Tin.STATUS_STRICT) {
if (!pointsWeightBuffer.bakw) {
pointsWeightBuffer.bakw = {};
}
pointsWeightBuffer.bakw[pointId] = 1 / avgWeight;
}
});
// Calculate centroid weight
let centroidSum = 0;
for (let i = 0; i < 4; i++) {
const key = `b${i}`;
const weight =
pointsWeightBuffer[target as keyof WeightBufferBD]![key] || 0;
centroidSum += weight;
}
pointsWeightBuffer[target as keyof WeightBufferBD]!["c"] = centroidSum /
4;
if (this.strict_status === Tin.STATUS_STRICT && pointsWeightBuffer.bakw) {
pointsWeightBuffer.bakw["c"] = 1 /
pointsWeightBuffer[target as keyof WeightBufferBD]!["c"];
}
});
this.pointsWeightBuffer = pointsWeightBuffer;
}
}