@maplat/transform 0.5.0 → 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/README.ja.md +162 -0
- package/README.md +162 -0
- package/dist/index.d.ts +217 -0
- package/dist/maplat_transform.js +753 -451
- package/dist/maplat_transform.umd.js +1 -1
- package/package.json +3 -1
- package/src/constants.ts +8 -0
- package/src/coord-utils.ts +27 -0
- package/src/index.ts +17 -379
- package/src/map-transform.ts +473 -0
- package/src/transform.ts +338 -0
- package/src/types.ts +40 -0
- package/src/viewpoint.ts +104 -0
package/src/transform.ts
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import type { Feature, Polygon, Position } from "geojson";
|
|
2
|
+
import { booleanPointInPolygon, point, getCoords } from "@turf/turf";
|
|
3
|
+
import { unitCalc, transformArr } from "./geometry.ts";
|
|
4
|
+
import type { Tri } from "./geometry.ts";
|
|
5
|
+
import {
|
|
6
|
+
FORMAT_VERSION,
|
|
7
|
+
isModernCompiled,
|
|
8
|
+
restoreLegacyState,
|
|
9
|
+
restoreModernState
|
|
10
|
+
} from "./compiled-state.ts";
|
|
11
|
+
import type { EdgeSet } from "./edgeutils.ts";
|
|
12
|
+
import type {
|
|
13
|
+
Compiled,
|
|
14
|
+
CompiledLegacy,
|
|
15
|
+
IndexedTinsBD,
|
|
16
|
+
KinksBD,
|
|
17
|
+
LegacyStatePayload,
|
|
18
|
+
ModernStatePayload,
|
|
19
|
+
PointSet,
|
|
20
|
+
StrictMode,
|
|
21
|
+
StrictStatus,
|
|
22
|
+
TinsBD,
|
|
23
|
+
VertexMode,
|
|
24
|
+
VerticesParamsBD,
|
|
25
|
+
WeightBufferBD,
|
|
26
|
+
YaxisMode,
|
|
27
|
+
CentroidBD
|
|
28
|
+
} from "./types.ts";
|
|
29
|
+
|
|
30
|
+
export const format_version = FORMAT_VERSION;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 座標変換の基本機能を提供するクラス
|
|
34
|
+
*
|
|
35
|
+
* 2つの座標系間の変換を、TINネットワークを使用して実現します。
|
|
36
|
+
* このクラスは基本的な変換機能のみを提供し、
|
|
37
|
+
* 設定ファイルの生成などの追加機能はTinクラスで提供されます。
|
|
38
|
+
*/
|
|
39
|
+
export class Transform {
|
|
40
|
+
/**
|
|
41
|
+
* 各種モードの定数定義
|
|
42
|
+
* すべてreadonlyで、型安全性を確保
|
|
43
|
+
*/
|
|
44
|
+
static VERTEX_PLAIN = "plain" as const;
|
|
45
|
+
static VERTEX_BIRDEYE = "birdeye" as const;
|
|
46
|
+
static MODE_STRICT = "strict" as const;
|
|
47
|
+
static MODE_AUTO = "auto" as const;
|
|
48
|
+
static MODE_LOOSE = "loose" as const;
|
|
49
|
+
static STATUS_STRICT = "strict" as const;
|
|
50
|
+
static STATUS_ERROR = "strict_error" as const;
|
|
51
|
+
static STATUS_LOOSE = "loose" as const;
|
|
52
|
+
static YAXIS_FOLLOW = "follow" as const;
|
|
53
|
+
static YAXIS_INVERT = "invert" as const;
|
|
54
|
+
|
|
55
|
+
points: PointSet[] = [];
|
|
56
|
+
pointsWeightBuffer?: WeightBufferBD;
|
|
57
|
+
strict_status?: StrictStatus;
|
|
58
|
+
vertices_params?: VerticesParamsBD;
|
|
59
|
+
centroid?: CentroidBD;
|
|
60
|
+
edgeNodes?: PointSet[];
|
|
61
|
+
edges?: EdgeSet[];
|
|
62
|
+
tins?: TinsBD;
|
|
63
|
+
kinks?: KinksBD;
|
|
64
|
+
yaxisMode: YaxisMode = Transform.YAXIS_INVERT;
|
|
65
|
+
strictMode: StrictMode = Transform.MODE_AUTO;
|
|
66
|
+
vertexMode?: VertexMode = Transform.VERTEX_PLAIN;
|
|
67
|
+
bounds?: number[][];
|
|
68
|
+
boundsPolygon?: Feature<Polygon>;
|
|
69
|
+
wh?: number[];
|
|
70
|
+
xy?: number[];
|
|
71
|
+
indexedTins?: IndexedTinsBD;
|
|
72
|
+
stateFull = false;
|
|
73
|
+
stateTriangle?: Tri;
|
|
74
|
+
stateBackward?: boolean;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Optional properties for MaplatCore extension
|
|
78
|
+
* These properties allow consuming applications to extend Transform instances
|
|
79
|
+
* with additional metadata without requiring Module Augmentation
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
/** Layer priority for rendering order */
|
|
83
|
+
priority?: number;
|
|
84
|
+
|
|
85
|
+
/** Layer importance for display decisions */
|
|
86
|
+
importance?: number;
|
|
87
|
+
|
|
88
|
+
/** Bounds in XY (source) coordinate system */
|
|
89
|
+
xyBounds?: Feature<Polygon>;
|
|
90
|
+
|
|
91
|
+
/** Bounds in Mercator (Web Mercator) coordinate system */
|
|
92
|
+
mercBounds?: Feature<Polygon>;
|
|
93
|
+
|
|
94
|
+
constructor() { }
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* コンパイルされた設定を適用します
|
|
98
|
+
*
|
|
99
|
+
* @param compiled - コンパイルされた設定オブジェクト
|
|
100
|
+
* @returns 変換に必要な主要なオブジェクトのセット
|
|
101
|
+
*
|
|
102
|
+
* 以下の処理を行います:
|
|
103
|
+
* 1. バージョンに応じた設定の解釈
|
|
104
|
+
* 2. 各種パラメータの復元
|
|
105
|
+
* 3. TINネットワークの再構築
|
|
106
|
+
* 4. インデックスの作成
|
|
107
|
+
*/
|
|
108
|
+
setCompiled(compiled: Compiled | CompiledLegacy): void {
|
|
109
|
+
if (isModernCompiled(compiled)) {
|
|
110
|
+
this.applyModernState(restoreModernState(compiled));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.applyLegacyState(restoreLegacyState(compiled as CompiledLegacy));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private applyModernState(state: ModernStatePayload): void {
|
|
117
|
+
this.points = state.points;
|
|
118
|
+
this.pointsWeightBuffer = state.pointsWeightBuffer;
|
|
119
|
+
this.strict_status = state.strictStatus;
|
|
120
|
+
this.vertices_params = state.verticesParams;
|
|
121
|
+
this.centroid = state.centroid;
|
|
122
|
+
this.edges = state.edges;
|
|
123
|
+
this.edgeNodes = state.edgeNodes || [];
|
|
124
|
+
this.tins = state.tins;
|
|
125
|
+
this.addIndexedTin();
|
|
126
|
+
this.kinks = state.kinks;
|
|
127
|
+
this.yaxisMode = state.yaxisMode ?? Transform.YAXIS_INVERT;
|
|
128
|
+
this.vertexMode = state.vertexMode ?? Transform.VERTEX_PLAIN;
|
|
129
|
+
this.strictMode = state.strictMode ?? Transform.MODE_AUTO;
|
|
130
|
+
if (state.bounds) {
|
|
131
|
+
this.bounds = state.bounds;
|
|
132
|
+
this.boundsPolygon = state.boundsPolygon;
|
|
133
|
+
this.xy = state.xy;
|
|
134
|
+
this.wh = state.wh;
|
|
135
|
+
} else {
|
|
136
|
+
this.bounds = undefined;
|
|
137
|
+
this.boundsPolygon = undefined;
|
|
138
|
+
this.xy = state.xy ?? [0, 0];
|
|
139
|
+
if (state.wh) this.wh = state.wh;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private applyLegacyState(state: LegacyStatePayload): void {
|
|
144
|
+
this.tins = state.tins;
|
|
145
|
+
this.addIndexedTin();
|
|
146
|
+
this.strict_status = state.strictStatus;
|
|
147
|
+
this.pointsWeightBuffer = state.pointsWeightBuffer;
|
|
148
|
+
this.vertices_params = state.verticesParams;
|
|
149
|
+
this.centroid = state.centroid;
|
|
150
|
+
this.kinks = state.kinks;
|
|
151
|
+
this.points = state.points;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* TINネットワークのインデックスを作成します
|
|
156
|
+
*
|
|
157
|
+
* インデックスは変換処理を高速化するために使用されます。
|
|
158
|
+
* グリッド形式のインデックスを作成し、各グリッドに
|
|
159
|
+
* 含まれる三角形を記録します。
|
|
160
|
+
*/
|
|
161
|
+
addIndexedTin() {
|
|
162
|
+
const tins = this.tins!;
|
|
163
|
+
const forw = tins.forw;
|
|
164
|
+
const bakw = tins.bakw;
|
|
165
|
+
const gridNum = Math.ceil(Math.sqrt(forw!.features.length));
|
|
166
|
+
if (gridNum < 3) {
|
|
167
|
+
this.indexedTins = undefined;
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
let forwBound: Position[] = [];
|
|
171
|
+
let bakwBound: Position[] = [];
|
|
172
|
+
const forwEachBound = forw!.features.map((tri: Tri) => {
|
|
173
|
+
let eachBound: Position[] = [];
|
|
174
|
+
getCoords(tri)[0].map((point: Position) => {
|
|
175
|
+
if (forwBound.length === 0)
|
|
176
|
+
forwBound = [Array.from(point), Array.from(point)];
|
|
177
|
+
else {
|
|
178
|
+
if (point[0] < forwBound[0][0]) forwBound[0][0] = point[0];
|
|
179
|
+
if (point[0] > forwBound[1][0]) forwBound[1][0] = point[0];
|
|
180
|
+
if (point[1] < forwBound[0][1]) forwBound[0][1] = point[1];
|
|
181
|
+
if (point[1] > forwBound[1][1]) forwBound[1][1] = point[1];
|
|
182
|
+
}
|
|
183
|
+
if (eachBound.length === 0)
|
|
184
|
+
eachBound = [Array.from(point), Array.from(point)];
|
|
185
|
+
else {
|
|
186
|
+
if (point[0] < eachBound[0][0]) eachBound[0][0] = point[0];
|
|
187
|
+
if (point[0] > eachBound[1][0]) eachBound[1][0] = point[0];
|
|
188
|
+
if (point[1] < eachBound[0][1]) eachBound[0][1] = point[1];
|
|
189
|
+
if (point[1] > eachBound[1][1]) eachBound[1][1] = point[1];
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
return eachBound;
|
|
193
|
+
});
|
|
194
|
+
const forwXUnit = (forwBound[1][0] - forwBound[0][0]) / gridNum;
|
|
195
|
+
const forwYUnit = (forwBound[1][1] - forwBound[0][1]) / gridNum;
|
|
196
|
+
const forwGridCache = forwEachBound.reduce(
|
|
197
|
+
(prev: number[][][], bound: Position[], index: number) => {
|
|
198
|
+
const normXMin = unitCalc(bound[0][0], forwBound[0][0], forwXUnit, gridNum);
|
|
199
|
+
const normXMax = unitCalc(bound[1][0], forwBound[0][0], forwXUnit, gridNum);
|
|
200
|
+
const normYMin = unitCalc(bound[0][1], forwBound[0][1], forwYUnit, gridNum);
|
|
201
|
+
const normYMax = unitCalc(bound[1][1], forwBound[0][1], forwYUnit, gridNum);
|
|
202
|
+
for (let cx = normXMin; cx <= normXMax; cx++) {
|
|
203
|
+
if (!prev[cx]) prev[cx] = [];
|
|
204
|
+
for (let cy = normYMin; cy <= normYMax; cy++) {
|
|
205
|
+
if (!prev[cx][cy]) prev[cx][cy] = [];
|
|
206
|
+
prev[cx][cy].push(index);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return prev;
|
|
210
|
+
},
|
|
211
|
+
[]
|
|
212
|
+
);
|
|
213
|
+
const bakwEachBound = bakw!.features.map((tri: Tri) => {
|
|
214
|
+
let eachBound: Position[] = [];
|
|
215
|
+
getCoords(tri)[0].map((point: Position) => {
|
|
216
|
+
if (bakwBound.length === 0)
|
|
217
|
+
bakwBound = [Array.from(point), Array.from(point)];
|
|
218
|
+
else {
|
|
219
|
+
if (point[0] < bakwBound[0][0]) bakwBound[0][0] = point[0];
|
|
220
|
+
if (point[0] > bakwBound[1][0]) bakwBound[1][0] = point[0];
|
|
221
|
+
if (point[1] < bakwBound[0][1]) bakwBound[0][1] = point[1];
|
|
222
|
+
if (point[1] > bakwBound[1][1]) bakwBound[1][1] = point[1];
|
|
223
|
+
}
|
|
224
|
+
if (eachBound.length === 0)
|
|
225
|
+
eachBound = [Array.from(point), Array.from(point)];
|
|
226
|
+
else {
|
|
227
|
+
if (point[0] < eachBound[0][0]) eachBound[0][0] = point[0];
|
|
228
|
+
if (point[0] > eachBound[1][0]) eachBound[1][0] = point[0];
|
|
229
|
+
if (point[1] < eachBound[0][1]) eachBound[0][1] = point[1];
|
|
230
|
+
if (point[1] > eachBound[1][1]) eachBound[1][1] = point[1];
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
return eachBound;
|
|
234
|
+
});
|
|
235
|
+
const bakwXUnit = (bakwBound[1][0] - bakwBound[0][0]) / gridNum;
|
|
236
|
+
const bakwYUnit = (bakwBound[1][1] - bakwBound[0][1]) / gridNum;
|
|
237
|
+
const bakwGridCache = bakwEachBound.reduce(
|
|
238
|
+
(prev: number[][][], bound: Position[], index: number) => {
|
|
239
|
+
const normXMin = unitCalc(bound[0][0], bakwBound[0][0], bakwXUnit, gridNum);
|
|
240
|
+
const normXMax = unitCalc(bound[1][0], bakwBound[0][0], bakwXUnit, gridNum);
|
|
241
|
+
const normYMin = unitCalc(bound[0][1], bakwBound[0][1], bakwYUnit, gridNum);
|
|
242
|
+
const normYMax = unitCalc(bound[1][1], bakwBound[0][1], bakwYUnit, gridNum);
|
|
243
|
+
for (let cx = normXMin; cx <= normXMax; cx++) {
|
|
244
|
+
if (!prev[cx]) prev[cx] = [];
|
|
245
|
+
for (let cy = normYMin; cy <= normYMax; cy++) {
|
|
246
|
+
if (!prev[cx][cy]) prev[cx][cy] = [];
|
|
247
|
+
prev[cx][cy].push(index);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return prev;
|
|
251
|
+
},
|
|
252
|
+
[]
|
|
253
|
+
);
|
|
254
|
+
this.indexedTins = {
|
|
255
|
+
forw: {
|
|
256
|
+
gridNum,
|
|
257
|
+
xOrigin: forwBound[0][0],
|
|
258
|
+
yOrigin: forwBound[0][1],
|
|
259
|
+
xUnit: forwXUnit,
|
|
260
|
+
yUnit: forwYUnit,
|
|
261
|
+
gridCache: forwGridCache
|
|
262
|
+
},
|
|
263
|
+
bakw: {
|
|
264
|
+
gridNum,
|
|
265
|
+
xOrigin: bakwBound[0][0],
|
|
266
|
+
yOrigin: bakwBound[0][1],
|
|
267
|
+
xUnit: bakwXUnit,
|
|
268
|
+
yUnit: bakwYUnit,
|
|
269
|
+
gridCache: bakwGridCache
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* 座標変換を実行します
|
|
276
|
+
*
|
|
277
|
+
* @param apoint - 変換する座標
|
|
278
|
+
* @param backward - 逆方向の変換かどうか
|
|
279
|
+
* @param ignoreBounds - 境界チェックを無視するかどうか
|
|
280
|
+
* @returns 変換後の座標、または境界外の場合はfalse
|
|
281
|
+
*
|
|
282
|
+
* @throws {Error} 逆方向変換が許可されていない状態での逆変換時
|
|
283
|
+
*/
|
|
284
|
+
transform(apoint: number[], backward?: boolean, ignoreBounds?: boolean): number[] | false {
|
|
285
|
+
if (!this.tins)
|
|
286
|
+
throw new Error("setCompiled() must be called before transform()");
|
|
287
|
+
if (backward && this.strict_status == Transform.STATUS_ERROR)
|
|
288
|
+
throw new Error('Backward transform is not allowed if strict_status == "strict_error"');
|
|
289
|
+
if (this.yaxisMode == Transform.YAXIS_FOLLOW && backward) {
|
|
290
|
+
apoint = [apoint[0], -1 * apoint[1]];
|
|
291
|
+
}
|
|
292
|
+
const tpoint = point(apoint);
|
|
293
|
+
if (this.bounds && !backward && !ignoreBounds) {
|
|
294
|
+
if (!booleanPointInPolygon(tpoint, this.boundsPolygon!)) return false;
|
|
295
|
+
}
|
|
296
|
+
const tins = backward ? this.tins!.bakw : this.tins!.forw;
|
|
297
|
+
const indexedTins = backward
|
|
298
|
+
? this.indexedTins!.bakw
|
|
299
|
+
: this.indexedTins!.forw;
|
|
300
|
+
const verticesParams = backward
|
|
301
|
+
? this.vertices_params!.bakw
|
|
302
|
+
: this.vertices_params!.forw;
|
|
303
|
+
const centroid = backward ? this.centroid!.bakw : this.centroid!.forw;
|
|
304
|
+
const weightBuffer = backward
|
|
305
|
+
? this.pointsWeightBuffer!.bakw
|
|
306
|
+
: this.pointsWeightBuffer!.forw;
|
|
307
|
+
let stateTriangle = undefined,
|
|
308
|
+
stateSetFunc = undefined;
|
|
309
|
+
if (this.stateFull) {
|
|
310
|
+
if (this.stateBackward == backward) {
|
|
311
|
+
stateTriangle = this.stateTriangle;
|
|
312
|
+
} else {
|
|
313
|
+
this.stateBackward = backward;
|
|
314
|
+
this.stateTriangle = undefined;
|
|
315
|
+
}
|
|
316
|
+
stateSetFunc = (tri?: Tri) => {
|
|
317
|
+
this.stateTriangle = tri;
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
let ret = transformArr(
|
|
321
|
+
tpoint,
|
|
322
|
+
tins!,
|
|
323
|
+
indexedTins,
|
|
324
|
+
verticesParams,
|
|
325
|
+
centroid,
|
|
326
|
+
weightBuffer,
|
|
327
|
+
stateTriangle,
|
|
328
|
+
stateSetFunc
|
|
329
|
+
);
|
|
330
|
+
if (this.bounds && backward && !ignoreBounds) {
|
|
331
|
+
const rpoint = point(ret);
|
|
332
|
+
if (!booleanPointInPolygon(rpoint, this.boundsPolygon!)) return false;
|
|
333
|
+
} else if (this.yaxisMode == Transform.YAXIS_FOLLOW && !backward) {
|
|
334
|
+
ret = [ret[0], -1 * ret[1]];
|
|
335
|
+
}
|
|
336
|
+
return ret;
|
|
337
|
+
}
|
|
338
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -120,3 +120,43 @@ export interface LegacyStatePayload {
|
|
|
120
120
|
centroid?: CentroidBD;
|
|
121
121
|
kinks?: KinksBD;
|
|
122
122
|
}
|
|
123
|
+
|
|
124
|
+
// ─── MapTransform types ───────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Viewport representation: center in mercator, zoom level, rotation in radians.
|
|
128
|
+
*/
|
|
129
|
+
export interface Viewpoint {
|
|
130
|
+
/** Mercator coordinate [x, y] */
|
|
131
|
+
center: number[];
|
|
132
|
+
/** Mercator zoom level */
|
|
133
|
+
zoom: number;
|
|
134
|
+
/** Rotation angle in radians */
|
|
135
|
+
rotation: number;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Sub-map TIN data for MapTransform.setMapData().
|
|
140
|
+
*/
|
|
141
|
+
export interface SubMapData {
|
|
142
|
+
/** Compiled TIN data */
|
|
143
|
+
compiled: Compiled;
|
|
144
|
+
/** Layer priority (higher = checked first) */
|
|
145
|
+
priority: number;
|
|
146
|
+
/** Layer importance (used when multiple layers overlap) */
|
|
147
|
+
importance: number;
|
|
148
|
+
/** Bounds vertices in pixel (XY) space. Falls back to compiled.bounds if omitted. */
|
|
149
|
+
bounds?: number[][];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Input data for MapTransform.setMapData().
|
|
154
|
+
*/
|
|
155
|
+
export interface MapData {
|
|
156
|
+
/** Compiled TIN data for the main layer */
|
|
157
|
+
compiled: Compiled;
|
|
158
|
+
/** maxZoom value used to compute _maxxy = 2^maxZoom * 256 */
|
|
159
|
+
maxZoom?: number;
|
|
160
|
+
/** Sub-map layers */
|
|
161
|
+
sub_maps?: SubMapData[];
|
|
162
|
+
}
|
package/src/viewpoint.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { MERC_MAX, MERC_CROSSMATRIX } from "./constants.ts";
|
|
2
|
+
import type { Viewpoint } from "./types.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* size(画面サイズ)とズームから、地図面座標上での半径を得る
|
|
6
|
+
*
|
|
7
|
+
* @param size - 画面サイズ [width, height]
|
|
8
|
+
* @param zoom - メルカトルズームレベル
|
|
9
|
+
* @returns メルカトル座標上での半径
|
|
10
|
+
*/
|
|
11
|
+
export function zoom2Radius(size: [number, number], zoom: number): number {
|
|
12
|
+
const radius = Math.floor(Math.min(size[0], size[1]) / 4);
|
|
13
|
+
return (radius * MERC_MAX) / 128 / Math.pow(2, zoom);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 与えられた差分行列を回転する
|
|
18
|
+
*
|
|
19
|
+
* @param xys - 回転する座標の配列
|
|
20
|
+
* @param theta - 回転角(ラジアン)
|
|
21
|
+
* @returns 回転後の座標の配列
|
|
22
|
+
*/
|
|
23
|
+
export function rotateMatrix(xys: number[][], theta: number): number[][] {
|
|
24
|
+
const result: number[][] = [];
|
|
25
|
+
for (let i = 0; i < xys.length; i++) {
|
|
26
|
+
const xy = xys[i];
|
|
27
|
+
const x = xy[0] * Math.cos(theta) - xy[1] * Math.sin(theta);
|
|
28
|
+
const y = xy[0] * Math.sin(theta) + xy[1] * Math.cos(theta);
|
|
29
|
+
result.push([x, y]);
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 画面サイズと地図ズームから、メルカトル座標上での5座標を取得する
|
|
36
|
+
*
|
|
37
|
+
* @param center - 中心のメルカトル座標 [x, y]
|
|
38
|
+
* @param zoom - メルカトルズームレベル
|
|
39
|
+
* @param rotation - 回転角(ラジアン)
|
|
40
|
+
* @param size - 画面サイズ [width, height]
|
|
41
|
+
* @returns 中心+上下左右の5点のメルカトル座標配列
|
|
42
|
+
*/
|
|
43
|
+
export function mercViewpoint2Mercs(
|
|
44
|
+
center: number[],
|
|
45
|
+
zoom: number,
|
|
46
|
+
rotation: number,
|
|
47
|
+
size: [number, number]
|
|
48
|
+
): number[][] {
|
|
49
|
+
const radius = zoom2Radius(size, zoom);
|
|
50
|
+
const crossDelta = rotateMatrix(MERC_CROSSMATRIX, rotation);
|
|
51
|
+
const cross = crossDelta.map(xy => [
|
|
52
|
+
xy[0] * radius + center[0],
|
|
53
|
+
xy[1] * radius + center[1]
|
|
54
|
+
]);
|
|
55
|
+
return cross;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* メルカトル5地点情報からメルカトル地図でのサイズ情報(中心座標、ズーム、回転)を得る
|
|
60
|
+
*
|
|
61
|
+
* @param mercs - 中心+上下左右の5点のメルカトル座標配列
|
|
62
|
+
* @param size - 画面サイズ [width, height]
|
|
63
|
+
* @returns Viewpoint オブジェクト(center, zoom, rotation)
|
|
64
|
+
*/
|
|
65
|
+
export function mercs2MercViewpoint(
|
|
66
|
+
mercs: number[][],
|
|
67
|
+
size: [number, number]
|
|
68
|
+
): Viewpoint {
|
|
69
|
+
const center = mercs[0];
|
|
70
|
+
const nesw = mercs.slice(1, 5);
|
|
71
|
+
const neswDelta = nesw.map(val => [
|
|
72
|
+
val[0] - center[0],
|
|
73
|
+
val[1] - center[1]
|
|
74
|
+
]);
|
|
75
|
+
const normal = [
|
|
76
|
+
[0.0, 1.0],
|
|
77
|
+
[1.0, 0.0],
|
|
78
|
+
[0.0, -1.0],
|
|
79
|
+
[-1.0, 0.0]
|
|
80
|
+
];
|
|
81
|
+
let abss = 0;
|
|
82
|
+
let cosx = 0;
|
|
83
|
+
let sinx = 0;
|
|
84
|
+
for (let i = 0; i < 4; i++) {
|
|
85
|
+
const delta = neswDelta[i];
|
|
86
|
+
const norm = normal[i];
|
|
87
|
+
const abs = Math.sqrt(Math.pow(delta[0], 2) + Math.pow(delta[1], 2));
|
|
88
|
+
abss += abs;
|
|
89
|
+
const outer = delta[0] * norm[1] - delta[1] * norm[0];
|
|
90
|
+
const inner = Math.acos(
|
|
91
|
+
(delta[0] * norm[0] + delta[1] * norm[1]) / abs
|
|
92
|
+
);
|
|
93
|
+
const theta = outer > 0.0 ? -1.0 * inner : inner;
|
|
94
|
+
cosx += Math.cos(theta);
|
|
95
|
+
sinx += Math.sin(theta);
|
|
96
|
+
}
|
|
97
|
+
const scale = abss / 4.0;
|
|
98
|
+
const omega = Math.atan2(sinx, cosx);
|
|
99
|
+
|
|
100
|
+
const radius = Math.floor(Math.min(size[0], size[1]) / 4);
|
|
101
|
+
const zoom = Math.log((radius * MERC_MAX) / 128 / scale) / Math.log(2);
|
|
102
|
+
|
|
103
|
+
return { center, zoom, rotation: omega };
|
|
104
|
+
}
|