@maplat/transform 0.4.1 → 0.5.1
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/LICENSE +224 -224
- package/README.ja.md +340 -170
- package/README.md +341 -171
- package/dist/index.d.ts +218 -1
- package/dist/maplat_transform.js +756 -459
- package/dist/maplat_transform.umd.js +1 -1
- package/package.json +3 -1
- package/src/compiled-state.ts +213 -211
- package/src/constants.ts +8 -0
- package/src/coord-utils.ts +27 -0
- package/src/edgeutils.ts +47 -47
- package/src/geometry.ts +240 -247
- package/src/index.ts +38 -398
- package/src/map-transform.ts +473 -0
- package/src/transform.ts +338 -0
- package/src/triangulation.ts +170 -179
- package/src/types.ts +162 -122
- package/src/viewpoint.ts +104 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import type { Feature, Polygon } from "geojson";
|
|
2
|
+
import { booleanPointInPolygon, point, polygon } from "@turf/turf";
|
|
3
|
+
import { Transform } from "./transform.ts";
|
|
4
|
+
import { zoom2Radius, mercViewpoint2Mercs, mercs2MercViewpoint } from "./viewpoint.ts";
|
|
5
|
+
import { xy2SysCoord, sysCoord2Xy } from "./coord-utils.ts";
|
|
6
|
+
import type { MapData, Viewpoint } from "./types.ts";
|
|
7
|
+
|
|
8
|
+
/** Internal representation of a loaded sub-map TIN entry */
|
|
9
|
+
interface SubTinEntry {
|
|
10
|
+
tin: Transform;
|
|
11
|
+
priority: number;
|
|
12
|
+
importance: number;
|
|
13
|
+
xyBounds: Feature<Polygon>;
|
|
14
|
+
mercBounds: Feature<Polygon>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Tile size constant (same as MaplatCore) */
|
|
18
|
+
const TILE_SIZE = 256;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* MapTransform — 処理2・3・4を担う座標変換クラス
|
|
22
|
+
*
|
|
23
|
+
* - 処理2: submaps 属性を持つ地図で、複数 TIN のうちどれを適用するか判定・選択し座標変換
|
|
24
|
+
* - 処理3: ビューポート ↔ メルカトル5点 変換
|
|
25
|
+
* - 処理4: ビューポート ↔ TIN 適用後メルカトル5点 変換
|
|
26
|
+
*
|
|
27
|
+
* OpenLayers への依存ゼロ。ブラウザ・Node.js 両対応。
|
|
28
|
+
*/
|
|
29
|
+
export class MapTransform {
|
|
30
|
+
private mainTin: Transform | null = null;
|
|
31
|
+
private subTins: SubTinEntry[] = [];
|
|
32
|
+
private _maxxy = 0;
|
|
33
|
+
|
|
34
|
+
// ─── 初期化 ────────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 地図データ(コンパイル済み TIN + sub_maps)をロードする
|
|
38
|
+
*
|
|
39
|
+
* @param mapData - メイン TIN と sub_maps の情報
|
|
40
|
+
*/
|
|
41
|
+
setMapData(mapData: MapData): void {
|
|
42
|
+
// メイン TIN を構築
|
|
43
|
+
const mainTin = new Transform();
|
|
44
|
+
mainTin.setCompiled(mapData.compiled);
|
|
45
|
+
this.mainTin = mainTin;
|
|
46
|
+
|
|
47
|
+
// _maxxy を計算
|
|
48
|
+
// 優先順位: MapData.maxZoom > compiled.wh からの自動計算
|
|
49
|
+
if (mapData.maxZoom !== undefined) {
|
|
50
|
+
this._maxxy = Math.pow(2, mapData.maxZoom) * TILE_SIZE;
|
|
51
|
+
} else if (mapData.compiled.wh) {
|
|
52
|
+
const maxDim = Math.max(mapData.compiled.wh[0], mapData.compiled.wh[1]);
|
|
53
|
+
const maxZoom = Math.ceil(Math.log2(maxDim / TILE_SIZE));
|
|
54
|
+
this._maxxy = Math.pow(2, maxZoom) * TILE_SIZE;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// sub_maps を構築
|
|
58
|
+
this.subTins = [];
|
|
59
|
+
if (mapData.sub_maps) {
|
|
60
|
+
for (const subMapData of mapData.sub_maps) {
|
|
61
|
+
const tin = new Transform();
|
|
62
|
+
tin.setCompiled(subMapData.compiled);
|
|
63
|
+
|
|
64
|
+
// bounds: SubMapData.bounds を優先し、なければ compiled.bounds を使用
|
|
65
|
+
const rawBounds: number[][] | undefined =
|
|
66
|
+
subMapData.bounds ?? subMapData.compiled.bounds;
|
|
67
|
+
if (!rawBounds) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
"SubMapData must have bounds or compiled.bounds to create xyBounds polygon"
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const xyBoundsCoords = [...rawBounds, rawBounds[0]];
|
|
74
|
+
const mercBoundsCoords = xyBoundsCoords.map(xy => {
|
|
75
|
+
const merc = tin.transform(xy, false);
|
|
76
|
+
if (!merc) throw new Error("Failed to transform sub-map bounds to mercator");
|
|
77
|
+
return merc;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
this.subTins.push({
|
|
81
|
+
tin,
|
|
82
|
+
priority: subMapData.priority,
|
|
83
|
+
importance: subMapData.importance,
|
|
84
|
+
xyBounds: polygon([xyBoundsCoords]) as Feature<Polygon>,
|
|
85
|
+
mercBounds: polygon([mercBoundsCoords]) as Feature<Polygon>
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ─── 処理2: submap TIN 選択付き変換 ───────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* ピクセル座標 → メルカトル座標(最適レイヤー選択)
|
|
95
|
+
*
|
|
96
|
+
* @param xy - ピクセル座標 [x, y]
|
|
97
|
+
* @returns メルカトル座標、または範囲外の場合は false
|
|
98
|
+
*/
|
|
99
|
+
xy2Merc(xy: number[]): number[] | false {
|
|
100
|
+
const result = this.xy2MercWithLayer(xy);
|
|
101
|
+
if (!result) return false;
|
|
102
|
+
return result[1];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* メルカトル座標 → ピクセル座標(最適レイヤー選択)
|
|
107
|
+
*
|
|
108
|
+
* @param merc - メルカトル座標 [x, y]
|
|
109
|
+
* @returns ピクセル座標、または範囲外の場合は false
|
|
110
|
+
*/
|
|
111
|
+
merc2Xy(merc: number[]): number[] | false {
|
|
112
|
+
const results = this.merc2XyWithLayer(merc);
|
|
113
|
+
const first = results[0] || results[1];
|
|
114
|
+
if (!first) return false;
|
|
115
|
+
return first[1];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* ピクセル座標 → メルカトル座標(レイヤーID付き)
|
|
120
|
+
* histmap_tin.ts xy2MercAsync_returnLayer() の同期版
|
|
121
|
+
*
|
|
122
|
+
* @param xy - ピクセル座標 [x, y]
|
|
123
|
+
* @returns [レイヤーインデックス, メルカトル座標] または false
|
|
124
|
+
*/
|
|
125
|
+
xy2MercWithLayer(xy: number[]): [number, number[]] | false {
|
|
126
|
+
this._assertMapData();
|
|
127
|
+
|
|
128
|
+
// priority 降順にソート(index 0 はメイン TIN)
|
|
129
|
+
const tinSorted = this._getTinsSortedByPriority();
|
|
130
|
+
|
|
131
|
+
for (let i = 0; i < tinSorted.length; i++) {
|
|
132
|
+
const { index, isMain } = tinSorted[i];
|
|
133
|
+
|
|
134
|
+
// メイン TIN(index 0)は常にマッチ、sub TIN はポリゴン内チェック
|
|
135
|
+
if (isMain || booleanPointInPolygon(point(xy), this.subTins[index - 1].xyBounds)) {
|
|
136
|
+
const merc = this._transformByIndex(xy, index, false);
|
|
137
|
+
if (merc === false) continue;
|
|
138
|
+
return [index, merc];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* メルカトル座標 → ピクセル座標(複数レイヤー結果)
|
|
146
|
+
* histmap_tin.ts merc2XyAsync_returnLayer() の同期版
|
|
147
|
+
*
|
|
148
|
+
* 現在は MaplatCore の仕様に合わせ、最大2レイヤーまで返す。
|
|
149
|
+
* 3レイヤー以上返したい場合は、下記の .slice(0, 2) および .filter(i < 2) の
|
|
150
|
+
* 上限値を増やすか、引数で上限を指定できるようにすること。
|
|
151
|
+
*
|
|
152
|
+
* @param merc - メルカトル座標 [x, y]
|
|
153
|
+
* @returns 最大2要素の配列。各要素は [レイヤーインデックス, ピクセル座標] または undefined
|
|
154
|
+
*/
|
|
155
|
+
merc2XyWithLayer(merc: number[]): ([number, number[]] | undefined)[] {
|
|
156
|
+
this._assertMapData();
|
|
157
|
+
|
|
158
|
+
// 全 TIN で逆変換を実行し、各結果が xyBounds 内かを確認
|
|
159
|
+
const allTins = this._getAllTinsWithIndex();
|
|
160
|
+
const rawResults: ([Transform, number, number[]] | [Transform, number])[] =
|
|
161
|
+
allTins.map(({ index, tin, isMain }) => {
|
|
162
|
+
const xy = this._transformByIndex(merc, index, true);
|
|
163
|
+
if (xy === false) return [tin, index] as [Transform, number];
|
|
164
|
+
// メイン TIN(index 0)は常に有効、sub TIN はポリゴン内チェック
|
|
165
|
+
if (isMain || booleanPointInPolygon(point(xy), this.subTins[index - 1].xyBounds)) {
|
|
166
|
+
return [tin, index, xy] as [Transform, number, number[]];
|
|
167
|
+
}
|
|
168
|
+
return [tin, index] as [Transform, number];
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// priority 降順でソート
|
|
172
|
+
const sorted = rawResults.sort((a, b) => {
|
|
173
|
+
const priA = a[0].priority ?? 0;
|
|
174
|
+
const priB = b[0].priority ?? 0;
|
|
175
|
+
return priA < priB ? 1 : -1;
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// importance と優先度で最大2レイヤーを選択
|
|
179
|
+
const result = sorted.reduce(
|
|
180
|
+
(
|
|
181
|
+
ret: (undefined | [number, number[], Transform])[],
|
|
182
|
+
current,
|
|
183
|
+
priIndex,
|
|
184
|
+
arry
|
|
185
|
+
) => {
|
|
186
|
+
const tin = current[0];
|
|
187
|
+
const index = current[1];
|
|
188
|
+
const xy = current[2] as number[] | undefined;
|
|
189
|
+
if (!xy) return ret;
|
|
190
|
+
|
|
191
|
+
// より高優先レイヤーの xyBounds 内にあるか確認
|
|
192
|
+
for (let i = 0; i < priIndex; i++) {
|
|
193
|
+
const targetIndex = arry[i][1];
|
|
194
|
+
const isTargetMain = targetIndex === 0;
|
|
195
|
+
// 高優先 TIN が変換失敗している場合はスキップ
|
|
196
|
+
if (!arry[i][2]) continue;
|
|
197
|
+
if (
|
|
198
|
+
isTargetMain ||
|
|
199
|
+
booleanPointInPolygon(point(xy), this.subTins[targetIndex - 1].xyBounds)
|
|
200
|
+
) {
|
|
201
|
+
if (ret.length) {
|
|
202
|
+
const hide = !ret[0];
|
|
203
|
+
const storedTin = hide ? ret[1]![2] : ret[0]![2];
|
|
204
|
+
const tinImportance = tin.importance ?? 0;
|
|
205
|
+
const storedImportance = storedTin.importance ?? 0;
|
|
206
|
+
if (!hide) {
|
|
207
|
+
// visible な stored TIN が既に存在 → current を 2番目要素として追加
|
|
208
|
+
const visible = ret.filter(
|
|
209
|
+
(r): r is [number, number[], Transform] => r !== undefined
|
|
210
|
+
);
|
|
211
|
+
const newRet = [...visible, [index, xy, tin] as [number, number[], Transform]];
|
|
212
|
+
return newRet
|
|
213
|
+
.sort((a, b) =>
|
|
214
|
+
(a[2].importance ?? 0) < (b[2].importance ?? 0) ? 1 : -1
|
|
215
|
+
)
|
|
216
|
+
.slice(0, 2) as (undefined | [number, number[], Transform])[]; // 上限を増やす場合はここも変更
|
|
217
|
+
} else if (tinImportance < storedImportance) {
|
|
218
|
+
return ret;
|
|
219
|
+
} else {
|
|
220
|
+
return [undefined, [index, xy, tin]] as (undefined | [number, number[], Transform])[];
|
|
221
|
+
}
|
|
222
|
+
} else {
|
|
223
|
+
// ret が空なのに xyBounds 内 → 高優先 TIN が変換失敗しているだけ → visible として追加
|
|
224
|
+
return [[index, xy, tin]] as (undefined | [number, number[], Transform])[];
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!ret.length || !ret[0]) {
|
|
230
|
+
return [[index, xy, tin]] as (undefined | [number, number[], Transform])[];
|
|
231
|
+
} else {
|
|
232
|
+
ret.push([index, xy, tin]);
|
|
233
|
+
return ret
|
|
234
|
+
.sort((a, b) => {
|
|
235
|
+
const impA = a![2].importance ?? 0;
|
|
236
|
+
const impB = b![2].importance ?? 0;
|
|
237
|
+
return impA < impB ? 1 : -1;
|
|
238
|
+
})
|
|
239
|
+
.filter((_row, i) => i < 2); // 上限を増やす場合はここも変更(例: i < 3)
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
[]
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
return result.map(row => {
|
|
246
|
+
if (!row) return undefined;
|
|
247
|
+
return [row[0], row[1]] as [number, number[]];
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* メルカトル5点 → システム座標(複数レイヤー)
|
|
253
|
+
* histmap_tin.ts mercs2SysCoordsAsync_multiLayer() の同期版
|
|
254
|
+
*
|
|
255
|
+
* @param mercs - 5点のメルカトル座標配列(中心+上下左右)
|
|
256
|
+
* @returns 各レイヤーのシステム座標配列(または undefined)
|
|
257
|
+
*/
|
|
258
|
+
mercs2SysCoords(mercs: number[][]): (number[][] | undefined)[] {
|
|
259
|
+
this._assertMapData();
|
|
260
|
+
const centerResults = this.merc2XyWithLayer(mercs[0]);
|
|
261
|
+
|
|
262
|
+
let hide = false;
|
|
263
|
+
return centerResults.map((result, i) => {
|
|
264
|
+
if (!result) {
|
|
265
|
+
hide = true;
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
const index = result[0];
|
|
269
|
+
const centerXy = result[1];
|
|
270
|
+
|
|
271
|
+
if (i !== 0 && !hide) return [this.xy2SysCoordInternal(centerXy)];
|
|
272
|
+
|
|
273
|
+
const xys = mercs.map((merc, j) => {
|
|
274
|
+
if (j === 0) return centerXy;
|
|
275
|
+
return this._transformByIndex(merc, index, true) as number[];
|
|
276
|
+
});
|
|
277
|
+
return xys.map(xy => this.xy2SysCoordInternal(xy));
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ─── 処理3: ビューポート変換 ───────────────────────────────────────────────
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* ビューポート → TIN 適用後メルカトル5点
|
|
285
|
+
* histmap_tin.ts viewpoint2MercsAsync() の同期版
|
|
286
|
+
*
|
|
287
|
+
* @param viewpoint - ビューポート(center, zoom, rotation)
|
|
288
|
+
* @param size - 画面サイズ [width, height]
|
|
289
|
+
* @returns TIN 変換後のメルカトル5点
|
|
290
|
+
*/
|
|
291
|
+
viewpoint2Mercs(viewpoint: Viewpoint, size: [number, number]): number[][] {
|
|
292
|
+
this._assertMapData();
|
|
293
|
+
this._assertMaxxy();
|
|
294
|
+
|
|
295
|
+
// ビューポート → メルカトル5点(処理3の純粋関数)
|
|
296
|
+
const mercs = mercViewpoint2Mercs(viewpoint.center, viewpoint.zoom, viewpoint.rotation, size);
|
|
297
|
+
|
|
298
|
+
// メルカトル → ピクセル座標(sysCoord2Xy)
|
|
299
|
+
const xys = mercs.map(merc => sysCoord2Xy(merc, this._maxxy));
|
|
300
|
+
|
|
301
|
+
// ピクセル中心 → TIN 変換 → メルカトル中心
|
|
302
|
+
const centerResult = this.xy2MercWithLayer(xys[0]);
|
|
303
|
+
if (!centerResult) throw new Error("viewpoint2Mercs: center point is out of bounds");
|
|
304
|
+
|
|
305
|
+
const centerIndex = centerResult[0];
|
|
306
|
+
const centerMerc = centerResult[1];
|
|
307
|
+
|
|
308
|
+
// 残り4点を同じレイヤーで変換
|
|
309
|
+
const resultMercs: number[][] = xys.map((xy, i) => {
|
|
310
|
+
if (i === 0) return centerMerc;
|
|
311
|
+
const merc = this._transformByIndex(xy, centerIndex, false);
|
|
312
|
+
if (merc === false) throw new Error(`viewpoint2Mercs: point ${i} is out of bounds`);
|
|
313
|
+
return merc;
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
return resultMercs;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* TIN 適用後メルカトル5点 → ビューポート
|
|
321
|
+
* histmap_tin.ts mercs2ViewpointAsync() の同期版
|
|
322
|
+
*
|
|
323
|
+
* @param mercs - TIN 変換後のメルカトル5点
|
|
324
|
+
* @param size - 画面サイズ [width, height]
|
|
325
|
+
* @returns ビューポート(center, zoom, rotation)
|
|
326
|
+
*/
|
|
327
|
+
mercs2Viewpoint(mercs: number[][], size: [number, number]): Viewpoint {
|
|
328
|
+
this._assertMapData();
|
|
329
|
+
this._assertMaxxy();
|
|
330
|
+
|
|
331
|
+
// メルカトル中心 → TIN 逆変換 → ピクセル中心
|
|
332
|
+
const centerResults = this.merc2XyWithLayer(mercs[0]);
|
|
333
|
+
const result = centerResults[0] || centerResults[1];
|
|
334
|
+
if (!result) throw new Error("mercs2Viewpoint: center point is out of bounds");
|
|
335
|
+
|
|
336
|
+
const centerIndex = result[0];
|
|
337
|
+
const centerXy = result[1];
|
|
338
|
+
|
|
339
|
+
// 残り4点を同じレイヤーで TIN 逆変換
|
|
340
|
+
const xys: number[][] = mercs.map((merc, i) => {
|
|
341
|
+
if (i === 0) return centerXy;
|
|
342
|
+
const xy = this._transformByIndex(merc, centerIndex, true);
|
|
343
|
+
if (xy === false) throw new Error(`mercs2Viewpoint: point ${i} is out of bounds`);
|
|
344
|
+
return xy;
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// ピクセル → システム座標(xy2SysCoord)
|
|
348
|
+
const sysCoords = xys.map(xy => xy2SysCoord(xy, this._maxxy));
|
|
349
|
+
|
|
350
|
+
// システム座標5点 → ビューポート(mercs2MercViewpoint)
|
|
351
|
+
return mercs2MercViewpoint(sysCoords, size);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ─── ユーティリティ(静的メソッド)────────────────────────────────────────
|
|
355
|
+
|
|
356
|
+
/** zoom2Radius の静的ラッパー */
|
|
357
|
+
static zoom2Radius(size: [number, number], zoom: number): number {
|
|
358
|
+
return zoom2Radius(size, zoom);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** mercViewpoint2Mercs の静的ラッパー */
|
|
362
|
+
static mercViewpoint2Mercs(
|
|
363
|
+
center: number[],
|
|
364
|
+
zoom: number,
|
|
365
|
+
rotation: number,
|
|
366
|
+
size: [number, number]
|
|
367
|
+
): number[][] {
|
|
368
|
+
return mercViewpoint2Mercs(center, zoom, rotation, size);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** mercs2MercViewpoint の静的ラッパー */
|
|
372
|
+
static mercs2MercViewpoint(mercs: number[][], size: [number, number]): Viewpoint {
|
|
373
|
+
return mercs2MercViewpoint(mercs, size);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/** xy2SysCoord の静的ラッパー */
|
|
377
|
+
static xy2SysCoord(xy: number[], maxxy: number): number[] {
|
|
378
|
+
return xy2SysCoord(xy, maxxy);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** sysCoord2Xy の静的ラッパー */
|
|
382
|
+
static sysCoord2Xy(sysCoord: number[], maxxy: number): number[] {
|
|
383
|
+
return sysCoord2Xy(sysCoord, maxxy);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ─── 内部ヘルパー ──────────────────────────────────────────────────────────
|
|
387
|
+
|
|
388
|
+
private _assertMapData(): void {
|
|
389
|
+
if (!this.mainTin) {
|
|
390
|
+
throw new Error("setMapData() must be called before transformation");
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private _assertMaxxy(): void {
|
|
395
|
+
if (this._maxxy === 0) {
|
|
396
|
+
throw new Error(
|
|
397
|
+
"MapData.maxZoom or compiled.wh must be set for viewpoint conversion (xy2SysCoord / sysCoord2Xy)"
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* レイヤーインデックスに対応する Transform インスタンスを返す(三角網描画などの用途)
|
|
404
|
+
*
|
|
405
|
+
* @param idx - 0 = メイン TIN、1以上 = sub_maps[idx-1]
|
|
406
|
+
* @returns 対応する Transform、または範囲外の場合は null
|
|
407
|
+
*/
|
|
408
|
+
getLayerTransform(idx: number): Transform | null {
|
|
409
|
+
if (idx === 0) return this.mainTin;
|
|
410
|
+
const sub = this.subTins[idx - 1];
|
|
411
|
+
return sub ? sub.tin : null;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** レイヤー数を返す(メイン + sub 数) */
|
|
415
|
+
get layerCount(): number {
|
|
416
|
+
return 1 + this.subTins.length;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* viewpoint 変換に使用する最大ピクセル幅(2^maxZoom × 256)
|
|
421
|
+
* stateToViewpoint / viewpointToState で zoom ↔ scale 変換に使用する
|
|
422
|
+
* zoom = log2(scale × maxxy / 256) の関係
|
|
423
|
+
*/
|
|
424
|
+
get maxxy(): number {
|
|
425
|
+
return this._maxxy;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/** priority 降順でソートした [index, tin, isMain] の配列を返す */
|
|
429
|
+
private _getTinsSortedByPriority(): { index: number; tin: Transform; isMain: boolean }[] {
|
|
430
|
+
const all = this._getAllTinsWithIndex();
|
|
431
|
+
return all.sort((a, b) => {
|
|
432
|
+
const priA = a.tin.priority ?? 0;
|
|
433
|
+
const priB = b.tin.priority ?? 0;
|
|
434
|
+
return priA < priB ? 1 : -1;
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/** メイン TIN + 全 sub TIN を index 付きで返す */
|
|
439
|
+
private _getAllTinsWithIndex(): { index: number; tin: Transform; isMain: boolean }[] {
|
|
440
|
+
const result: { index: number; tin: Transform; isMain: boolean }[] = [
|
|
441
|
+
{ index: 0, tin: this.mainTin!, isMain: true }
|
|
442
|
+
];
|
|
443
|
+
this.subTins.forEach((entry, i) => {
|
|
444
|
+
// priority/importance を Transform インスタンスに反映(ソート・比較に使用)
|
|
445
|
+
entry.tin.priority = entry.priority;
|
|
446
|
+
entry.tin.importance = entry.importance;
|
|
447
|
+
result.push({ index: i + 1, tin: entry.tin, isMain: false });
|
|
448
|
+
});
|
|
449
|
+
return result;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* 指定レイヤーインデックスで TIN 変換を実行する
|
|
454
|
+
* index 0 → mainTin, index 1..n → subTins[index-1]
|
|
455
|
+
*/
|
|
456
|
+
private _transformByIndex(
|
|
457
|
+
coord: number[],
|
|
458
|
+
index: number,
|
|
459
|
+
backward: boolean
|
|
460
|
+
): number[] | false {
|
|
461
|
+
if (index === 0) {
|
|
462
|
+
return this.mainTin!.transform(coord, backward);
|
|
463
|
+
}
|
|
464
|
+
const subEntry = this.subTins[index - 1];
|
|
465
|
+
if (!subEntry) return false;
|
|
466
|
+
return subEntry.tin.transform(coord, backward, true);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** 内部用 xy2SysCoord(_maxxy を使用) */
|
|
470
|
+
private xy2SysCoordInternal(xy: number[]): number[] {
|
|
471
|
+
return xy2SysCoord(xy, this._maxxy);
|
|
472
|
+
}
|
|
473
|
+
}
|