@maplat/tin
Version:
JavaScript library which performs homeomorphic conversion mutually between the coordinate systems of two planes based on the control points.
822 lines (738 loc) • 25.8 kB
text/typescript
/**
* Tin (Triangulated Irregular Network) クラス
* 2つの平面座標系間の同相変換を実現します。
*/
import {
booleanPointInPolygon,
centroid as turfCentroid,
convex,
featureCollection,
point,
polygon,
} from "@turf/turf";
import type { Feature, Point, Polygon, Position } from "geojson";
import {
counterTri,
format_version as FORMAT_VERSION_V2,
normalizeEdges,
rotateVerticesTriangle,
Transform,
transformArr,
} from "@maplat/transform";
const FORMAT_VERSION_V3 = 3.00000;
import type {
Compiled,
CompiledLegacy,
Edge,
EdgeSet,
EdgeSetLegacy,
PointSet,
PropertyTriKey,
StrictMode,
Tins,
TinsBD,
Tri,
VertexMode,
YaxisMode,
} from "@maplat/transform";
import constrainedTin from "./constrained-tin.ts";
import {
calculateBirdeyeVertices,
calculatePlainVertices,
type BoundaryVerticesParams,
} from "./boundary-vertices.ts";
import findIntersections from "./kinks.ts";
import { insertSearchIndex } from "./searchutils.ts";
import { counterPoint, createPoint, vertexCalc } from "./vertexutils.ts";
import { buildPointsWeightBuffer } from "./weight-buffer.ts";
import { resolveOverlaps } from "./strict-overlap.ts";
import type { SearchIndex } from "./searchutils.ts";
import type { PointsSetBD, VertexPosition } from "./types/tin.d.ts";
type EdgeSegmentItem = [Position, number, number, number, string?];
type EdgeNodeItem = [Position, Position, number];
/**
* Tinクラスの初期化オプション
*/
export interface Options {
bounds?: Position[];
wh?: number[];
vertexMode?: VertexMode;
strictMode?: StrictMode;
yaxisMode?: YaxisMode;
importance?: number;
priority?: number;
stateFull?: boolean;
points?: PointSet[];
edges?: EdgeSet[];
/** true にすると v2 アルゴリズム(wh bbox・turf 重心・4 境界頂点)で build する */
useV2Algorithm?: boolean;
}
/**
* Tin (Triangulated Irregular Network) クラス
* Transformクラスを拡張し、TINネットワークの生成機能を追加
*/
export class Tin extends Transform {
importance: number;
priority: number;
pointsSet: PointsSetBD | undefined;
useV2Algorithm: boolean;
/**
* 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;
this.useV2Algorithm = options.useV2Algorithm ?? false;
if (options.points) {
this.setPoints(options.points);
}
if (options.edges) {
this.setEdges(options.edges);
}
}
/**
* フォーマットバージョンを取得します
*/
getFormatVersion(): number {
return this.useV2Algorithm ? FORMAT_VERSION_V2 : FORMAT_VERSION_V3;
}
/**
* 制御点(GCP: Ground Control Points)を設定します。
* 指定した点群に合わせて内部のTINキャッシュをリセットします。
*/
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: Compiled = {} as Compiled;
compiled.version = this.useV2Algorithm ? FORMAT_VERSION_V2 : FORMAT_VERSION_V3;
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) {
for (let i = 0; i < vertices.length; 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;
if (this.useV2Algorithm) {
// V2 submap: xy/wh define the bbox used for triangulation — must be preserved.
compiled.xy = this.xy;
compiled.wh = this.wh;
}
// V3 submap: bounds polygon is the envelope; xy/wh are not used for
// triangulation and are therefore not serialized.
} else {
// Main map: wh stores image dimensions for display and V2 bbox calculation.
compiled.wh = this.wh;
}
compiled.edges = this.edges ?? [];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
compiled.edgeNodes = (this.edgeNodes ?? []) as any;
return compiled;
}
/**
* コンパイルされた設定を適用します(v3+フォーマット対応)
*
* バージョン3以上のコンパイル済みデータが渡された場合は restoreV3State() を
* 使用してN頂点対応の復元を行います。それ以外は基底クラスの実装に委譲します。
*/
override setCompiled(compiled: Compiled | CompiledLegacy): void {
super.setCompiled(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 });
});
resolveOverlaps(
this.tins!,
searchIndex,
this.pointsSet?.edges || [],
);
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 = {
forw: featureCollection(kinks[0]),
bakw: featureCollection(kinks[1]),
};
}
}
/**
* 点群セットを生成します。
* GCP と中間エッジノードを GeoJSON Point に変換し、後続の三角分割に備えます。
*/
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: Position[]): EdgeSegmentItem[] => {
const lengths = nodes.map((node: Position, index: number, arr: Position[]) => {
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: number[], len: number, idx: number) => {
if (idx === 0) return [0];
acc.push(acc[idx - 1] + len);
return acc;
}, [] as number[]);
return accumLengths.map((accum: number, idx: number, arr: number[]): EdgeSegmentItem => {
const ratio = accum / arr[arr.length - 1];
return [nodes[idx], lengths[idx], accumLengths[idx], ratio];
});
});
// Generate edge nodes
segments
.map((segment: EdgeSegmentItem[], idx: number) => {
const otherSegment = segments[idx ? 0 : 1];
return segment
.filter((item: EdgeSegmentItem, index: number) => {
return !(
index === 0 ||
index === segment.length - 1 ||
item[4] === "handled"
);
})
.flatMap((item: EdgeSegmentItem): EdgeNodeItem[] => {
const node = item[0];
const ratio = item[3];
const counterpart = otherSegment.reduce(
(prev: EdgeSegmentItem[] | undefined, curr: EdgeSegmentItem, currIdx: number, arr: EdgeSegmentItem[]) => {
if (prev) return prev;
const nextItem = arr[currIdx + 1];
if (curr[3] === ratio) {
curr[4] = "handled";
return [curr];
}
if (
curr[3] < ratio && nextItem &&
nextItem[3] > ratio
) {
return [curr, nextItem];
}
return undefined;
},
undefined as EdgeSegmentItem[] | undefined,
);
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][0] - curr[0][0]) *
ratioInSegment + curr[0][0],
(next[0][1] - curr[0][1]) *
ratioInSegment + curr[0][1],
];
return idx === 0
? [[node, interpNode, ratio]]
: [[interpNode, node, ratio]];
}
return [];
});
})
.reduce((prev: EdgeNodeItem[], curr: EdgeNodeItem[]) => prev.concat(curr), [])
.sort((a: EdgeNodeItem, b: EdgeNodeItem) => a[2] < b[2] ? -1 : 1)
.map((item: EdgeNodeItem, index: number, arr: EdgeNodeItem[]) => {
this.edgeNodes![edgeNodeIndex] = [
item[0],
item[1],
];
const forPoint = createPoint(
item[0],
item[1],
`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];
// Invariant: boundsPolygon is always set when bounds is set (see setBounds / setCompiled).
if (this.bounds && !this.boundsPolygon) throw new Error("Internal error: bounds is set but boundsPolygon is missing");
const bp = this.bounds ? this.boundsPolygon : undefined;
const allPointsInside = this.points.reduce((prev: boolean, point: PointSet) => {
return prev &&
(bp
? booleanPointInPolygon(point[0] as Position, bp)
: 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,
};
}
/**
* Compute a bounding box derived from GCP coordinates with a 5% margin.
* Used in V3 plain mode where no explicit image bounds are available.
*/
private computeGcpBbox(): {
minx: number; maxx: number; miny: number; maxy: number;
} {
let gcpMinx = Infinity, gcpMaxx = -Infinity;
let gcpMiny = Infinity, gcpMaxy = -Infinity;
for (const p of this.points) {
const x = p[0][0] as number, y = p[0][1] as number;
if (x < gcpMinx) gcpMinx = x;
if (x > gcpMaxx) gcpMaxx = x;
if (y < gcpMiny) gcpMiny = y;
if (y > gcpMaxy) gcpMaxy = y;
}
const gcpW = gcpMaxx - gcpMinx;
const gcpH = gcpMaxy - gcpMiny;
return {
minx: gcpMinx - 0.05 * gcpW,
maxx: gcpMaxx + 0.05 * gcpW,
miny: gcpMiny - 0.05 * gcpH,
maxy: gcpMaxy + 0.05 * gcpH,
};
}
/**
* TINネットワークを同期的に更新し、座標変換の準備を行います。
* 重めの計算を伴うため、呼び出し側が非同期制御を行いたい場合は
* {@link updateTinAsync} を利用してください。
*/
updateTin(): void {
let strict = this.strictMode;
if (strict !== Tin.MODE_STRICT && strict !== Tin.MODE_LOOSE) {
strict = Tin.MODE_AUTO;
}
const isV3 = !this.useV2Algorithm;
let rawPointsSet: { forw: Feature<Point>[]; bakw: Feature<Point>[]; edges: Edge[] };
let minx: number, maxx: number, miny: number, maxy: number;
if (isV3) {
// V3 (main map and submap): always derive bbox from GCPs.
// When a bounds polygon (envelope) is provided, validate GCPs are inside it first.
if (this.bounds) {
// Invariant: boundsPolygon is always set when bounds is set (see setBounds / setCompiled).
const bp = this.boundsPolygon;
if (!bp) throw new Error("Internal error: bounds is set but boundsPolygon is missing");
const allInsideBounds = this.points.every(
(p: PointSet) => booleanPointInPolygon(p[0] as Position, bp),
);
if (!allInsideBounds) throw "SOME POINTS OUTSIDE";
}
rawPointsSet = this.generatePointsSet();
({ minx, maxx, miny, maxy } = this.computeGcpBbox());
} else {
// V2: use xy/wh bbox with full validation.
const validated = this.validateAndPrepareInputs();
rawPointsSet = validated.pointsSet;
minx = validated.minx;
maxx = validated.maxx;
miny = validated.miny;
maxy = validated.maxy;
}
// 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 Tins) as Position,
}));
convexCalc.forEach((item: { forw: Position; bakw: Position }) => {
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 Tins) as Position,
}));
convexCalc.forEach((item: { forw: Position; bakw: Position }) => {
convexBuf[`${item.forw[0]}:${item.forw[1]}`] = item;
});
} catch {
throw "TOO LINEAR2";
}
// Set centroids
let centCalc: { forw: Position; bakw: Position };
if (isV3) {
// V3 (main map and submap): find the TIN triangle containing turf's centroid
// and use its geometric centre (mean of 3 vertices) as the new centroid.
const forCentCoord = forCentroid.geometry!.coordinates;
const containingTri = (tinForw as Tins).features.find((tri: Tri) =>
booleanPointInPolygon(
point(forCentCoord),
tri as unknown as Feature<Polygon>,
)
);
if (containingTri) {
const coords = containingTri.geometry!.coordinates[0];
const aGeom = containingTri.properties!.a.geom as Position;
const bGeom = containingTri.properties!.b.geom as Position;
const cGeom = containingTri.properties!.c.geom as Position;
centCalc = {
forw: [
(coords[0][0] + coords[1][0] + coords[2][0]) / 3,
(coords[0][1] + coords[1][1] + coords[2][1]) / 3,
],
bakw: [
(aGeom[0] + bGeom[0] + cGeom[0]) / 3,
(aGeom[1] + bGeom[1] + cGeom[1]) / 3,
],
};
} else {
// Fallback: use turf centroid directly.
centCalc = {
forw: forCentCoord,
bakw: transformArr(forCentroid, tinForw as Tins) as Position,
};
}
} else {
// V2: transform turf centroid through TIN.
centCalc = {
forw: forCentroid.geometry!.coordinates,
bakw: transformArr(forCentroid, tinForw as Tins) as Position,
};
}
const centroidPoint = createPoint(centCalc.forw, centCalc.bakw, "c");
this.centroid = {
forw: centroidPoint,
bakw: counterPoint(centroidPoint),
};
// Calculate vertices
// allGcps includes both GCPs and edge intermediate nodes so that
// checkAndAdjustVerticesN can guarantee all constrained-edge bakw positions
// are enclosed within the boundary polygon.
const allGcps: BoundaryVerticesParams["allGcps"] = [
...this.points.map((p) => ({ forw: p[0] as Position, bakw: p[1] as Position })),
...(this.edgeNodes ?? []).map((n) => ({ forw: n[0] as Position, bakw: n[1] as Position })),
];
const boundaryParams: BoundaryVerticesParams = {
convexBuf,
centroid: centCalc,
allGcps,
minx,
maxx,
miny,
maxy,
};
// V3 enables the 36-bin edge vertex pass for both main maps and submaps.
const verticesSet: VertexPosition[] = this.vertexMode === Tin.VERTEX_BIRDEYE
? calculateBirdeyeVertices(boundaryParams, isV3)
: calculatePlainVertices(boundaryParams, isV3);
// 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: rawPointsSet.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();
const targets: Array<keyof TinsBD> = ["forw"];
if (this.strict_status === Tin.STATUS_LOOSE) {
targets.push("bakw");
}
const includeReciprocals = this.strict_status === Tin.STATUS_STRICT;
this.pointsWeightBuffer = buildPointsWeightBuffer({
tins: this.tins!,
targets,
includeReciprocals,
numBoundaryVertices: verticesSet.length,
});
}
/**
* 非同期ラッパーを提供します。
* 互換性のために Promise ベースの API を維持しますが、内部処理は同期的です。
*/
async updateTinAsync(): Promise<void> {
this.updateTin();
}
}