@maplat/transform 0.2.2 → 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/geometry.ts CHANGED
@@ -1,249 +1,249 @@
1
- import booleanPointInPolygon from "@turf/boolean-point-in-polygon";
2
- import { featureCollection } from "@turf/helpers";
3
- import type { Feature, FeatureCollection, Polygon, Point, Position } from "geojson";
4
- //import { Tri, Tins, IndexedTins, WeightBuffer, VerticesParams } from "./index";
5
- type PropertyTri = { geom: Position; index: number | string };
6
- export type PropertyTriKey = "a" | "b" | "c";
7
- type PropertiesTri = { [key in PropertyTriKey]: PropertyTri };
8
- export type Tri = Feature<Polygon, PropertiesTri>;
9
- export type Tins = FeatureCollection<Polygon, PropertiesTri>;
10
- export type WeightBuffer = { [index: string]: number };
11
- export type VerticesParams = [number[], Tins[]?];
12
- export interface IndexedTins {
13
- gridNum: number;
14
- xOrigin: number;
15
- yOrigin: number;
16
- xUnit: number;
17
- yUnit: number;
18
- gridCache: number[][][];
19
- }
20
-
21
- /**
22
- * 指定された点が三角形の内部に存在するかを判定し、該当する三角形を返す
23
- * @param point 判定する点
24
- * @param tins 三角形群
25
- * @returns 点を含む三角形、または未定義(点が三角形群の外部にある場合)
26
- */
27
- function hit(point: Feature<Point>, tins: Tins): Tri | undefined {
28
- for (let i = 0; i < tins.features.length; i++) {
29
- const inside = booleanPointInPolygon(point, tins.features[i]);
30
- if (inside) {
31
- return tins.features[i];
32
- }
33
- }
34
- }
35
-
36
- /**
37
- * 三角形内の点の座標を、対応する三角形の座標系に変換する
38
- * @param of 変換する点
39
- * @param tri 三角形
40
- * @param weightBuffer 重み付けバッファ(オプション)
41
- * @returns 変換後の座標
42
- */
43
- function transformTinArr(of: Feature<Point>, tri: Tri, weightBuffer: WeightBuffer | undefined) {
44
- const a = tri.geometry.coordinates[0][0];
45
- const b = tri.geometry.coordinates[0][1];
46
- const c = tri.geometry.coordinates[0][2];
47
- const o = of.geometry.coordinates;
48
- const ad = tri.properties.a.geom;
49
- const bd = tri.properties.b.geom;
50
- const cd = tri.properties.c.geom;
51
- const ab = [b[0] - a[0], b[1] - a[1]];
52
- const ac = [c[0] - a[0], c[1] - a[1]];
53
- const ao = [o[0] - a[0], o[1] - a[1]];
54
- const abd = [bd[0] - ad[0], bd[1] - ad[1]];
55
- const acd = [cd[0] - ad[0], cd[1] - ad[1]];
56
- let abv = (ac[1] * ao[0] - ac[0] * ao[1]) / (ab[0] * ac[1] - ab[1] * ac[0]);
57
- let acv = (ab[0] * ao[1] - ab[1] * ao[0]) / (ab[0] * ac[1] - ab[1] * ac[0]);
58
-
59
- // 重み付けがある場合は補正を行う
60
- if (weightBuffer) {
61
- const aW = weightBuffer[tri.properties.a.index];
62
- const bW = weightBuffer[tri.properties.b.index];
63
- const cW = weightBuffer[tri.properties.c.index];
64
- let nabv;
65
- if (abv < 0 || acv < 0 || 1 - abv - acv < 0) {
66
- const normB = abv / (abv + acv);
67
- const normC = acv / (abv + acv);
68
- nabv = abv / bW / (normB / bW + normC / cW);
69
- acv = acv / cW / (normB / bW + normC / cW);
70
- } else {
71
- nabv = abv / bW / (abv / bW + acv / cW + (1 - abv - acv) / aW);
72
- acv = acv / cW / (abv / bW + acv / cW + (1 - abv - acv) / aW);
73
- }
74
- abv = nabv;
75
- }
76
- return [
77
- abv * abd[0] + acv * acd[0] + ad[0],
78
- abv * abd[1] + acv * acd[1] + ad[1]
79
- ];
80
- }
81
-
82
- /**
83
- * 頂点パラメータを使用して点の座標を変換する
84
- * @param o 変換する点
85
- * @param verticesParams 頂点パラメータ
86
- * @param centroid 重心点
87
- * @param weightBuffer 重み付けバッファ
88
- * @returns 変換後の座標
89
- */
90
- function useVerticesArr(
91
- o: Feature<Point>,
92
- verticesParams: VerticesParams,
93
- centroid: Feature<Point>,
94
- weightBuffer: WeightBuffer
95
- ): Position {
96
- const coord = o.geometry!.coordinates;
97
- const centCoord = centroid.geometry!.coordinates;
98
- const radian = Math.atan2(coord[0] - centCoord[0], coord[1] - centCoord[1]);
99
- const index = decideUseVertex(radian, verticesParams[0]);
100
- if (index === undefined) {
101
- throw new Error("Unable to determine vertex index");
102
- }
103
- const tin = verticesParams[1]![index];
104
- return transformTinArr(o, tin.features[0], weightBuffer);
105
- }
106
-
107
- /**
108
- * 点の座標を変換する
109
- * 点が三角形の内部にある場合は三角形による変換を、
110
- * 外部にある場合は頂点パラメータによる変換を行う
111
- * @param point 変換する点
112
- * @param tins 三角形群
113
- * @param indexedTins インデックス付き三角形群(オプション)
114
- * @param verticesParams 頂点パラメータ(オプション)
115
- * @param centroid 重心点(オプション)
116
- * @param weightBuffer 重み付けバッファ(オプション)
117
- * @param stateTriangle 状態三角形(オプション)
118
- * @param stateSetFunc 状態設定関数(オプション)
119
- * @returns 変換後の座標
120
- */
121
- function transformArr(
122
- point: Feature<Point>,
123
- tins: Tins,
124
- indexedTins?: IndexedTins,
125
- verticesParams?: VerticesParams,
126
- centroid?: Feature<Point>,
127
- weightBuffer?: WeightBuffer,
128
- stateTriangle?: Tri,
129
- stateSetFunc?: (tri?: Tri) => void
130
- ): Position {
131
- let tin: Tri | undefined;
132
- if (stateTriangle) {
133
- tin = hit(point, featureCollection([stateTriangle]));
134
- }
135
- if (!tin) {
136
- if (indexedTins) {
137
- const coords = point.geometry!.coordinates;
138
- const gridNum = indexedTins.gridNum;
139
- const xOrigin = indexedTins.xOrigin;
140
- const yOrigin = indexedTins.yOrigin;
141
- const xUnit = indexedTins.xUnit;
142
- const yUnit = indexedTins.yUnit;
143
- const gridCache = indexedTins.gridCache;
144
- const normX = unitCalc(coords[0], xOrigin, xUnit, gridNum);
145
- const normY = unitCalc(coords[1], yOrigin, yUnit, gridNum);
146
- const tinsKey = gridCache[normX]
147
- ? gridCache[normX][normY]
148
- ? gridCache[normX][normY]
149
- : []
150
- : [];
151
- tins = featureCollection(tinsKey.map((key: number) => tins.features[key]));
152
- }
153
- tin = hit(point, tins);
154
- }
155
- if (stateSetFunc) stateSetFunc(tin);
156
- return tin
157
- ? transformTinArr(point, tin, weightBuffer)
158
- : useVerticesArr(point, verticesParams!, centroid!, weightBuffer!);
159
- }
160
-
161
- /**
162
- * 座標値をグリッド単位に正規化する
163
- * @param coord 正規化する座標値
164
- * @param origin 原点座標
165
- * @param unit グリッド単位
166
- * @param gridNum グリッド数
167
- * @returns 正規化された座標値
168
- */
169
- function unitCalc(
170
- coord: number,
171
- origin: number,
172
- unit: number,
173
- gridNum: number
174
- ) {
175
- let normCoord = Math.floor((coord - origin) / unit);
176
- if (normCoord >= gridNum) normCoord = gridNum - 1;
177
- return normCoord;
178
- }
179
-
180
- /**
181
- * 与えられた角度に最も近い頂点のインデックスを決定する
182
- *
183
- * 目標の角度(radian)と頂点リストの角度(radianList)を比較し、
184
- * 目標の角度を挟む2つの頂点のうち、より近い方の頂点を選択する
185
- *
186
- * @param radian 目標の角度(ラジアン)
187
- * @param radianList 頂点の角度リスト(ラジアン)
188
- * @returns 最適な頂点のインデックス。適切な頂点が見つからない場合はundefined
189
- *
190
- * @example
191
- * // 例: 0.5ラジアンの角度に対して、[0, π/2, π, 3π/2]の頂点リストから最適な頂点を選択
192
- * const index = decideUseVertex(0.5, [0, Math.PI/2, Math.PI, Math.PI*3/2]);
193
- * // returns 0 (最初の頂点が最も近い)
194
- */
195
- function decideUseVertex(radian: number, radianList: number[]): number | undefined {
196
- // 最初の頂点との角度差を正規化
197
- let idel = normalizeRadian(radian - radianList[0]);
198
- let minTheta = Math.PI * 2; // 最小角度差の初期値
199
- let minIndex; // 最適な頂点のインデックス
200
-
201
- // すべての隣接する頂点ペアをチェック
202
- for (let i = 0; i < radianList.length; i++) {
203
- const j = (i + 1) % radianList.length; // 次の頂点のインデックス(最後の頂点の次は最初の頂点)
204
- const jdel = normalizeRadian(radian - radianList[j]); // 次の頂点との角度差
205
- const minDel = Math.min(Math.abs(idel), Math.abs(jdel)); // 現在の頂点ペアとの最小角度差
206
-
207
- // 目標角度が現在の頂点ペア間にあり(idel * jdel <= 0)、
208
- // かつ、これまでの最小角度差より小さい場合
209
- if (idel * jdel <= 0 && minDel < minTheta) {
210
- minTheta = minDel;
211
- minIndex = i;
212
- }
213
- idel = jdel; // 次のイテレーションのために現在の角度差を保存
214
- }
215
- return minIndex;
216
- }
217
-
218
- /**
219
- * 角度を指定された範囲に正規化する
220
- *
221
- * @param target 正規化する角度(ラジアン)
222
- * @param noNegative trueの場合は[0, 2π)の範囲に、falseの場合は(-π, π]の範囲に正規化
223
- * @returns 正規化された角度(ラジアン)
224
- *
225
- * @example
226
- * // [-π, π]の範囲に正規化
227
- * normalizeRadian(3 * Math.PI); // returns -π
228
- *
229
- * // [0, 2π)の範囲に正規化
230
- * normalizeRadian(3 * Math.PI, true); // returns π
231
- */
232
- function normalizeRadian(target: number, noNegative = false): number {
233
- // 正規化の範囲を決定する関数
234
- const rangeFunc = noNegative
235
- ? function (val: number) {
236
- return !(val >= 0 && val < Math.PI * 2); // [0, 2π)の範囲外
237
- }
238
- : function (val: number) {
239
- return !(val > -1 * Math.PI && val <= Math.PI); // (-π, π]の範囲外
240
- };
241
-
242
- // 範囲内に収まるまで2πを加減算
243
- while (rangeFunc(target)) {
244
- target = target + 2 * Math.PI * (target > 0 ? -1 : 1);
245
- }
246
- return target;
247
- }
248
-
1
+ import booleanPointInPolygon from "@turf/boolean-point-in-polygon";
2
+ import { featureCollection } from "@turf/helpers";
3
+ import type { Feature, FeatureCollection, Polygon, Point, Position } from "geojson";
4
+ //import { Tri, Tins, IndexedTins, WeightBuffer, VerticesParams } from "./index";
5
+ type PropertyTri = { geom: Position; index: number | string };
6
+ export type PropertyTriKey = "a" | "b" | "c";
7
+ type PropertiesTri = { [key in PropertyTriKey]: PropertyTri };
8
+ export type Tri = Feature<Polygon, PropertiesTri>;
9
+ export type Tins = FeatureCollection<Polygon, PropertiesTri>;
10
+ export type WeightBuffer = { [index: string]: number };
11
+ export type VerticesParams = [number[], Tins[]?];
12
+ export interface IndexedTins {
13
+ gridNum: number;
14
+ xOrigin: number;
15
+ yOrigin: number;
16
+ xUnit: number;
17
+ yUnit: number;
18
+ gridCache: number[][][];
19
+ }
20
+
21
+ /**
22
+ * 指定された点が三角形の内部に存在するかを判定し、該当する三角形を返す
23
+ * @param point 判定する点
24
+ * @param tins 三角形群
25
+ * @returns 点を含む三角形、または未定義(点が三角形群の外部にある場合)
26
+ */
27
+ function hit(point: Feature<Point>, tins: Tins): Tri | undefined {
28
+ for (let i = 0; i < tins.features.length; i++) {
29
+ const inside = booleanPointInPolygon(point, tins.features[i]);
30
+ if (inside) {
31
+ return tins.features[i];
32
+ }
33
+ }
34
+ }
35
+
36
+ /**
37
+ * 三角形内の点の座標を、対応する三角形の座標系に変換する
38
+ * @param of 変換する点
39
+ * @param tri 三角形
40
+ * @param weightBuffer 重み付けバッファ(オプション)
41
+ * @returns 変換後の座標
42
+ */
43
+ function transformTinArr(of: Feature<Point>, tri: Tri, weightBuffer: WeightBuffer | undefined) {
44
+ const a = tri.geometry.coordinates[0][0];
45
+ const b = tri.geometry.coordinates[0][1];
46
+ const c = tri.geometry.coordinates[0][2];
47
+ const o = of.geometry.coordinates;
48
+ const ad = tri.properties.a.geom;
49
+ const bd = tri.properties.b.geom;
50
+ const cd = tri.properties.c.geom;
51
+ const ab = [b[0] - a[0], b[1] - a[1]];
52
+ const ac = [c[0] - a[0], c[1] - a[1]];
53
+ const ao = [o[0] - a[0], o[1] - a[1]];
54
+ const abd = [bd[0] - ad[0], bd[1] - ad[1]];
55
+ const acd = [cd[0] - ad[0], cd[1] - ad[1]];
56
+ let abv = (ac[1] * ao[0] - ac[0] * ao[1]) / (ab[0] * ac[1] - ab[1] * ac[0]);
57
+ let acv = (ab[0] * ao[1] - ab[1] * ao[0]) / (ab[0] * ac[1] - ab[1] * ac[0]);
58
+
59
+ // 重み付けがある場合は補正を行う
60
+ if (weightBuffer) {
61
+ const aW = weightBuffer[tri.properties.a.index];
62
+ const bW = weightBuffer[tri.properties.b.index];
63
+ const cW = weightBuffer[tri.properties.c.index];
64
+ let nabv;
65
+ if (abv < 0 || acv < 0 || 1 - abv - acv < 0) {
66
+ const normB = abv / (abv + acv);
67
+ const normC = acv / (abv + acv);
68
+ nabv = abv / bW / (normB / bW + normC / cW);
69
+ acv = acv / cW / (normB / bW + normC / cW);
70
+ } else {
71
+ nabv = abv / bW / (abv / bW + acv / cW + (1 - abv - acv) / aW);
72
+ acv = acv / cW / (abv / bW + acv / cW + (1 - abv - acv) / aW);
73
+ }
74
+ abv = nabv;
75
+ }
76
+ return [
77
+ abv * abd[0] + acv * acd[0] + ad[0],
78
+ abv * abd[1] + acv * acd[1] + ad[1]
79
+ ];
80
+ }
81
+
82
+ /**
83
+ * 頂点パラメータを使用して点の座標を変換する
84
+ * @param o 変換する点
85
+ * @param verticesParams 頂点パラメータ
86
+ * @param centroid 重心点
87
+ * @param weightBuffer 重み付けバッファ
88
+ * @returns 変換後の座標
89
+ */
90
+ function useVerticesArr(
91
+ o: Feature<Point>,
92
+ verticesParams: VerticesParams,
93
+ centroid: Feature<Point>,
94
+ weightBuffer: WeightBuffer
95
+ ): Position {
96
+ const coord = o.geometry!.coordinates;
97
+ const centCoord = centroid.geometry!.coordinates;
98
+ const radian = Math.atan2(coord[0] - centCoord[0], coord[1] - centCoord[1]);
99
+ const index = decideUseVertex(radian, verticesParams[0]);
100
+ if (index === undefined) {
101
+ throw new Error("Unable to determine vertex index");
102
+ }
103
+ const tin = verticesParams[1]![index];
104
+ return transformTinArr(o, tin.features[0], weightBuffer);
105
+ }
106
+
107
+ /**
108
+ * 点の座標を変換する
109
+ * 点が三角形の内部にある場合は三角形による変換を、
110
+ * 外部にある場合は頂点パラメータによる変換を行う
111
+ * @param point 変換する点
112
+ * @param tins 三角形群
113
+ * @param indexedTins インデックス付き三角形群(オプション)
114
+ * @param verticesParams 頂点パラメータ(オプション)
115
+ * @param centroid 重心点(オプション)
116
+ * @param weightBuffer 重み付けバッファ(オプション)
117
+ * @param stateTriangle 状態三角形(オプション)
118
+ * @param stateSetFunc 状態設定関数(オプション)
119
+ * @returns 変換後の座標
120
+ */
121
+ function transformArr(
122
+ point: Feature<Point>,
123
+ tins: Tins,
124
+ indexedTins?: IndexedTins,
125
+ verticesParams?: VerticesParams,
126
+ centroid?: Feature<Point>,
127
+ weightBuffer?: WeightBuffer,
128
+ stateTriangle?: Tri,
129
+ stateSetFunc?: (tri?: Tri) => void
130
+ ): Position {
131
+ let tin: Tri | undefined;
132
+ if (stateTriangle) {
133
+ tin = hit(point, featureCollection([stateTriangle]));
134
+ }
135
+ if (!tin) {
136
+ if (indexedTins) {
137
+ const coords = point.geometry!.coordinates;
138
+ const gridNum = indexedTins.gridNum;
139
+ const xOrigin = indexedTins.xOrigin;
140
+ const yOrigin = indexedTins.yOrigin;
141
+ const xUnit = indexedTins.xUnit;
142
+ const yUnit = indexedTins.yUnit;
143
+ const gridCache = indexedTins.gridCache;
144
+ const normX = unitCalc(coords[0], xOrigin, xUnit, gridNum);
145
+ const normY = unitCalc(coords[1], yOrigin, yUnit, gridNum);
146
+ const tinsKey = gridCache[normX]
147
+ ? gridCache[normX][normY]
148
+ ? gridCache[normX][normY]
149
+ : []
150
+ : [];
151
+ tins = featureCollection(tinsKey.map((key: number) => tins.features[key]));
152
+ }
153
+ tin = hit(point, tins);
154
+ }
155
+ if (stateSetFunc) stateSetFunc(tin);
156
+ return tin
157
+ ? transformTinArr(point, tin, weightBuffer)
158
+ : useVerticesArr(point, verticesParams!, centroid!, weightBuffer!);
159
+ }
160
+
161
+ /**
162
+ * 座標値をグリッド単位に正規化する
163
+ * @param coord 正規化する座標値
164
+ * @param origin 原点座標
165
+ * @param unit グリッド単位
166
+ * @param gridNum グリッド数
167
+ * @returns 正規化された座標値
168
+ */
169
+ function unitCalc(
170
+ coord: number,
171
+ origin: number,
172
+ unit: number,
173
+ gridNum: number
174
+ ) {
175
+ let normCoord = Math.floor((coord - origin) / unit);
176
+ if (normCoord >= gridNum) normCoord = gridNum - 1;
177
+ return normCoord;
178
+ }
179
+
180
+ /**
181
+ * 与えられた角度に最も近い頂点のインデックスを決定する
182
+ *
183
+ * 目標の角度(radian)と頂点リストの角度(radianList)を比較し、
184
+ * 目標の角度を挟む2つの頂点のうち、より近い方の頂点を選択する
185
+ *
186
+ * @param radian 目標の角度(ラジアン)
187
+ * @param radianList 頂点の角度リスト(ラジアン)
188
+ * @returns 最適な頂点のインデックス。適切な頂点が見つからない場合はundefined
189
+ *
190
+ * @example
191
+ * // 例: 0.5ラジアンの角度に対して、[0, π/2, π, 3π/2]の頂点リストから最適な頂点を選択
192
+ * const index = decideUseVertex(0.5, [0, Math.PI/2, Math.PI, Math.PI*3/2]);
193
+ * // returns 0 (最初の頂点が最も近い)
194
+ */
195
+ function decideUseVertex(radian: number, radianList: number[]): number | undefined {
196
+ // 最初の頂点との角度差を正規化
197
+ let idel = normalizeRadian(radian - radianList[0]);
198
+ let minTheta = Math.PI * 2; // 最小角度差の初期値
199
+ let minIndex; // 最適な頂点のインデックス
200
+
201
+ // すべての隣接する頂点ペアをチェック
202
+ for (let i = 0; i < radianList.length; i++) {
203
+ const j = (i + 1) % radianList.length; // 次の頂点のインデックス(最後の頂点の次は最初の頂点)
204
+ const jdel = normalizeRadian(radian - radianList[j]); // 次の頂点との角度差
205
+ const minDel = Math.min(Math.abs(idel), Math.abs(jdel)); // 現在の頂点ペアとの最小角度差
206
+
207
+ // 目標角度が現在の頂点ペア間にあり(idel * jdel <= 0)、
208
+ // かつ、これまでの最小角度差より小さい場合
209
+ if (idel * jdel <= 0 && minDel < minTheta) {
210
+ minTheta = minDel;
211
+ minIndex = i;
212
+ }
213
+ idel = jdel; // 次のイテレーションのために現在の角度差を保存
214
+ }
215
+ return minIndex;
216
+ }
217
+
218
+ /**
219
+ * 角度を指定された範囲に正規化する
220
+ *
221
+ * @param target 正規化する角度(ラジアン)
222
+ * @param noNegative trueの場合は[0, 2π)の範囲に、falseの場合は(-π, π]の範囲に正規化
223
+ * @returns 正規化された角度(ラジアン)
224
+ *
225
+ * @example
226
+ * // [-π, π]の範囲に正規化
227
+ * normalizeRadian(3 * Math.PI); // returns -π
228
+ *
229
+ * // [0, 2π)の範囲に正規化
230
+ * normalizeRadian(3 * Math.PI, true); // returns π
231
+ */
232
+ function normalizeRadian(target: number, noNegative = false): number {
233
+ // 正規化の範囲を決定する関数
234
+ const rangeFunc = noNegative
235
+ ? function (val: number) {
236
+ return !(val >= 0 && val < Math.PI * 2); // [0, 2π)の範囲外
237
+ }
238
+ : function (val: number) {
239
+ return !(val > -1 * Math.PI && val <= Math.PI); // (-π, π]の範囲外
240
+ };
241
+
242
+ // 範囲内に収まるまで2πを加減算
243
+ while (rangeFunc(target)) {
244
+ target = target + 2 * Math.PI * (target > 0 ? -1 : 1);
245
+ }
246
+ return target;
247
+ }
248
+
249
249
  export { transformArr, unitCalc };