@maplat/transform 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -1,567 +1,567 @@
1
- import booleanPointInPolygon from "@turf/boolean-point-in-polygon";
2
- import { featureCollection, point } from "@turf/helpers";
3
- import { getCoords } from "@turf/invariant";
4
- import { indexesToTri, normalizeNodeKey } from "./triangulation.ts";
5
- import type { Feature, Polygon, Position, Point, FeatureCollection } from "geojson";
6
- import { normalizeEdges } from "./edgeutils.ts";
7
- import type {
8
- WeightBuffer, Tins, VerticesParams, PropertyTriKey,
9
- IndexedTins, Tri
10
- } from "./geometry.ts";
11
- import { unitCalc, transformArr } from "./geometry.ts";
12
- import type { EdgeSet, EdgeSetLegacy } from "./edgeutils.ts";
13
- export type { Tins, Tri, PropertyTriKey } from './geometry.ts';
14
- export { transformArr } from './geometry.ts';
15
- export { rotateVerticesTriangle, counterTri } from './triangulation.ts';
16
- export type { Edge, EdgeSet, EdgeSetLegacy } from './edgeutils.ts';
17
- export { normalizeEdges } from './edgeutils.ts';
18
-
19
- /**
20
- * 座標ペアの型定義。[ソース座標, ターゲット座標] の形式
21
- */
22
- export type PointSet = [Position, Position];
23
-
24
- /**
25
- * 変換の方向を示す型定義
26
- * - forw: 順方向(ソース → ターゲット)
27
- * - bakw: 逆方向(ターゲット → ソース)
28
- */
29
- export type BiDirectionKey = "forw" | "bakw";
30
-
31
- /**
32
- * 両方向の重み付けバッファの型定義
33
- */
34
- export type WeightBufferBD = { [key in BiDirectionKey]?: WeightBuffer };
35
-
36
- /**
37
- * 頂点モードの型定義
38
- * - plain: 通常モード
39
- * - birdeye: 鳥瞰図モード
40
- */
41
- export type VertexMode = "plain" | "birdeye";
42
-
43
- /**
44
- * 厳密性モードの型定義
45
- * - strict: 厳密モード(交差なしを保証)
46
- * - auto: 自動モード(可能な限り厳密に)
47
- * - loose: 緩和モード(交差を許容)
48
- */
49
- export type StrictMode = "strict" | "auto" | "loose";
50
-
51
- /**
52
- * 厳密性モードの生成結果
53
- * - strict: 厳密モード生成成功
54
- * - auto: 厳密モードでエラー
55
- * - loose: 緩和モード
56
- */
57
- export type StrictStatus = "strict" | "strict_error" | "loose";
58
-
59
- /**
60
- * Y軸の向きの型定義
61
- * - follow: Y軸の向きを維持
62
- * - invert: Y軸の向きを反転
63
- */
64
- export type YaxisMode = "follow" | "invert";
65
- export type Centroid = Feature<Point>;
66
- export type CentroidBD = { [key in BiDirectionKey]?: Centroid };
67
- export type TinsBD = { [key in BiDirectionKey]?: Tins };
68
- export type Kinks = FeatureCollection<Point>;
69
- export type KinksBD = { [key in BiDirectionKey]?: Kinks };
70
- export type VerticesParamsBD = { [key in BiDirectionKey]?: VerticesParams };
71
- export type IndexedTinsBD = { [key in BiDirectionKey]?: IndexedTins };
72
-
73
- export const format_version = 2.00703; //(Version 2 format for library version 0.7.3)
74
-
75
- /**
76
- * コンパイルされた設定の型定義
77
- * 変換に必要な全ての情報を含む
78
- */
79
- export interface Compiled {
80
- version?: number;
81
- points: PointSet[];
82
- tins_points: (number | string)[][][];
83
- weight_buffer: WeightBufferBD;
84
- strict_status?: StrictStatus;
85
- centroid_point: Position[];
86
- edgeNodes?: PointSet[];
87
- kinks_points?: Position[];
88
- yaxisMode?: YaxisMode;
89
- vertexMode?: VertexMode;
90
- strictMode?: StrictMode;
91
- vertices_params: number[][];
92
- vertices_points: PointSet[];
93
- edges: EdgeSet[];
94
- bounds?: number[][];
95
- boundsPolygon?: Feature<Polygon>;
96
- wh?: number[];
97
- xy?: number[];
98
- }
99
-
100
- // For old Interface
101
- export interface CompiledLegacy extends Compiled {
102
- tins?: TinsBD;
103
- centroid?: CentroidBD;
104
- kinks?: KinksBD;
105
- vertices_params: number[][] & VerticesParamsBD;
106
- edges: EdgeSet[] & EdgeSetLegacy[];
107
- }
108
-
109
- /**
110
- * 座標変換の基本機能を提供するクラス
111
- *
112
- * 2つの座標系間の変換を、TINネットワークを使用して実現します。
113
- * このクラスは基本的な変換機能のみを提供し、
114
- * 設定ファイルの生成などの追加機能はTinクラスで提供されます。
115
- */
116
- export class Transform {
117
- /**
118
- * 各種モードの定数定義
119
- * すべてreadonlyで、型安全性を確保
120
- */
121
- static VERTEX_PLAIN = "plain" as const;
122
- static VERTEX_BIRDEYE = "birdeye" as const;
123
- static MODE_STRICT = "strict" as const;
124
- static MODE_AUTO = "auto" as const;
125
- static MODE_LOOSE = "loose" as const;
126
- static STATUS_STRICT = "strict" as const;
127
- static STATUS_ERROR = "strict_error" as const;
128
- static STATUS_LOOSE = "loose" as const;
129
- static YAXIS_FOLLOW = "follow" as const;
130
- static YAXIS_INVERT = "invert" as const;
131
-
132
- points: PointSet[] = [];
133
- pointsWeightBuffer?: WeightBufferBD;
134
- strict_status?: StrictStatus;
135
- vertices_params?: VerticesParamsBD;
136
- centroid?: CentroidBD;
137
- edgeNodes?: PointSet[];
138
- edges?: EdgeSet[];
139
- tins?: TinsBD;
140
- kinks?: KinksBD;
141
- yaxisMode: YaxisMode = Transform.YAXIS_INVERT;
142
- strictMode: StrictMode = Transform.MODE_AUTO;
143
- vertexMode?: VertexMode = Transform.VERTEX_PLAIN;
144
- bounds?: number[][];
145
- boundsPolygon?: Feature<Polygon>;
146
- wh?: number[];
147
- xy?: number[];
148
- indexedTins?: IndexedTinsBD;
149
- stateFull = false;
150
- stateTriangle?: Tri;
151
- stateBackward?: boolean;
152
-
153
- constructor() {}
154
-
155
- /**
156
- * コンパイルされた設定を適用します
157
- *
158
- * @param compiled - コンパイルされた設定オブジェクト
159
- * @returns 変換に必要な主要なオブジェクトのセット
160
- *
161
- * 以下の処理を行います:
162
- * 1. バージョンに応じた設定の解釈
163
- * 2. 各種パラメータの復元
164
- * 3. TINネットワークの再構築
165
- * 4. インデックスの作成
166
- */
167
- setCompiled(compiled: Compiled | CompiledLegacy): void {
168
- if (
169
- compiled.version ||
170
- (!(compiled as CompiledLegacy).tins && compiled.points && compiled.tins_points)
171
- ) {
172
- // 新コンパイルロジック
173
- // pointsはそのままpoints
174
- this.points = compiled.points;
175
- // After 0.7.3 Normalizing old formats for weightBuffer
176
- this.pointsWeightBuffer =
177
- !compiled.version || compiled.version < 2.00703
178
- ? (["forw", "bakw"] as BiDirectionKey[]).reduce((bd, forb) => {
179
- const base = compiled.weight_buffer[forb];
180
- if (base) {
181
- bd[forb] = Object.keys(base!).reduce((buffer, key) => {
182
- const normKey = normalizeNodeKey(key);
183
- buffer[normKey] = base![key];
184
- return buffer;
185
- }, {} as WeightBuffer);
186
- }
187
- return bd;
188
- }, {} as WeightBufferBD)
189
- : compiled.weight_buffer;
190
- // kinksやtinsの存在状況でstrict_statusを判定
191
- if (compiled.strict_status) {
192
- this.strict_status = compiled.strict_status;
193
- } else if (compiled.kinks_points) {
194
- this.strict_status = Transform.STATUS_ERROR;
195
- } else if (compiled.tins_points.length == 2) {
196
- this.strict_status = Transform.STATUS_LOOSE;
197
- } else {
198
- this.strict_status = Transform.STATUS_STRICT;
199
- }
200
- // vertices_paramsを復元
201
- this.vertices_params = {
202
- forw: [(compiled.vertices_params as number[][])[0]],
203
- bakw: [(compiled.vertices_params as number[][])[1]]
204
- };
205
- this.vertices_params.forw![1] = [0, 1, 2, 3].map(idx => {
206
- const idxNxt = (idx + 1) % 4;
207
- const tri = indexesToTri(
208
- ["c", `b${idx}`, `b${idxNxt}`],
209
- compiled.points,
210
- compiled.edgeNodes || [],
211
- compiled.centroid_point,
212
- compiled.vertices_points,
213
- false,
214
- format_version
215
- );
216
- return featureCollection([tri]);
217
- });
218
- this.vertices_params.bakw![1] = [0, 1, 2, 3].map(idx => {
219
- const idxNxt = (idx + 1) % 4;
220
- const tri = indexesToTri(
221
- ["c", `b${idx}`, `b${idxNxt}`],
222
- compiled.points,
223
- compiled.edgeNodes || [],
224
- compiled.centroid_point,
225
- compiled.vertices_points,
226
- true,
227
- format_version
228
- );
229
- return featureCollection([tri]);
230
- });
231
- // centroidを復元
232
- this.centroid = {
233
- forw: point(compiled.centroid_point[0], {
234
- target: {
235
- geom: compiled.centroid_point[1],
236
- index: "c"
237
- }
238
- }),
239
- bakw: point(compiled.centroid_point[1], {
240
- target: {
241
- geom: compiled.centroid_point[0],
242
- index: "c"
243
- }
244
- })
245
- };
246
- // edgesを復元
247
- this.edges = normalizeEdges(compiled.edges || []);
248
- this.edgeNodes = compiled.edgeNodes || [];
249
- // tinsを復元
250
- const bakwI = compiled.tins_points.length == 1 ? 0 : 1;
251
- this.tins = {
252
- forw: featureCollection(
253
- compiled.tins_points[0].map((idxes: (number | string)[]) =>
254
- indexesToTri(
255
- idxes,
256
- compiled.points,
257
- compiled.edgeNodes || [],
258
- compiled.centroid_point,
259
- compiled.vertices_points,
260
- false,
261
- compiled.version
262
- )
263
- )
264
- ),
265
- bakw: featureCollection(
266
- compiled.tins_points[bakwI].map((idxes: (number | string)[]) =>
267
- indexesToTri(
268
- idxes,
269
- compiled.points,
270
- compiled.edgeNodes || [],
271
- compiled.centroid_point,
272
- compiled.vertices_points,
273
- true,
274
- compiled.version
275
- )
276
- )
277
- )
278
- };
279
- this.addIndexedTin();
280
- // kinksを復元
281
- if (compiled.kinks_points) {
282
- this.kinks = {
283
- bakw: featureCollection(
284
- compiled.kinks_points.map((coord: Position) => point(coord))
285
- )
286
- };
287
- }
288
- // yaxisModeを復元
289
- if (compiled.yaxisMode) {
290
- this.yaxisMode = compiled.yaxisMode;
291
- } else {
292
- this.yaxisMode = Transform.YAXIS_INVERT;
293
- }
294
- // After 0.7.3: Restore strict_mode & vertex_mode
295
- if (compiled.vertexMode) {
296
- this.vertexMode = compiled.vertexMode;
297
- }
298
- if (compiled.strictMode) {
299
- this.strictMode = compiled.strictMode;
300
- }
301
- // boundsを復元
302
- if (compiled.bounds) {
303
- this.bounds = compiled.bounds;
304
- this.boundsPolygon = compiled.boundsPolygon;
305
- this.xy = compiled.xy;
306
- this.wh = compiled.wh;
307
- } else {
308
- this.xy = [0, 0];
309
- if (compiled.wh) this.wh = compiled.wh;
310
- this.bounds = undefined;
311
- this.boundsPolygon = undefined;
312
- }
313
- } else {
314
- // 旧コンパイルロジック
315
- compiled = JSON.parse(
316
- JSON.stringify(compiled)
317
- .replace('"cent"', '"c"')
318
- .replace(/"bbox(\d+)"/g, '"b$1"')
319
- );
320
- this.tins = (compiled as CompiledLegacy).tins;
321
- this.addIndexedTin();
322
- this.strict_status = compiled.strict_status;
323
- this.pointsWeightBuffer = compiled.weight_buffer;
324
- this.vertices_params = compiled.vertices_params as VerticesParamsBD;
325
- this.centroid = (compiled as CompiledLegacy).centroid;
326
- this.kinks = (compiled as CompiledLegacy).kinks;
327
- const points: PointSet[] = [];
328
- for (let i = 0; i < this.tins!.forw!.features.length; i++) {
329
- const tri = this.tins!.forw!.features[i];
330
- (["a", "b", "c"] as PropertyTriKey[]).map((key, idx) => {
331
- const forw = tri.geometry!.coordinates[0][idx];
332
- const bakw = tri.properties![key].geom;
333
- const pIdx = tri.properties![key].index;
334
- if (typeof pIdx === 'number') {
335
- points[pIdx] = [forw, bakw];
336
- }
337
- });
338
- }
339
- this.points = points;
340
- }
341
- }
342
-
343
- /**
344
- * TINネットワークのインデックスを作成します
345
- *
346
- * インデックスは変換処理を高速化するために使用されます。
347
- * グリッド形式のインデックスを作成し、各グリッドに
348
- * 含まれる三角形を記録します。
349
- */
350
- addIndexedTin() {
351
- const tins = this.tins!;
352
- const forw = tins.forw;
353
- const bakw = tins.bakw;
354
- const gridNum = Math.ceil(Math.sqrt(forw!.features.length));
355
- if (gridNum < 3) {
356
- this.indexedTins = undefined;
357
- return;
358
- }
359
- let forwBound: Position[] = [];
360
- let bakwBound: Position[] = [];
361
- const forwEachBound = forw!.features.map((tri: Tri) => {
362
- let eachBound: Position[] = [];
363
- getCoords(tri)[0].map((point: Position) => {
364
- if (forwBound.length === 0)
365
- forwBound = [Array.from(point), Array.from(point)];
366
- else {
367
- if (point[0] < forwBound[0][0]) forwBound[0][0] = point[0];
368
- if (point[0] > forwBound[1][0]) forwBound[1][0] = point[0];
369
- if (point[1] < forwBound[0][1]) forwBound[0][1] = point[1];
370
- if (point[1] > forwBound[1][1]) forwBound[1][1] = point[1];
371
- }
372
- if (eachBound.length === 0)
373
- eachBound = [Array.from(point), Array.from(point)];
374
- else {
375
- if (point[0] < eachBound[0][0]) eachBound[0][0] = point[0];
376
- if (point[0] > eachBound[1][0]) eachBound[1][0] = point[0];
377
- if (point[1] < eachBound[0][1]) eachBound[0][1] = point[1];
378
- if (point[1] > eachBound[1][1]) eachBound[1][1] = point[1];
379
- }
380
- });
381
- return eachBound;
382
- });
383
- const forwXUnit = (forwBound[1][0] - forwBound[0][0]) / gridNum;
384
- const forwYUnit = (forwBound[1][1] - forwBound[0][1]) / gridNum;
385
- const forwGridCache = forwEachBound.reduce(
386
- (prev: number[][][], bound: Position[], index: number) => {
387
- const normXMin = unitCalc(
388
- bound[0][0],
389
- forwBound[0][0],
390
- forwXUnit,
391
- gridNum
392
- );
393
- const normXMax = unitCalc(
394
- bound[1][0],
395
- forwBound[0][0],
396
- forwXUnit,
397
- gridNum
398
- );
399
- const normYMin = unitCalc(
400
- bound[0][1],
401
- forwBound[0][1],
402
- forwYUnit,
403
- gridNum
404
- );
405
- const normYMax = unitCalc(
406
- bound[1][1],
407
- forwBound[0][1],
408
- forwYUnit,
409
- gridNum
410
- );
411
- for (let cx = normXMin; cx <= normXMax; cx++) {
412
- if (!prev[cx]) prev[cx] = [];
413
- for (let cy = normYMin; cy <= normYMax; cy++) {
414
- if (!prev[cx][cy]) prev[cx][cy] = [];
415
- prev[cx][cy].push(index);
416
- }
417
- }
418
- return prev;
419
- },
420
- []
421
- );
422
- const bakwEachBound = bakw!.features.map((tri: Tri) => {
423
- let eachBound: Position[] = [];
424
- getCoords(tri)[0].map((point: Position) => {
425
- if (bakwBound.length === 0)
426
- bakwBound = [Array.from(point), Array.from(point)];
427
- else {
428
- if (point[0] < bakwBound[0][0]) bakwBound[0][0] = point[0];
429
- if (point[0] > bakwBound[1][0]) bakwBound[1][0] = point[0];
430
- if (point[1] < bakwBound[0][1]) bakwBound[0][1] = point[1];
431
- if (point[1] > bakwBound[1][1]) bakwBound[1][1] = point[1];
432
- }
433
- if (eachBound.length === 0)
434
- eachBound = [Array.from(point), Array.from(point)];
435
- else {
436
- if (point[0] < eachBound[0][0]) eachBound[0][0] = point[0];
437
- if (point[0] > eachBound[1][0]) eachBound[1][0] = point[0];
438
- if (point[1] < eachBound[0][1]) eachBound[0][1] = point[1];
439
- if (point[1] > eachBound[1][1]) eachBound[1][1] = point[1];
440
- }
441
- });
442
- return eachBound;
443
- });
444
- const bakwXUnit = (bakwBound[1][0] - bakwBound[0][0]) / gridNum;
445
- const bakwYUnit = (bakwBound[1][1] - bakwBound[0][1]) / gridNum;
446
- const bakwGridCache = bakwEachBound.reduce(
447
- (prev: number[][][], bound: Position[], index: number) => {
448
- const normXMin = unitCalc(
449
- bound[0][0],
450
- bakwBound[0][0],
451
- bakwXUnit,
452
- gridNum
453
- );
454
- const normXMax = unitCalc(
455
- bound[1][0],
456
- bakwBound[0][0],
457
- bakwXUnit,
458
- gridNum
459
- );
460
- const normYMin = unitCalc(
461
- bound[0][1],
462
- bakwBound[0][1],
463
- bakwYUnit,
464
- gridNum
465
- );
466
- const normYMax = unitCalc(
467
- bound[1][1],
468
- bakwBound[0][1],
469
- bakwYUnit,
470
- gridNum
471
- );
472
- for (let cx = normXMin; cx <= normXMax; cx++) {
473
- if (!prev[cx]) prev[cx] = [];
474
- for (let cy = normYMin; cy <= normYMax; cy++) {
475
- if (!prev[cx][cy]) prev[cx][cy] = [];
476
- prev[cx][cy].push(index);
477
- }
478
- }
479
- return prev;
480
- },
481
- []
482
- );
483
- this.indexedTins = {
484
- forw: {
485
- gridNum,
486
- xOrigin: forwBound[0][0],
487
- yOrigin: forwBound[0][1],
488
- xUnit: forwXUnit,
489
- yUnit: forwYUnit,
490
- gridCache: forwGridCache
491
- },
492
- bakw: {
493
- gridNum,
494
- xOrigin: bakwBound[0][0],
495
- yOrigin: bakwBound[0][1],
496
- xUnit: bakwXUnit,
497
- yUnit: bakwYUnit,
498
- gridCache: bakwGridCache
499
- }
500
- };
501
- }
502
-
503
- /**
504
- * 座標変換を実行します
505
- *
506
- * @param apoint - 変換する座標
507
- * @param backward - 逆方向の変換かどうか
508
- * @param ignoreBounds - 境界チェックを無視するかどうか
509
- * @returns 変換後の座標、または境界外の場合はfalse
510
- *
511
- * @throws {Error} 逆方向変換が許可されていない状態での逆変換時
512
- */
513
- transform(apoint: number[], backward?: boolean, ignoreBounds?: boolean): number[] | false {
514
- if (backward && this.strict_status == Transform.STATUS_ERROR)
515
- throw 'Backward transform is not allowed if strict_status == "strict_error"';
516
- // if (!this.tins) this.updateTin();
517
- if (this.yaxisMode == Transform.YAXIS_FOLLOW && backward) {
518
- apoint = [apoint[0], -1 * apoint[1]];
519
- }
520
- const tpoint = point(apoint);
521
- if (this.bounds && !backward && !ignoreBounds) {
522
- if (!booleanPointInPolygon(tpoint, this.boundsPolygon!)) return false;
523
- }
524
- const tins = backward ? this.tins!.bakw : this.tins!.forw;
525
- const indexedTins = backward
526
- ? this.indexedTins!.bakw
527
- : this.indexedTins!.forw;
528
- const verticesParams = backward
529
- ? this.vertices_params!.bakw
530
- : this.vertices_params!.forw;
531
- const centroid = backward ? this.centroid!.bakw : this.centroid!.forw;
532
- const weightBuffer = backward
533
- ? this.pointsWeightBuffer!.bakw
534
- : this.pointsWeightBuffer!.forw;
535
- let stateTriangle = undefined,
536
- stateSetFunc = undefined;
537
- if (this.stateFull) {
538
- if (this.stateBackward == backward) {
539
- stateTriangle = this.stateTriangle;
540
- } else {
541
- this.stateBackward = backward;
542
- this.stateTriangle = undefined;
543
- }
544
- stateSetFunc = (tri?: Tri) => {
545
- this.stateTriangle = tri;
546
- };
547
- }
548
- let ret = transformArr(
549
- tpoint,
550
- tins!,
551
- indexedTins,
552
- verticesParams,
553
- centroid,
554
- weightBuffer,
555
- stateTriangle,
556
- stateSetFunc
557
- );
558
- if (this.bounds && backward && !ignoreBounds) {
559
- const rpoint = point(ret);
560
- if (!booleanPointInPolygon(rpoint, this.boundsPolygon!)) return false;
561
- } else if (this.yaxisMode == Transform.YAXIS_FOLLOW && !backward) {
562
- ret = [ret[0], -1 * ret[1]];
563
- }
564
- return ret;
565
- }
566
-
1
+ import booleanPointInPolygon from "@turf/boolean-point-in-polygon";
2
+ import { featureCollection, point } from "@turf/helpers";
3
+ import { getCoords } from "@turf/invariant";
4
+ import { indexesToTri, normalizeNodeKey } from "./triangulation.ts";
5
+ import type { Feature, Polygon, Position, Point, FeatureCollection } from "geojson";
6
+ import { normalizeEdges } from "./edgeutils.ts";
7
+ import type {
8
+ WeightBuffer, Tins, VerticesParams, PropertyTriKey,
9
+ IndexedTins, Tri
10
+ } from "./geometry.ts";
11
+ import { unitCalc, transformArr } from "./geometry.ts";
12
+ import type { EdgeSet, EdgeSetLegacy } from "./edgeutils.ts";
13
+ export type { Tins, Tri, PropertyTriKey } from './geometry.ts';
14
+ export { transformArr } from './geometry.ts';
15
+ export { rotateVerticesTriangle, counterTri } from './triangulation.ts';
16
+ export type { Edge, EdgeSet, EdgeSetLegacy } from './edgeutils.ts';
17
+ export { normalizeEdges } from './edgeutils.ts';
18
+
19
+ /**
20
+ * 座標ペアの型定義。[ソース座標, ターゲット座標] の形式
21
+ */
22
+ export type PointSet = [Position, Position];
23
+
24
+ /**
25
+ * 変換の方向を示す型定義
26
+ * - forw: 順方向(ソース → ターゲット)
27
+ * - bakw: 逆方向(ターゲット → ソース)
28
+ */
29
+ export type BiDirectionKey = "forw" | "bakw";
30
+
31
+ /**
32
+ * 両方向の重み付けバッファの型定義
33
+ */
34
+ export type WeightBufferBD = { [key in BiDirectionKey]?: WeightBuffer };
35
+
36
+ /**
37
+ * 頂点モードの型定義
38
+ * - plain: 通常モード
39
+ * - birdeye: 鳥瞰図モード
40
+ */
41
+ export type VertexMode = "plain" | "birdeye";
42
+
43
+ /**
44
+ * 厳密性モードの型定義
45
+ * - strict: 厳密モード(交差なしを保証)
46
+ * - auto: 自動モード(可能な限り厳密に)
47
+ * - loose: 緩和モード(交差を許容)
48
+ */
49
+ export type StrictMode = "strict" | "auto" | "loose";
50
+
51
+ /**
52
+ * 厳密性モードの生成結果
53
+ * - strict: 厳密モード生成成功
54
+ * - auto: 厳密モードでエラー
55
+ * - loose: 緩和モード
56
+ */
57
+ export type StrictStatus = "strict" | "strict_error" | "loose";
58
+
59
+ /**
60
+ * Y軸の向きの型定義
61
+ * - follow: Y軸の向きを維持
62
+ * - invert: Y軸の向きを反転
63
+ */
64
+ export type YaxisMode = "follow" | "invert";
65
+ export type Centroid = Feature<Point>;
66
+ export type CentroidBD = { [key in BiDirectionKey]?: Centroid };
67
+ export type TinsBD = { [key in BiDirectionKey]?: Tins };
68
+ export type Kinks = FeatureCollection<Point>;
69
+ export type KinksBD = { [key in BiDirectionKey]?: Kinks };
70
+ export type VerticesParamsBD = { [key in BiDirectionKey]?: VerticesParams };
71
+ export type IndexedTinsBD = { [key in BiDirectionKey]?: IndexedTins };
72
+
73
+ export const format_version = 2.00703; //(Version 2 format for library version 0.7.3)
74
+
75
+ /**
76
+ * コンパイルされた設定の型定義
77
+ * 変換に必要な全ての情報を含む
78
+ */
79
+ export interface Compiled {
80
+ version?: number;
81
+ points: PointSet[];
82
+ tins_points: (number | string)[][][];
83
+ weight_buffer: WeightBufferBD;
84
+ strict_status?: StrictStatus;
85
+ centroid_point: Position[];
86
+ edgeNodes?: PointSet[];
87
+ kinks_points?: Position[];
88
+ yaxisMode?: YaxisMode;
89
+ vertexMode?: VertexMode;
90
+ strictMode?: StrictMode;
91
+ vertices_params: number[][];
92
+ vertices_points: PointSet[];
93
+ edges: EdgeSet[];
94
+ bounds?: number[][];
95
+ boundsPolygon?: Feature<Polygon>;
96
+ wh?: number[];
97
+ xy?: number[];
98
+ }
99
+
100
+ // For old Interface
101
+ export interface CompiledLegacy extends Compiled {
102
+ tins?: TinsBD;
103
+ centroid?: CentroidBD;
104
+ kinks?: KinksBD;
105
+ vertices_params: number[][] & VerticesParamsBD;
106
+ edges: EdgeSet[] & EdgeSetLegacy[];
107
+ }
108
+
109
+ /**
110
+ * 座標変換の基本機能を提供するクラス
111
+ *
112
+ * 2つの座標系間の変換を、TINネットワークを使用して実現します。
113
+ * このクラスは基本的な変換機能のみを提供し、
114
+ * 設定ファイルの生成などの追加機能はTinクラスで提供されます。
115
+ */
116
+ export class Transform {
117
+ /**
118
+ * 各種モードの定数定義
119
+ * すべてreadonlyで、型安全性を確保
120
+ */
121
+ static VERTEX_PLAIN = "plain" as const;
122
+ static VERTEX_BIRDEYE = "birdeye" as const;
123
+ static MODE_STRICT = "strict" as const;
124
+ static MODE_AUTO = "auto" as const;
125
+ static MODE_LOOSE = "loose" as const;
126
+ static STATUS_STRICT = "strict" as const;
127
+ static STATUS_ERROR = "strict_error" as const;
128
+ static STATUS_LOOSE = "loose" as const;
129
+ static YAXIS_FOLLOW = "follow" as const;
130
+ static YAXIS_INVERT = "invert" as const;
131
+
132
+ points: PointSet[] = [];
133
+ pointsWeightBuffer?: WeightBufferBD;
134
+ strict_status?: StrictStatus;
135
+ vertices_params?: VerticesParamsBD;
136
+ centroid?: CentroidBD;
137
+ edgeNodes?: PointSet[];
138
+ edges?: EdgeSet[];
139
+ tins?: TinsBD;
140
+ kinks?: KinksBD;
141
+ yaxisMode: YaxisMode = Transform.YAXIS_INVERT;
142
+ strictMode: StrictMode = Transform.MODE_AUTO;
143
+ vertexMode?: VertexMode = Transform.VERTEX_PLAIN;
144
+ bounds?: number[][];
145
+ boundsPolygon?: Feature<Polygon>;
146
+ wh?: number[];
147
+ xy?: number[];
148
+ indexedTins?: IndexedTinsBD;
149
+ stateFull = false;
150
+ stateTriangle?: Tri;
151
+ stateBackward?: boolean;
152
+
153
+ constructor() {}
154
+
155
+ /**
156
+ * コンパイルされた設定を適用します
157
+ *
158
+ * @param compiled - コンパイルされた設定オブジェクト
159
+ * @returns 変換に必要な主要なオブジェクトのセット
160
+ *
161
+ * 以下の処理を行います:
162
+ * 1. バージョンに応じた設定の解釈
163
+ * 2. 各種パラメータの復元
164
+ * 3. TINネットワークの再構築
165
+ * 4. インデックスの作成
166
+ */
167
+ setCompiled(compiled: Compiled | CompiledLegacy): void {
168
+ if (
169
+ compiled.version ||
170
+ (!(compiled as CompiledLegacy).tins && compiled.points && compiled.tins_points)
171
+ ) {
172
+ // 新コンパイルロジック
173
+ // pointsはそのままpoints
174
+ this.points = compiled.points;
175
+ // After 0.7.3 Normalizing old formats for weightBuffer
176
+ this.pointsWeightBuffer =
177
+ !compiled.version || compiled.version < 2.00703
178
+ ? (["forw", "bakw"] as BiDirectionKey[]).reduce((bd, forb) => {
179
+ const base = compiled.weight_buffer[forb];
180
+ if (base) {
181
+ bd[forb] = Object.keys(base!).reduce((buffer, key) => {
182
+ const normKey = normalizeNodeKey(key);
183
+ buffer[normKey] = base![key];
184
+ return buffer;
185
+ }, {} as WeightBuffer);
186
+ }
187
+ return bd;
188
+ }, {} as WeightBufferBD)
189
+ : compiled.weight_buffer;
190
+ // kinksやtinsの存在状況でstrict_statusを判定
191
+ if (compiled.strict_status) {
192
+ this.strict_status = compiled.strict_status;
193
+ } else if (compiled.kinks_points) {
194
+ this.strict_status = Transform.STATUS_ERROR;
195
+ } else if (compiled.tins_points.length == 2) {
196
+ this.strict_status = Transform.STATUS_LOOSE;
197
+ } else {
198
+ this.strict_status = Transform.STATUS_STRICT;
199
+ }
200
+ // vertices_paramsを復元
201
+ this.vertices_params = {
202
+ forw: [(compiled.vertices_params as number[][])[0]],
203
+ bakw: [(compiled.vertices_params as number[][])[1]]
204
+ };
205
+ this.vertices_params.forw![1] = [0, 1, 2, 3].map(idx => {
206
+ const idxNxt = (idx + 1) % 4;
207
+ const tri = indexesToTri(
208
+ ["c", `b${idx}`, `b${idxNxt}`],
209
+ compiled.points,
210
+ compiled.edgeNodes || [],
211
+ compiled.centroid_point,
212
+ compiled.vertices_points,
213
+ false,
214
+ format_version
215
+ );
216
+ return featureCollection([tri]);
217
+ });
218
+ this.vertices_params.bakw![1] = [0, 1, 2, 3].map(idx => {
219
+ const idxNxt = (idx + 1) % 4;
220
+ const tri = indexesToTri(
221
+ ["c", `b${idx}`, `b${idxNxt}`],
222
+ compiled.points,
223
+ compiled.edgeNodes || [],
224
+ compiled.centroid_point,
225
+ compiled.vertices_points,
226
+ true,
227
+ format_version
228
+ );
229
+ return featureCollection([tri]);
230
+ });
231
+ // centroidを復元
232
+ this.centroid = {
233
+ forw: point(compiled.centroid_point[0], {
234
+ target: {
235
+ geom: compiled.centroid_point[1],
236
+ index: "c"
237
+ }
238
+ }),
239
+ bakw: point(compiled.centroid_point[1], {
240
+ target: {
241
+ geom: compiled.centroid_point[0],
242
+ index: "c"
243
+ }
244
+ })
245
+ };
246
+ // edgesを復元
247
+ this.edges = normalizeEdges(compiled.edges || []);
248
+ this.edgeNodes = compiled.edgeNodes || [];
249
+ // tinsを復元
250
+ const bakwI = compiled.tins_points.length == 1 ? 0 : 1;
251
+ this.tins = {
252
+ forw: featureCollection(
253
+ compiled.tins_points[0].map((idxes: (number | string)[]) =>
254
+ indexesToTri(
255
+ idxes,
256
+ compiled.points,
257
+ compiled.edgeNodes || [],
258
+ compiled.centroid_point,
259
+ compiled.vertices_points,
260
+ false,
261
+ compiled.version
262
+ )
263
+ )
264
+ ),
265
+ bakw: featureCollection(
266
+ compiled.tins_points[bakwI].map((idxes: (number | string)[]) =>
267
+ indexesToTri(
268
+ idxes,
269
+ compiled.points,
270
+ compiled.edgeNodes || [],
271
+ compiled.centroid_point,
272
+ compiled.vertices_points,
273
+ true,
274
+ compiled.version
275
+ )
276
+ )
277
+ )
278
+ };
279
+ this.addIndexedTin();
280
+ // kinksを復元
281
+ if (compiled.kinks_points) {
282
+ this.kinks = {
283
+ bakw: featureCollection(
284
+ compiled.kinks_points.map((coord: Position) => point(coord))
285
+ )
286
+ };
287
+ }
288
+ // yaxisModeを復元
289
+ if (compiled.yaxisMode) {
290
+ this.yaxisMode = compiled.yaxisMode;
291
+ } else {
292
+ this.yaxisMode = Transform.YAXIS_INVERT;
293
+ }
294
+ // After 0.7.3: Restore strict_mode & vertex_mode
295
+ if (compiled.vertexMode) {
296
+ this.vertexMode = compiled.vertexMode;
297
+ }
298
+ if (compiled.strictMode) {
299
+ this.strictMode = compiled.strictMode;
300
+ }
301
+ // boundsを復元
302
+ if (compiled.bounds) {
303
+ this.bounds = compiled.bounds;
304
+ this.boundsPolygon = compiled.boundsPolygon;
305
+ this.xy = compiled.xy;
306
+ this.wh = compiled.wh;
307
+ } else {
308
+ this.xy = [0, 0];
309
+ if (compiled.wh) this.wh = compiled.wh;
310
+ this.bounds = undefined;
311
+ this.boundsPolygon = undefined;
312
+ }
313
+ } else {
314
+ // 旧コンパイルロジック
315
+ compiled = JSON.parse(
316
+ JSON.stringify(compiled)
317
+ .replace('"cent"', '"c"')
318
+ .replace(/"bbox(\d+)"/g, '"b$1"')
319
+ );
320
+ this.tins = (compiled as CompiledLegacy).tins;
321
+ this.addIndexedTin();
322
+ this.strict_status = compiled.strict_status;
323
+ this.pointsWeightBuffer = compiled.weight_buffer;
324
+ this.vertices_params = compiled.vertices_params as VerticesParamsBD;
325
+ this.centroid = (compiled as CompiledLegacy).centroid;
326
+ this.kinks = (compiled as CompiledLegacy).kinks;
327
+ const points: PointSet[] = [];
328
+ for (let i = 0; i < this.tins!.forw!.features.length; i++) {
329
+ const tri = this.tins!.forw!.features[i];
330
+ (["a", "b", "c"] as PropertyTriKey[]).map((key, idx) => {
331
+ const forw = tri.geometry!.coordinates[0][idx];
332
+ const bakw = tri.properties![key].geom;
333
+ const pIdx = tri.properties![key].index;
334
+ if (typeof pIdx === 'number') {
335
+ points[pIdx] = [forw, bakw];
336
+ }
337
+ });
338
+ }
339
+ this.points = points;
340
+ }
341
+ }
342
+
343
+ /**
344
+ * TINネットワークのインデックスを作成します
345
+ *
346
+ * インデックスは変換処理を高速化するために使用されます。
347
+ * グリッド形式のインデックスを作成し、各グリッドに
348
+ * 含まれる三角形を記録します。
349
+ */
350
+ addIndexedTin() {
351
+ const tins = this.tins!;
352
+ const forw = tins.forw;
353
+ const bakw = tins.bakw;
354
+ const gridNum = Math.ceil(Math.sqrt(forw!.features.length));
355
+ if (gridNum < 3) {
356
+ this.indexedTins = undefined;
357
+ return;
358
+ }
359
+ let forwBound: Position[] = [];
360
+ let bakwBound: Position[] = [];
361
+ const forwEachBound = forw!.features.map((tri: Tri) => {
362
+ let eachBound: Position[] = [];
363
+ getCoords(tri)[0].map((point: Position) => {
364
+ if (forwBound.length === 0)
365
+ forwBound = [Array.from(point), Array.from(point)];
366
+ else {
367
+ if (point[0] < forwBound[0][0]) forwBound[0][0] = point[0];
368
+ if (point[0] > forwBound[1][0]) forwBound[1][0] = point[0];
369
+ if (point[1] < forwBound[0][1]) forwBound[0][1] = point[1];
370
+ if (point[1] > forwBound[1][1]) forwBound[1][1] = point[1];
371
+ }
372
+ if (eachBound.length === 0)
373
+ eachBound = [Array.from(point), Array.from(point)];
374
+ else {
375
+ if (point[0] < eachBound[0][0]) eachBound[0][0] = point[0];
376
+ if (point[0] > eachBound[1][0]) eachBound[1][0] = point[0];
377
+ if (point[1] < eachBound[0][1]) eachBound[0][1] = point[1];
378
+ if (point[1] > eachBound[1][1]) eachBound[1][1] = point[1];
379
+ }
380
+ });
381
+ return eachBound;
382
+ });
383
+ const forwXUnit = (forwBound[1][0] - forwBound[0][0]) / gridNum;
384
+ const forwYUnit = (forwBound[1][1] - forwBound[0][1]) / gridNum;
385
+ const forwGridCache = forwEachBound.reduce(
386
+ (prev: number[][][], bound: Position[], index: number) => {
387
+ const normXMin = unitCalc(
388
+ bound[0][0],
389
+ forwBound[0][0],
390
+ forwXUnit,
391
+ gridNum
392
+ );
393
+ const normXMax = unitCalc(
394
+ bound[1][0],
395
+ forwBound[0][0],
396
+ forwXUnit,
397
+ gridNum
398
+ );
399
+ const normYMin = unitCalc(
400
+ bound[0][1],
401
+ forwBound[0][1],
402
+ forwYUnit,
403
+ gridNum
404
+ );
405
+ const normYMax = unitCalc(
406
+ bound[1][1],
407
+ forwBound[0][1],
408
+ forwYUnit,
409
+ gridNum
410
+ );
411
+ for (let cx = normXMin; cx <= normXMax; cx++) {
412
+ if (!prev[cx]) prev[cx] = [];
413
+ for (let cy = normYMin; cy <= normYMax; cy++) {
414
+ if (!prev[cx][cy]) prev[cx][cy] = [];
415
+ prev[cx][cy].push(index);
416
+ }
417
+ }
418
+ return prev;
419
+ },
420
+ []
421
+ );
422
+ const bakwEachBound = bakw!.features.map((tri: Tri) => {
423
+ let eachBound: Position[] = [];
424
+ getCoords(tri)[0].map((point: Position) => {
425
+ if (bakwBound.length === 0)
426
+ bakwBound = [Array.from(point), Array.from(point)];
427
+ else {
428
+ if (point[0] < bakwBound[0][0]) bakwBound[0][0] = point[0];
429
+ if (point[0] > bakwBound[1][0]) bakwBound[1][0] = point[0];
430
+ if (point[1] < bakwBound[0][1]) bakwBound[0][1] = point[1];
431
+ if (point[1] > bakwBound[1][1]) bakwBound[1][1] = point[1];
432
+ }
433
+ if (eachBound.length === 0)
434
+ eachBound = [Array.from(point), Array.from(point)];
435
+ else {
436
+ if (point[0] < eachBound[0][0]) eachBound[0][0] = point[0];
437
+ if (point[0] > eachBound[1][0]) eachBound[1][0] = point[0];
438
+ if (point[1] < eachBound[0][1]) eachBound[0][1] = point[1];
439
+ if (point[1] > eachBound[1][1]) eachBound[1][1] = point[1];
440
+ }
441
+ });
442
+ return eachBound;
443
+ });
444
+ const bakwXUnit = (bakwBound[1][0] - bakwBound[0][0]) / gridNum;
445
+ const bakwYUnit = (bakwBound[1][1] - bakwBound[0][1]) / gridNum;
446
+ const bakwGridCache = bakwEachBound.reduce(
447
+ (prev: number[][][], bound: Position[], index: number) => {
448
+ const normXMin = unitCalc(
449
+ bound[0][0],
450
+ bakwBound[0][0],
451
+ bakwXUnit,
452
+ gridNum
453
+ );
454
+ const normXMax = unitCalc(
455
+ bound[1][0],
456
+ bakwBound[0][0],
457
+ bakwXUnit,
458
+ gridNum
459
+ );
460
+ const normYMin = unitCalc(
461
+ bound[0][1],
462
+ bakwBound[0][1],
463
+ bakwYUnit,
464
+ gridNum
465
+ );
466
+ const normYMax = unitCalc(
467
+ bound[1][1],
468
+ bakwBound[0][1],
469
+ bakwYUnit,
470
+ gridNum
471
+ );
472
+ for (let cx = normXMin; cx <= normXMax; cx++) {
473
+ if (!prev[cx]) prev[cx] = [];
474
+ for (let cy = normYMin; cy <= normYMax; cy++) {
475
+ if (!prev[cx][cy]) prev[cx][cy] = [];
476
+ prev[cx][cy].push(index);
477
+ }
478
+ }
479
+ return prev;
480
+ },
481
+ []
482
+ );
483
+ this.indexedTins = {
484
+ forw: {
485
+ gridNum,
486
+ xOrigin: forwBound[0][0],
487
+ yOrigin: forwBound[0][1],
488
+ xUnit: forwXUnit,
489
+ yUnit: forwYUnit,
490
+ gridCache: forwGridCache
491
+ },
492
+ bakw: {
493
+ gridNum,
494
+ xOrigin: bakwBound[0][0],
495
+ yOrigin: bakwBound[0][1],
496
+ xUnit: bakwXUnit,
497
+ yUnit: bakwYUnit,
498
+ gridCache: bakwGridCache
499
+ }
500
+ };
501
+ }
502
+
503
+ /**
504
+ * 座標変換を実行します
505
+ *
506
+ * @param apoint - 変換する座標
507
+ * @param backward - 逆方向の変換かどうか
508
+ * @param ignoreBounds - 境界チェックを無視するかどうか
509
+ * @returns 変換後の座標、または境界外の場合はfalse
510
+ *
511
+ * @throws {Error} 逆方向変換が許可されていない状態での逆変換時
512
+ */
513
+ transform(apoint: number[], backward?: boolean, ignoreBounds?: boolean): number[] | false {
514
+ if (backward && this.strict_status == Transform.STATUS_ERROR)
515
+ throw 'Backward transform is not allowed if strict_status == "strict_error"';
516
+ // if (!this.tins) this.updateTin();
517
+ if (this.yaxisMode == Transform.YAXIS_FOLLOW && backward) {
518
+ apoint = [apoint[0], -1 * apoint[1]];
519
+ }
520
+ const tpoint = point(apoint);
521
+ if (this.bounds && !backward && !ignoreBounds) {
522
+ if (!booleanPointInPolygon(tpoint, this.boundsPolygon!)) return false;
523
+ }
524
+ const tins = backward ? this.tins!.bakw : this.tins!.forw;
525
+ const indexedTins = backward
526
+ ? this.indexedTins!.bakw
527
+ : this.indexedTins!.forw;
528
+ const verticesParams = backward
529
+ ? this.vertices_params!.bakw
530
+ : this.vertices_params!.forw;
531
+ const centroid = backward ? this.centroid!.bakw : this.centroid!.forw;
532
+ const weightBuffer = backward
533
+ ? this.pointsWeightBuffer!.bakw
534
+ : this.pointsWeightBuffer!.forw;
535
+ let stateTriangle = undefined,
536
+ stateSetFunc = undefined;
537
+ if (this.stateFull) {
538
+ if (this.stateBackward == backward) {
539
+ stateTriangle = this.stateTriangle;
540
+ } else {
541
+ this.stateBackward = backward;
542
+ this.stateTriangle = undefined;
543
+ }
544
+ stateSetFunc = (tri?: Tri) => {
545
+ this.stateTriangle = tri;
546
+ };
547
+ }
548
+ let ret = transformArr(
549
+ tpoint,
550
+ tins!,
551
+ indexedTins,
552
+ verticesParams,
553
+ centroid,
554
+ weightBuffer,
555
+ stateTriangle,
556
+ stateSetFunc
557
+ );
558
+ if (this.bounds && backward && !ignoreBounds) {
559
+ const rpoint = point(ret);
560
+ if (!booleanPointInPolygon(rpoint, this.boundsPolygon!)) return false;
561
+ } else if (this.yaxisMode == Transform.YAXIS_FOLLOW && !backward) {
562
+ ret = [ret[0], -1 * ret[1]];
563
+ }
564
+ return ret;
565
+ }
566
+
567
567
  }