cesium-xs-sdk 1.0.0
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.md +104 -0
- package/dist/cesium-xs-sdk.cjs.js +7387 -0
- package/dist/cesium-xs-sdk.cjs.js.map +1 -0
- package/dist/cesium-xs-sdk.esm.js +7383 -0
- package/dist/cesium-xs-sdk.esm.js.map +1 -0
- package/dist/cesium-xs-sdk.umd.js +2 -0
- package/dist/cesium-xs-sdk.umd.js.map +1 -0
- package/package.json +78 -0
- package/src/core/Config.js +176 -0
- package/src/core/ModuleManager.js +157 -0
- package/src/index.d.ts +358 -0
- package/src/index.js +59 -0
- package/src/modules/EditorModule.js +370 -0
- package/src/modules/EventModule.js +211 -0
- package/src/modules/GraphicModule.js +1037 -0
- package/src/modules/PlotGraphicType.js +43 -0
- package/src/modules/SceneModule.js +600 -0
- package/src/modules/tools/BaseTool.js +155 -0
- package/src/modules/tools/DeleteTool.js +252 -0
- package/src/modules/tools/DrawTool.js +961 -0
- package/src/modules/tools/EditTool.js +1585 -0
- package/src/modules/tools/SelectTool.js +526 -0
- package/src/react.d.ts +18 -0
- package/src/react.js +49 -0
- package/src/utils/CoordinateUtil.js +63 -0
- package/src/utils/GeoCalcUtil.js +95 -0
- package/src/utils/PlotGeometryUtil.js +1134 -0
- package/src/vue.d.ts +18 -0
- package/src/vue.js +76 -0
- package/src/vue2.d.ts +20 -0
- package/src/vue2.js +75 -0
|
@@ -0,0 +1,1134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 标绘几何算法工具
|
|
3
|
+
* 移植自 cesium-plot-js 的几何生成能力
|
|
4
|
+
*
|
|
5
|
+
* 设计约定:
|
|
6
|
+
* - 输入控制点:[lon, lat] 或 [lon, lat, height]
|
|
7
|
+
* - 算法内部在经纬度平面上运算(与插件保持一致)
|
|
8
|
+
* - 输出:[[lon, lat, height], ...] 或 Cesium.Cartesian3[]
|
|
9
|
+
*
|
|
10
|
+
* 注意:标绘算法内部使用平面距离计算,不要替换为 GeoCalcUtil.calcDistance
|
|
11
|
+
*/
|
|
12
|
+
export class PlotGeometryUtil {
|
|
13
|
+
// ==================== 常量 ====================
|
|
14
|
+
|
|
15
|
+
static FITTING_COUNT = 100;
|
|
16
|
+
static ZERO_TOLERANCE = 0.0001;
|
|
17
|
+
|
|
18
|
+
// ==================== 基础平面几何工具 ====================
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 平面距离计算
|
|
22
|
+
* 将经纬度视为平面坐标计算两点距离
|
|
23
|
+
*/
|
|
24
|
+
static mathDistance(p1, p2) {
|
|
25
|
+
return Math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 计算点集合的总距离(平面)
|
|
30
|
+
*/
|
|
31
|
+
static wholeDistance(points) {
|
|
32
|
+
let distance = 0;
|
|
33
|
+
if (!points || !Array.isArray(points) || points.length === 0) return distance;
|
|
34
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
35
|
+
distance += this.mathDistance(points[i], points[i + 1]);
|
|
36
|
+
}
|
|
37
|
+
return distance;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 获取基础长度(插件算法中用于计算箭头尺寸)
|
|
42
|
+
*/
|
|
43
|
+
static getBaseLength(points) {
|
|
44
|
+
return this.wholeDistance(points) ** 0.99;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 求两个坐标的中点
|
|
49
|
+
*/
|
|
50
|
+
static mid(p1, p2) {
|
|
51
|
+
return [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 获取方位角(弧度)
|
|
56
|
+
* 将经纬度视为平面坐标计算方向
|
|
57
|
+
*/
|
|
58
|
+
static getAzimuth(startPoint, endPoint) {
|
|
59
|
+
const distance = this.mathDistance(startPoint, endPoint);
|
|
60
|
+
if (distance === 0) return 0;
|
|
61
|
+
|
|
62
|
+
const angle = Math.asin(Math.abs(endPoint[1] - startPoint[1]) / distance);
|
|
63
|
+
|
|
64
|
+
if (endPoint[1] >= startPoint[1] && endPoint[0] >= startPoint[0]) {
|
|
65
|
+
return angle + Math.PI;
|
|
66
|
+
}
|
|
67
|
+
if (endPoint[1] >= startPoint[1] && endPoint[0] < startPoint[0]) {
|
|
68
|
+
return Math.PI * 2 - angle;
|
|
69
|
+
}
|
|
70
|
+
if (endPoint[1] < startPoint[1] && endPoint[0] < startPoint[0]) {
|
|
71
|
+
return angle;
|
|
72
|
+
}
|
|
73
|
+
return Math.PI - angle;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 获取三点夹角(∠ABC)
|
|
78
|
+
*/
|
|
79
|
+
static getAngleOfThreePoints(pntA, pntB, pntC) {
|
|
80
|
+
const angle = this.getAzimuth(pntB, pntA) - this.getAzimuth(pntB, pntC);
|
|
81
|
+
return angle < 0 ? angle + Math.PI * 2 : angle;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 判断三点是否顺时针
|
|
86
|
+
*/
|
|
87
|
+
static isClockWise(pnt1, pnt2, pnt3) {
|
|
88
|
+
return (pnt3[1] - pnt1[1]) * (pnt2[0] - pnt1[0]) > (pnt2[1] - pnt1[1]) * (pnt3[0] - pnt1[0]);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 根据起止点和旋转方向求取第三个点
|
|
93
|
+
* @param {Array} startPnt 起点 [lon, lat]
|
|
94
|
+
* @param {Array} endPnt 终点 [lon, lat]
|
|
95
|
+
* @param {number} angle 旋转角(弧度)
|
|
96
|
+
* @param {number} distance 距离
|
|
97
|
+
* @param {boolean} clockWise 是否顺时针
|
|
98
|
+
* @returns {[number, number]}
|
|
99
|
+
*/
|
|
100
|
+
static getThirdPoint(startPnt, endPnt, angle, distance, clockWise) {
|
|
101
|
+
const azimuth = this.getAzimuth(startPnt, endPnt);
|
|
102
|
+
const alpha = clockWise ? azimuth + angle : azimuth - angle;
|
|
103
|
+
const dx = distance * Math.cos(alpha);
|
|
104
|
+
const dy = distance * Math.sin(alpha);
|
|
105
|
+
return [endPnt[0] + dx, endPnt[1] + dy];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 插值圆弧点(经纬度平面)
|
|
110
|
+
* @param {Array} center 圆心 [lon, lat]
|
|
111
|
+
* @param {number} radius 半径(经纬度平面距离)
|
|
112
|
+
* @param {number} startAngle 起始角(弧度)
|
|
113
|
+
* @param {number} endAngle 结束角(弧度)
|
|
114
|
+
* @param {number} segments 分段数
|
|
115
|
+
* @returns {Array[]}
|
|
116
|
+
*/
|
|
117
|
+
static getArcPoints(center, radius, startAngle, endAngle, segments = 100) {
|
|
118
|
+
let angleDiff = endAngle - startAngle;
|
|
119
|
+
angleDiff = angleDiff < 0 ? angleDiff + Math.PI * 2 : angleDiff;
|
|
120
|
+
|
|
121
|
+
const points = [];
|
|
122
|
+
for (let i = 0; i <= segments; i++) {
|
|
123
|
+
const angle = startAngle + (angleDiff * i) / segments;
|
|
124
|
+
const x = center[0] + radius * Math.cos(angle);
|
|
125
|
+
const y = center[1] + radius * Math.sin(angle);
|
|
126
|
+
points.push([x, y]);
|
|
127
|
+
}
|
|
128
|
+
return points;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 计算两点球面距离(米)
|
|
133
|
+
* 与 _getPointAtBearingAndDistance 使用同一地球半径,保证扇形圆弧闭合一致
|
|
134
|
+
* @param {Array} p1 [lon, lat]
|
|
135
|
+
* @param {Array} p2 [lon, lat]
|
|
136
|
+
* @returns {number}
|
|
137
|
+
*/
|
|
138
|
+
static _calcSphericalDistance(p1, p2) {
|
|
139
|
+
const earthRadius = 6371000;
|
|
140
|
+
const radPerDeg = Math.PI / 180;
|
|
141
|
+
const [lon1, lat1] = [p1[0] * radPerDeg, p1[1] * radPerDeg];
|
|
142
|
+
const [lon2, lat2] = [p2[0] * radPerDeg, p2[1] * radPerDeg];
|
|
143
|
+
const dLat = lat2 - lat1;
|
|
144
|
+
const dLon = lon2 - lon1;
|
|
145
|
+
const a = Math.sin(dLat / 2) ** 2 +
|
|
146
|
+
Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) ** 2;
|
|
147
|
+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
148
|
+
return earthRadius * c;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* 计算真实地理方位角(弧度)
|
|
153
|
+
* 采用与 _getPointAtBearingAndDistance 一致的约定:0=正东,逆时针增大
|
|
154
|
+
* @param {Array} from 起点 [lon, lat]
|
|
155
|
+
* @param {Array} to 终点 [lon, lat]
|
|
156
|
+
* @returns {number} 方位角 [0, 2π)
|
|
157
|
+
*/
|
|
158
|
+
static _getBearing(from, to) {
|
|
159
|
+
const radPerDeg = Math.PI / 180;
|
|
160
|
+
const lon1 = from[0] * radPerDeg;
|
|
161
|
+
const lat1 = from[1] * radPerDeg;
|
|
162
|
+
const lon2 = to[0] * radPerDeg;
|
|
163
|
+
const lat2 = to[1] * radPerDeg;
|
|
164
|
+
|
|
165
|
+
const dLon = lon2 - lon1;
|
|
166
|
+
const y = Math.sin(dLon) * Math.cos(lat2);
|
|
167
|
+
const x = Math.cos(lat1) * Math.sin(lat2) -
|
|
168
|
+
Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLon);
|
|
169
|
+
|
|
170
|
+
// 标准球面前进方位角:0=正北,顺时针增大
|
|
171
|
+
let standardBearing = Math.atan2(y, x);
|
|
172
|
+
if (standardBearing < 0) standardBearing += Math.PI * 2;
|
|
173
|
+
|
|
174
|
+
// 转换为本 SDK 约定:0=正东,逆时针增大
|
|
175
|
+
let angle = Math.PI / 2 - standardBearing;
|
|
176
|
+
if (angle < 0) angle += Math.PI * 2;
|
|
177
|
+
if (angle > Math.PI * 2 - 1e-12) angle = 0;
|
|
178
|
+
return angle;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 按真实地理方位角和距离求目标点(球面直接解法)
|
|
183
|
+
* 约定:0=正东,逆时针增大
|
|
184
|
+
* @param {Array} center 圆心 [lon, lat]
|
|
185
|
+
* @param {number} distanceMeters 距离(米)
|
|
186
|
+
* @param {number} angle 方位角(弧度)
|
|
187
|
+
* @returns {[number, number]}
|
|
188
|
+
*/
|
|
189
|
+
static _getPointAtBearingAndDistance(center, distanceMeters, angle) {
|
|
190
|
+
const earthRadius = 6371000;
|
|
191
|
+
const radPerDeg = Math.PI / 180;
|
|
192
|
+
const lon1 = center[0] * radPerDeg;
|
|
193
|
+
const lat1 = center[1] * radPerDeg;
|
|
194
|
+
const angularDistance = distanceMeters / earthRadius;
|
|
195
|
+
|
|
196
|
+
// 转换回标准球面前进方位角:0=正北,顺时针增大
|
|
197
|
+
const bearing = Math.PI / 2 - angle;
|
|
198
|
+
|
|
199
|
+
const lat2 = Math.asin(
|
|
200
|
+
Math.sin(lat1) * Math.cos(angularDistance) +
|
|
201
|
+
Math.cos(lat1) * Math.sin(angularDistance) * Math.cos(bearing)
|
|
202
|
+
);
|
|
203
|
+
const lon2 = lon1 + Math.atan2(
|
|
204
|
+
Math.sin(bearing) * Math.sin(angularDistance) * Math.cos(lat1),
|
|
205
|
+
Math.cos(angularDistance) - Math.sin(lat1) * Math.sin(lat2)
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
return [lon2 / radPerDeg, lat2 / radPerDeg];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* 在两个端点之间沿真实地理圆弧插值
|
|
213
|
+
* 精确包含 startPoint 和 endPoint,避免端点漂移
|
|
214
|
+
* @param {Array} center 圆心 [lon, lat]
|
|
215
|
+
* @param {Array} startPoint 起点 [lon, lat]
|
|
216
|
+
* @param {Array} endPoint 终点 [lon, lat]
|
|
217
|
+
* @param {number} radiusMeters 半径(米)
|
|
218
|
+
* @param {number} segments 分段数
|
|
219
|
+
* @returns {Array[]}
|
|
220
|
+
*/
|
|
221
|
+
static _getGeoArcPointsBetween(center, startPoint, endPoint, radiusMeters, segments = 100) {
|
|
222
|
+
const startAngle = this._getBearing(center, startPoint);
|
|
223
|
+
const endAngle = this._getBearing(center, endPoint);
|
|
224
|
+
|
|
225
|
+
let angleDiff = endAngle - startAngle;
|
|
226
|
+
angleDiff = angleDiff < 0 ? angleDiff + Math.PI * 2 : angleDiff;
|
|
227
|
+
|
|
228
|
+
const points = [startPoint];
|
|
229
|
+
for (let i = 1; i < segments; i++) {
|
|
230
|
+
const angle = startAngle + (angleDiff * i) / segments;
|
|
231
|
+
points.push(this._getPointAtBearingAndDistance(center, radiusMeters, angle));
|
|
232
|
+
}
|
|
233
|
+
points.push(endPoint);
|
|
234
|
+
return points;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ==================== 坐标转换工具 ====================
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* 提取经纬度(忽略高度)
|
|
241
|
+
*/
|
|
242
|
+
static toLonLat(point) {
|
|
243
|
+
return [point[0], point[1]];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* 将结果点数组统一转为 [lon, lat, height] 格式
|
|
248
|
+
*/
|
|
249
|
+
static toPositions(points, height = 0) {
|
|
250
|
+
return points.map(p => [p[0], p[1], height]);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* 去除多边形轮廓中的相邻重复点,并显式闭合首尾
|
|
255
|
+
* 用于修复 Cesium polyline/polygon 因零长度边导致的连线异常
|
|
256
|
+
* @param {Array[]} points [lon, lat] 或 [lon, lat, height] 数组
|
|
257
|
+
* @returns {Array[]}
|
|
258
|
+
*/
|
|
259
|
+
static _closePolygonPoints(points) {
|
|
260
|
+
if (!points || points.length === 0) return points;
|
|
261
|
+
const result = [points[0]];
|
|
262
|
+
for (let i = 1; i < points.length; i++) {
|
|
263
|
+
const prev = result[result.length - 1];
|
|
264
|
+
const curr = points[i];
|
|
265
|
+
if (prev[0] !== curr[0] || prev[1] !== curr[1]) {
|
|
266
|
+
result.push(curr);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (result.length > 1) {
|
|
270
|
+
const first = result[0];
|
|
271
|
+
const last = result[result.length - 1];
|
|
272
|
+
if (first[0] !== last[0] || first[1] !== last[1]) {
|
|
273
|
+
result.push(first);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* 将 [lon, lat, height] 数组转为 Cesium.Cartesian3 数组
|
|
281
|
+
*/
|
|
282
|
+
static toCartesian3Array(points) {
|
|
283
|
+
const flat = [];
|
|
284
|
+
for (const p of points) {
|
|
285
|
+
flat.push(p[0], p[1], p[2] || 0);
|
|
286
|
+
}
|
|
287
|
+
return Cesium.Cartesian3.fromDegreesArrayHeights(flat);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ==================== 曲线/插值工具(复合箭头、曲线标绘需要)====================
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* 线性插值
|
|
294
|
+
*/
|
|
295
|
+
static getPointOnLine(t, startPnt, endPnt) {
|
|
296
|
+
return [
|
|
297
|
+
startPnt[0] + t * (endPnt[0] - startPnt[0]),
|
|
298
|
+
startPnt[1] + t * (endPnt[1] - startPnt[1])
|
|
299
|
+
];
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* 三次贝塞尔曲线求值
|
|
304
|
+
*/
|
|
305
|
+
static getCubicValue(t, startPnt, cPnt1, cPnt2, endPnt) {
|
|
306
|
+
t = Math.max(Math.min(t, 1), 0);
|
|
307
|
+
const tp = 1 - t;
|
|
308
|
+
const t2 = t * t;
|
|
309
|
+
const t3 = t2 * t;
|
|
310
|
+
const tp2 = tp * tp;
|
|
311
|
+
const tp3 = tp2 * tp;
|
|
312
|
+
return [
|
|
313
|
+
tp3 * startPnt[0] + 3 * tp2 * t * cPnt1[0] + 3 * tp * t2 * cPnt2[0] + t3 * endPnt[0],
|
|
314
|
+
tp3 * startPnt[1] + 3 * tp2 * t * cPnt1[1] + 3 * tp * t2 * cPnt2[1] + t3 * endPnt[1]
|
|
315
|
+
];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* 计算三点的法线及角平分线上的两个控制点
|
|
320
|
+
*/
|
|
321
|
+
static getBisectorNormals(t, pnt1, pnt2, pnt3) {
|
|
322
|
+
const normal = this.getNormal(pnt1, pnt2, pnt3);
|
|
323
|
+
const dist = Math.sqrt(normal[0] * normal[0] + normal[1] * normal[1]);
|
|
324
|
+
const uX = normal[0] / dist;
|
|
325
|
+
const uY = normal[1] / dist;
|
|
326
|
+
const d1 = this.mathDistance(pnt1, pnt2);
|
|
327
|
+
const d2 = this.mathDistance(pnt2, pnt3);
|
|
328
|
+
let bisectorNormalRight, bisectorNormalLeft;
|
|
329
|
+
|
|
330
|
+
if (dist > this.ZERO_TOLERANCE) {
|
|
331
|
+
if (this.isClockWise(pnt1, pnt2, pnt3)) {
|
|
332
|
+
let dt = t * d1;
|
|
333
|
+
let x = pnt2[0] - dt * uY;
|
|
334
|
+
let y = pnt2[1] + dt * uX;
|
|
335
|
+
bisectorNormalRight = [x, y];
|
|
336
|
+
dt = t * d2;
|
|
337
|
+
x = pnt2[0] + dt * uY;
|
|
338
|
+
y = pnt2[1] - dt * uX;
|
|
339
|
+
bisectorNormalLeft = [x, y];
|
|
340
|
+
} else {
|
|
341
|
+
let dt = t * d1;
|
|
342
|
+
let x = pnt2[0] + dt * uY;
|
|
343
|
+
let y = pnt2[1] - dt * uX;
|
|
344
|
+
bisectorNormalRight = [x, y];
|
|
345
|
+
dt = t * d2;
|
|
346
|
+
x = pnt2[0] - dt * uY;
|
|
347
|
+
y = pnt2[1] + dt * uX;
|
|
348
|
+
bisectorNormalLeft = [x, y];
|
|
349
|
+
}
|
|
350
|
+
} else {
|
|
351
|
+
let x = pnt2[0] + t * (pnt1[0] - pnt2[0]);
|
|
352
|
+
let y = pnt2[1] + t * (pnt1[1] - pnt2[1]);
|
|
353
|
+
bisectorNormalRight = [x, y];
|
|
354
|
+
x = pnt2[0] + t * (pnt3[0] - pnt2[0]);
|
|
355
|
+
y = pnt2[1] + t * (pnt3[1] - pnt2[1]);
|
|
356
|
+
bisectorNormalLeft = [x, y];
|
|
357
|
+
}
|
|
358
|
+
return [bisectorNormalRight, bisectorNormalLeft];
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* 获取三点的内切法线方向
|
|
363
|
+
*/
|
|
364
|
+
static getNormal(pnt1, pnt2, pnt3) {
|
|
365
|
+
let dX1 = pnt1[0] - pnt2[0];
|
|
366
|
+
let dY1 = pnt1[1] - pnt2[1];
|
|
367
|
+
const d1 = Math.sqrt(dX1 * dX1 + dY1 * dY1);
|
|
368
|
+
dX1 /= d1;
|
|
369
|
+
dY1 /= d1;
|
|
370
|
+
let dX2 = pnt3[0] - pnt2[0];
|
|
371
|
+
let dY2 = pnt3[1] - pnt2[1];
|
|
372
|
+
const d2 = Math.sqrt(dX2 * dX2 + dY2 * dY2);
|
|
373
|
+
dX2 /= d2;
|
|
374
|
+
dY2 /= d2;
|
|
375
|
+
return [dX1 + dX2, dY1 + dY2];
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* 获取左侧第一个控制点(用于曲线插值)
|
|
380
|
+
*/
|
|
381
|
+
static getLeftMostControlPoint(controlPoints, t) {
|
|
382
|
+
const [pnt1, pnt2, pnt3] = [controlPoints[0], controlPoints[1], controlPoints[2]];
|
|
383
|
+
const pnts = this.getBisectorNormals(0, pnt1, pnt2, pnt3);
|
|
384
|
+
const normalRight = pnts[0];
|
|
385
|
+
const normal = this.getNormal(pnt1, pnt2, pnt3);
|
|
386
|
+
const dist = Math.sqrt(normal[0] * normal[0] + normal[1] * normal[1]);
|
|
387
|
+
let controlX, controlY;
|
|
388
|
+
if (dist > this.ZERO_TOLERANCE) {
|
|
389
|
+
const mid = this.mid(pnt1, pnt2);
|
|
390
|
+
const pX = pnt1[0] - mid[0];
|
|
391
|
+
const pY = pnt1[1] - mid[1];
|
|
392
|
+
const d1 = this.mathDistance(pnt1, pnt2);
|
|
393
|
+
const n = 2.0 / d1;
|
|
394
|
+
const nX = -n * pY;
|
|
395
|
+
const nY = n * pX;
|
|
396
|
+
const a11 = nX * nX - nY * nY;
|
|
397
|
+
const a12 = 2 * nX * nY;
|
|
398
|
+
const a22 = nY * nY - nX * nX;
|
|
399
|
+
const dX = normalRight[0] - mid[0];
|
|
400
|
+
const dY = normalRight[1] - mid[1];
|
|
401
|
+
controlX = mid[0] + a11 * dX + a12 * dY;
|
|
402
|
+
controlY = mid[1] + a12 * dX + a22 * dY;
|
|
403
|
+
} else {
|
|
404
|
+
controlX = pnt1[0] + t * (pnt2[0] - pnt1[0]);
|
|
405
|
+
controlY = pnt1[1] + t * (pnt2[1] - pnt1[1]);
|
|
406
|
+
}
|
|
407
|
+
return [controlX, controlY];
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* 获取右侧最后一个控制点(用于曲线插值)
|
|
412
|
+
*/
|
|
413
|
+
static getRightMostControlPoint(controlPoints, t) {
|
|
414
|
+
const count = controlPoints.length;
|
|
415
|
+
const pnt1 = controlPoints[count - 3];
|
|
416
|
+
const pnt2 = controlPoints[count - 2];
|
|
417
|
+
const pnt3 = controlPoints[count - 1];
|
|
418
|
+
const pnts = this.getBisectorNormals(0, pnt1, pnt2, pnt3);
|
|
419
|
+
const normalLeft = pnts[1];
|
|
420
|
+
const normal = this.getNormal(pnt1, pnt2, pnt3);
|
|
421
|
+
const dist = Math.sqrt(normal[0] * normal[0] + normal[1] * normal[1]);
|
|
422
|
+
let controlX, controlY;
|
|
423
|
+
if (dist > this.ZERO_TOLERANCE) {
|
|
424
|
+
const mid = this.mid(pnt2, pnt3);
|
|
425
|
+
const pX = pnt3[0] - mid[0];
|
|
426
|
+
const pY = pnt3[1] - mid[1];
|
|
427
|
+
const d1 = this.mathDistance(pnt2, pnt3);
|
|
428
|
+
const n = 2.0 / d1;
|
|
429
|
+
const nX = -n * pY;
|
|
430
|
+
const nY = n * pX;
|
|
431
|
+
const a11 = nX * nX - nY * nY;
|
|
432
|
+
const a12 = 2 * nX * nY;
|
|
433
|
+
const a22 = nY * nY - nX * nX;
|
|
434
|
+
const dX = normalLeft[0] - mid[0];
|
|
435
|
+
const dY = normalLeft[1] - mid[1];
|
|
436
|
+
controlX = mid[0] + a11 * dX + a12 * dY;
|
|
437
|
+
controlY = mid[1] + a12 * dX + a22 * dY;
|
|
438
|
+
} else {
|
|
439
|
+
controlX = pnt3[0] + t * (pnt2[0] - pnt3[0]);
|
|
440
|
+
controlY = pnt3[1] + t * (pnt2[1] - pnt3[1]);
|
|
441
|
+
}
|
|
442
|
+
return [controlX, controlY];
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* 曲线插值(用于 CurvedArrow / Curve)
|
|
447
|
+
* @param {number} t 张力系数
|
|
448
|
+
* @param {Array[]} controlPoints 控制点数组
|
|
449
|
+
*/
|
|
450
|
+
static getCurvePoints(t, controlPoints) {
|
|
451
|
+
if (!controlPoints || controlPoints.length < 3) {
|
|
452
|
+
return controlPoints ? controlPoints.slice() : [];
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const leftControl = this.getLeftMostControlPoint(controlPoints, t);
|
|
456
|
+
const normals = [leftControl];
|
|
457
|
+
let pnt1, pnt2, pnt3;
|
|
458
|
+
for (let i = 0; i < controlPoints.length - 2; i++) {
|
|
459
|
+
pnt1 = controlPoints[i];
|
|
460
|
+
pnt2 = controlPoints[i + 1];
|
|
461
|
+
pnt3 = controlPoints[i + 2];
|
|
462
|
+
const normalPoints = this.getBisectorNormals(t, pnt1, pnt2, pnt3);
|
|
463
|
+
normals.push(...normalPoints);
|
|
464
|
+
}
|
|
465
|
+
const rightControl = this.getRightMostControlPoint(controlPoints, t);
|
|
466
|
+
if (rightControl) {
|
|
467
|
+
normals.push(rightControl);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const points = [];
|
|
471
|
+
for (let i = 0; i < controlPoints.length - 1; i++) {
|
|
472
|
+
pnt1 = controlPoints[i];
|
|
473
|
+
pnt2 = controlPoints[i + 1];
|
|
474
|
+
points.push(pnt1);
|
|
475
|
+
for (let j = 0; j < this.FITTING_COUNT; j++) {
|
|
476
|
+
const pnt = this.getCubicValue(
|
|
477
|
+
j / this.FITTING_COUNT,
|
|
478
|
+
pnt1,
|
|
479
|
+
normals[i * 2],
|
|
480
|
+
normals[i * 2 + 1],
|
|
481
|
+
pnt2
|
|
482
|
+
);
|
|
483
|
+
points.push(pnt);
|
|
484
|
+
}
|
|
485
|
+
points.push(pnt2);
|
|
486
|
+
}
|
|
487
|
+
return points;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* 贝塞尔曲线插值
|
|
492
|
+
*/
|
|
493
|
+
static getBezierPoints(points) {
|
|
494
|
+
if (!points || points.length <= 2) return points;
|
|
495
|
+
const bezierPoints = [];
|
|
496
|
+
const n = points.length - 1;
|
|
497
|
+
for (let t = 0; t <= 1; t += 0.01) {
|
|
498
|
+
let x = 0, y = 0;
|
|
499
|
+
for (let index = 0; index <= n; index++) {
|
|
500
|
+
const factor = this.getBinomialFactor(n, index);
|
|
501
|
+
const a = t ** index;
|
|
502
|
+
const b = (1 - t) ** (n - index);
|
|
503
|
+
x += factor * a * b * points[index][0];
|
|
504
|
+
y += factor * a * b * points[index][1];
|
|
505
|
+
}
|
|
506
|
+
bezierPoints.push([x, y]);
|
|
507
|
+
}
|
|
508
|
+
bezierPoints.push(points[n]);
|
|
509
|
+
return bezierPoints;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
static getFactorial(n) {
|
|
513
|
+
let result = 1;
|
|
514
|
+
for (let i = 1; i <= n; i++) {
|
|
515
|
+
result *= i;
|
|
516
|
+
}
|
|
517
|
+
return result;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
static getBinomialFactor(n, index) {
|
|
521
|
+
return this.getFactorial(n) / (this.getFactorial(index) * this.getFactorial(n - index));
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* 二次 B 样条插值
|
|
526
|
+
*/
|
|
527
|
+
static getQBSplinePoints(points) {
|
|
528
|
+
if (!points || points.length <= 2) return points;
|
|
529
|
+
const n = 2;
|
|
530
|
+
const bSplinePoints = [];
|
|
531
|
+
const m = points.length - n - 1;
|
|
532
|
+
bSplinePoints.push(points[0]);
|
|
533
|
+
for (let i = 0; i <= m; i++) {
|
|
534
|
+
for (let t = 0; t <= 1; t += 0.05) {
|
|
535
|
+
let x = 0, y = 0;
|
|
536
|
+
for (let k = 0; k <= n; k++) {
|
|
537
|
+
const factor = this.getQuadricBSplineFactor(k, t);
|
|
538
|
+
x += factor * points[i + k][0];
|
|
539
|
+
y += factor * points[i + k][1];
|
|
540
|
+
}
|
|
541
|
+
bSplinePoints.push([x, y]);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
bSplinePoints.push(points[points.length - 1]);
|
|
545
|
+
return bSplinePoints;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
static getQuadricBSplineFactor(k, t) {
|
|
549
|
+
if (k === 0) return (t - 1) ** 2 / 2;
|
|
550
|
+
if (k === 1) return (-2 * t ** 2 + 2 * t + 1) / 2;
|
|
551
|
+
if (k === 2) return t ** 2 / 2;
|
|
552
|
+
return 0;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// ==================== 标绘图元生成 ====================
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* 直箭头(FineArrow)
|
|
559
|
+
* 控制点:2 个 [起点, 终点]
|
|
560
|
+
*/
|
|
561
|
+
static createFineArrow(points, options = {}) {
|
|
562
|
+
if (!points || points.length < 2) return [];
|
|
563
|
+
|
|
564
|
+
const [p1, p2] = points.map(p => this.toLonLat(p));
|
|
565
|
+
const height = points[0][2] || 0;
|
|
566
|
+
|
|
567
|
+
const tailWidthFactor = options.tailWidthFactor ?? 0.1;
|
|
568
|
+
const neckWidthFactor = options.neckWidthFactor ?? 0.2;
|
|
569
|
+
const headWidthFactor = options.headWidthFactor ?? 0.25;
|
|
570
|
+
const headAngle = options.headAngle ?? Math.PI / 8.5;
|
|
571
|
+
const neckAngle = options.neckAngle ?? Math.PI / 13;
|
|
572
|
+
|
|
573
|
+
const len = this.getBaseLength([p1, p2]);
|
|
574
|
+
const tailWidth = len * tailWidthFactor;
|
|
575
|
+
const neckWidth = len * neckWidthFactor;
|
|
576
|
+
const headWidth = len * headWidthFactor;
|
|
577
|
+
|
|
578
|
+
const tailLeft = this.getThirdPoint(p2, p1, Math.PI / 2, tailWidth, true);
|
|
579
|
+
const tailRight = this.getThirdPoint(p2, p1, Math.PI / 2, tailWidth, false);
|
|
580
|
+
const headLeft = this.getThirdPoint(p1, p2, headAngle, headWidth, false);
|
|
581
|
+
const headRight = this.getThirdPoint(p1, p2, headAngle, headWidth, true);
|
|
582
|
+
const neckLeft = this.getThirdPoint(p1, p2, neckAngle, neckWidth, false);
|
|
583
|
+
const neckRight = this.getThirdPoint(p1, p2, neckAngle, neckWidth, true);
|
|
584
|
+
|
|
585
|
+
const outlinePoints = [
|
|
586
|
+
tailLeft, neckLeft, headLeft, p2, headRight, neckRight, tailRight, p1
|
|
587
|
+
];
|
|
588
|
+
|
|
589
|
+
return this.toPositions(outlinePoints, height);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* 突击方向(AssaultDirection)
|
|
594
|
+
* 继承直箭头逻辑,仅系数不同,整体长度拉长 1.5 倍
|
|
595
|
+
* 控制点:2 个 [起点, 终点]
|
|
596
|
+
*/
|
|
597
|
+
static createAssaultDirection(points, options = {}) {
|
|
598
|
+
if (!points || points.length < 2) return [];
|
|
599
|
+
|
|
600
|
+
const [p1, p2] = points.map(p => this.toLonLat(p));
|
|
601
|
+
const height = points[0][2] || 0;
|
|
602
|
+
|
|
603
|
+
const tailWidthFactor = options.tailWidthFactor ?? 0.08;
|
|
604
|
+
const neckWidthFactor = options.neckWidthFactor ?? 0.1;
|
|
605
|
+
const headWidthFactor = options.headWidthFactor ?? 0.13;
|
|
606
|
+
const headAngle = options.headAngle ?? Math.PI / 4;
|
|
607
|
+
const neckAngle = options.neckAngle ?? Math.PI * 0.17741;
|
|
608
|
+
|
|
609
|
+
const len = this.getBaseLength([p1, p2]) * 1.5;
|
|
610
|
+
const tailWidth = len * tailWidthFactor;
|
|
611
|
+
const neckWidth = len * neckWidthFactor;
|
|
612
|
+
const headWidth = len * headWidthFactor;
|
|
613
|
+
|
|
614
|
+
const tailLeft = this.getThirdPoint(p2, p1, Math.PI / 2, tailWidth, true);
|
|
615
|
+
const tailRight = this.getThirdPoint(p2, p1, Math.PI / 2, tailWidth, false);
|
|
616
|
+
const headLeft = this.getThirdPoint(p1, p2, headAngle, headWidth, false);
|
|
617
|
+
const headRight = this.getThirdPoint(p1, p2, headAngle, headWidth, true);
|
|
618
|
+
const neckLeft = this.getThirdPoint(p1, p2, neckAngle, neckWidth, false);
|
|
619
|
+
const neckRight = this.getThirdPoint(p1, p2, neckAngle, neckWidth, true);
|
|
620
|
+
|
|
621
|
+
return this.toPositions([
|
|
622
|
+
tailLeft, neckLeft, headLeft, p2, headRight, neckRight, tailRight, p1
|
|
623
|
+
], height);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* 细直箭头(StraightArrow)
|
|
628
|
+
* 控制点:2 个 [起点, 终点]
|
|
629
|
+
* 输出:线型
|
|
630
|
+
*/
|
|
631
|
+
static createStraightArrow(points, options = {}) {
|
|
632
|
+
if (!points || points.length < 2) return [];
|
|
633
|
+
|
|
634
|
+
const [p1, p2] = points.map(p => this.toLonLat(p));
|
|
635
|
+
const height = points[0][2] || 0;
|
|
636
|
+
|
|
637
|
+
const arrowLengthScale = options.arrowLengthScale ?? 5;
|
|
638
|
+
const maxArrowLength = options.maxArrowLength ?? 3000000;
|
|
639
|
+
|
|
640
|
+
const distance = this.mathDistance(p1, p2);
|
|
641
|
+
let len = distance / arrowLengthScale;
|
|
642
|
+
len = Math.min(len, maxArrowLength);
|
|
643
|
+
|
|
644
|
+
const leftPnt = this.getThirdPoint(p1, p2, Math.PI / 6, len / 2, false);
|
|
645
|
+
const rightPnt = this.getThirdPoint(p1, p2, Math.PI / 6, len / 2, true);
|
|
646
|
+
|
|
647
|
+
return this.toPositions([p1, p2, leftPnt, p2, rightPnt], height);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* 三角形(Triangle)
|
|
652
|
+
* 控制点:3 个
|
|
653
|
+
*/
|
|
654
|
+
static createTriangle(points) {
|
|
655
|
+
if (!points || points.length < 3) return [];
|
|
656
|
+
return points.slice(0, 3).map(p => [p[0], p[1], p[2] || 0]);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* 扇形(Sector)
|
|
661
|
+
* 控制点:3 个 [圆心, 起始点, 结束点]
|
|
662
|
+
* 使用球面真实地理圆,保证圆弧经过两个半径控制点
|
|
663
|
+
*/
|
|
664
|
+
static createSector(points) {
|
|
665
|
+
if (!points || points.length < 3) return [];
|
|
666
|
+
|
|
667
|
+
const [center, pnt2, pnt3] = points.map(p => this.toLonLat(p));
|
|
668
|
+
const height = points[0][2] || 0;
|
|
669
|
+
|
|
670
|
+
// 使用球面距离作为半径,与圆弧插值函数保持一致
|
|
671
|
+
const radius = this._calcSphericalDistance(pnt2, center);
|
|
672
|
+
|
|
673
|
+
// 弧线精确经过两个控制点,避免端点漂移
|
|
674
|
+
const arcPoints = this._getGeoArcPointsBetween(center, pnt2, pnt3, radius);
|
|
675
|
+
arcPoints.push(center);
|
|
676
|
+
|
|
677
|
+
return this.toPositions(arcPoints, height);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* 椭圆(Ellipse)
|
|
682
|
+
* 控制点:3 个 [center, majorEnd, minorEnd]
|
|
683
|
+
*/
|
|
684
|
+
static createEllipse(points) {
|
|
685
|
+
if (!points || points.length < 3) return [];
|
|
686
|
+
|
|
687
|
+
const controlPoints = points.slice(0, 3);
|
|
688
|
+
const [center, majorEnd, minorEnd] = controlPoints.map(p => this.toLonLat(p));
|
|
689
|
+
const height = controlPoints[0][2] || 0;
|
|
690
|
+
|
|
691
|
+
const majorVec = [majorEnd[0] - center[0], majorEnd[1] - center[1]];
|
|
692
|
+
const minorVec = [minorEnd[0] - center[0], minorEnd[1] - center[1]];
|
|
693
|
+
|
|
694
|
+
const ellipsePoints = [];
|
|
695
|
+
for (let i = 0; i <= 100; i++) {
|
|
696
|
+
const angle = (Math.PI * 2 * i) / 100;
|
|
697
|
+
const x = center[0] + majorVec[0] * Math.cos(angle) + minorVec[0] * Math.sin(angle);
|
|
698
|
+
const y = center[1] + majorVec[1] * Math.cos(angle) + minorVec[1] * Math.sin(angle);
|
|
699
|
+
ellipsePoints.push([x, y]);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return this.toPositions(ellipsePoints, height);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// ==================== 复合箭头图元 ====================
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* 通用箭头头部点
|
|
709
|
+
* @param {Array[]} points 控制点数组
|
|
710
|
+
* @param {object} options 箭头系数
|
|
711
|
+
* @returns {Array[]} [neckLeft, headLeft, headPnt, headRight, neckRight]
|
|
712
|
+
*/
|
|
713
|
+
static _getAttackArrowHeadPoints(points, options = {}) {
|
|
714
|
+
if (!points || points.length < 2) return [];
|
|
715
|
+
const headHeightFactor = options.headHeightFactor ?? 0.18;
|
|
716
|
+
const headWidthFactor = options.headWidthFactor ?? 0.3;
|
|
717
|
+
const neckHeightFactor = options.neckHeightFactor ?? 0.85;
|
|
718
|
+
const neckWidthFactor = options.neckWidthFactor ?? 0.15;
|
|
719
|
+
const headTailFactor = options.headTailFactor ?? 0.8;
|
|
720
|
+
|
|
721
|
+
const len = this.getBaseLength(points);
|
|
722
|
+
let headHeight = len * headHeightFactor;
|
|
723
|
+
const headPnt = points[points.length - 1];
|
|
724
|
+
const len2 = this.mathDistance(headPnt, points[points.length - 2]);
|
|
725
|
+
const tailWidth = this.mathDistance(options.tailLeft, options.tailRight);
|
|
726
|
+
if (headHeight > tailWidth * headTailFactor) {
|
|
727
|
+
headHeight = tailWidth * headTailFactor;
|
|
728
|
+
}
|
|
729
|
+
const headWidth = headHeight * headWidthFactor;
|
|
730
|
+
const neckWidth = headHeight * neckWidthFactor;
|
|
731
|
+
headHeight = Math.min(headHeight, len2);
|
|
732
|
+
const neckHeight = headHeight * neckHeightFactor;
|
|
733
|
+
|
|
734
|
+
const headEndPnt = this.getThirdPoint(points[points.length - 2], headPnt, 0, headHeight, true);
|
|
735
|
+
const neckEndPnt = this.getThirdPoint(points[points.length - 2], headPnt, 0, neckHeight, true);
|
|
736
|
+
const headLeft = this.getThirdPoint(headPnt, headEndPnt, Math.PI / 2, headWidth, false);
|
|
737
|
+
const headRight = this.getThirdPoint(headPnt, headEndPnt, Math.PI / 2, headWidth, true);
|
|
738
|
+
const neckLeft = this.getThirdPoint(headPnt, neckEndPnt, Math.PI / 2, neckWidth, false);
|
|
739
|
+
const neckRight = this.getThirdPoint(headPnt, neckEndPnt, Math.PI / 2, neckWidth, true);
|
|
740
|
+
return [neckLeft, headLeft, headPnt, headRight, neckRight];
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* 通用箭头躯干点
|
|
745
|
+
* @param {Array[]} points 骨骼点
|
|
746
|
+
* @param {Array} neckLeft
|
|
747
|
+
* @param {Array} neckRight
|
|
748
|
+
* @param {number} tailWidthFactor
|
|
749
|
+
*/
|
|
750
|
+
static _getAttackArrowBodyPoints(points, neckLeft, neckRight, tailWidthFactor) {
|
|
751
|
+
const allLen = this.wholeDistance(points);
|
|
752
|
+
const len = this.getBaseLength(points);
|
|
753
|
+
const tailWidth = len * tailWidthFactor;
|
|
754
|
+
const neckWidth = this.mathDistance(neckLeft, neckRight);
|
|
755
|
+
const widthDif = (tailWidth - neckWidth) / 2;
|
|
756
|
+
let tempLen = 0;
|
|
757
|
+
const leftBodyPnts = [];
|
|
758
|
+
const rightBodyPnts = [];
|
|
759
|
+
for (let i = 1; i < points.length - 1; i++) {
|
|
760
|
+
const angle = this.getAngleOfThreePoints(points[i - 1], points[i], points[i + 1]) / 2;
|
|
761
|
+
tempLen += this.mathDistance(points[i - 1], points[i]);
|
|
762
|
+
const w = (tailWidth / 2 - (tempLen / allLen) * widthDif) / Math.sin(angle);
|
|
763
|
+
const left = this.getThirdPoint(points[i - 1], points[i], Math.PI - angle, w, true);
|
|
764
|
+
const right = this.getThirdPoint(points[i - 1], points[i], angle, w, false);
|
|
765
|
+
leftBodyPnts.push(left);
|
|
766
|
+
rightBodyPnts.push(right);
|
|
767
|
+
}
|
|
768
|
+
return leftBodyPnts.concat(rightBodyPnts);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* 获取燕尾/集结箭头的尾部点
|
|
773
|
+
*/
|
|
774
|
+
static _getSquadTailPoints(points, tailWidthFactor, swallowTailFactor = 0) {
|
|
775
|
+
const allLen = this.getBaseLength(points);
|
|
776
|
+
const tailWidth = allLen * tailWidthFactor;
|
|
777
|
+
const tailLeft = this.getThirdPoint(points[1], points[0], Math.PI / 2, tailWidth, false);
|
|
778
|
+
const tailRight = this.getThirdPoint(points[1], points[0], Math.PI / 2, tailWidth, true);
|
|
779
|
+
if (swallowTailFactor > 0) {
|
|
780
|
+
const len = tailWidth * swallowTailFactor;
|
|
781
|
+
const swallowTailPnt = this.getThirdPoint(points[1], points[0], 0, len, true);
|
|
782
|
+
return [tailLeft, swallowTailPnt, tailRight];
|
|
783
|
+
}
|
|
784
|
+
return [tailLeft, tailRight];
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* 攻击箭头(AttackArrow)
|
|
789
|
+
* 控制点:≥3 个 [tailLeft, tailRight, bone..., head]
|
|
790
|
+
*/
|
|
791
|
+
static createAttackArrow(points, options = {}) {
|
|
792
|
+
if (!points || points.length < 3) return [];
|
|
793
|
+
|
|
794
|
+
const lnglatPoints = points.map(p => this.toLonLat(p));
|
|
795
|
+
const height = points[0][2] || 0;
|
|
796
|
+
|
|
797
|
+
let [tailLeft, tailRight] = [lnglatPoints[0], lnglatPoints[1]];
|
|
798
|
+
if (this.isClockWise(lnglatPoints[0], lnglatPoints[1], lnglatPoints[2])) {
|
|
799
|
+
tailLeft = lnglatPoints[1];
|
|
800
|
+
tailRight = lnglatPoints[0];
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
const midTail = this.mid(tailLeft, tailRight);
|
|
804
|
+
const bonePnts = [midTail].concat(lnglatPoints.slice(2));
|
|
805
|
+
const headPnts = this._getAttackArrowHeadPoints(bonePnts, {
|
|
806
|
+
...options,
|
|
807
|
+
tailLeft,
|
|
808
|
+
tailRight
|
|
809
|
+
});
|
|
810
|
+
if (!headPnts || headPnts.length === 0) return this.toPositions(lnglatPoints, height);
|
|
811
|
+
|
|
812
|
+
const [neckLeft, neckRight] = [headPnts[0], headPnts[4]];
|
|
813
|
+
const tailWidthFactor = this.mathDistance(tailLeft, tailRight) / this.getBaseLength(bonePnts);
|
|
814
|
+
const bodyPnts = this._getAttackArrowBodyPoints(bonePnts, neckLeft, neckRight, tailWidthFactor);
|
|
815
|
+
const count = bodyPnts.length;
|
|
816
|
+
let leftPnts = [tailLeft].concat(bodyPnts.slice(0, count / 2));
|
|
817
|
+
leftPnts.push(neckLeft);
|
|
818
|
+
let rightPnts = [tailRight].concat(bodyPnts.slice(count / 2, count));
|
|
819
|
+
rightPnts.push(neckRight);
|
|
820
|
+
leftPnts = this.getQBSplinePoints(leftPnts);
|
|
821
|
+
rightPnts = this.getQBSplinePoints(rightPnts);
|
|
822
|
+
const result = this._closePolygonPoints(leftPnts.concat(headPnts, rightPnts.reverse()));
|
|
823
|
+
return this.toPositions(result, height);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* 燕尾攻击箭头(SwallowtailAttackArrow)
|
|
828
|
+
* 控制点:≥3 个
|
|
829
|
+
*/
|
|
830
|
+
static createSwallowtailAttackArrow(points, options = {}) {
|
|
831
|
+
if (!points || points.length < 3) return [];
|
|
832
|
+
|
|
833
|
+
const lnglatPoints = points.map(p => this.toLonLat(p));
|
|
834
|
+
const height = points[0][2] || 0;
|
|
835
|
+
|
|
836
|
+
let [tailLeft, tailRight] = [lnglatPoints[0], lnglatPoints[1]];
|
|
837
|
+
if (this.isClockWise(lnglatPoints[0], lnglatPoints[1], lnglatPoints[2])) {
|
|
838
|
+
tailLeft = lnglatPoints[1];
|
|
839
|
+
tailRight = lnglatPoints[0];
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
const midTail = this.mid(tailLeft, tailRight);
|
|
843
|
+
const bonePnts = [midTail].concat(lnglatPoints.slice(2));
|
|
844
|
+
const headPnts = this._getAttackArrowHeadPoints(bonePnts, {
|
|
845
|
+
...options,
|
|
846
|
+
tailLeft,
|
|
847
|
+
tailRight
|
|
848
|
+
});
|
|
849
|
+
if (!headPnts || headPnts.length === 0) return this.toPositions(lnglatPoints, height);
|
|
850
|
+
|
|
851
|
+
const [neckLeft, neckRight] = [headPnts[0], headPnts[4]];
|
|
852
|
+
const tailWidthFactor = options.tailWidthFactor ?? 0.1;
|
|
853
|
+
const swallowTailFactor = options.swallowTailFactor ?? 1;
|
|
854
|
+
const allLen = this.getBaseLength(bonePnts);
|
|
855
|
+
const tailWidth = this.mathDistance(tailLeft, tailRight);
|
|
856
|
+
const len = allLen * tailWidthFactor * swallowTailFactor;
|
|
857
|
+
const swallowTailPnt = this.getThirdPoint(bonePnts[1], bonePnts[0], 0, len, true);
|
|
858
|
+
const factor = tailWidth / allLen;
|
|
859
|
+
const bodyPnts = this._getAttackArrowBodyPoints(bonePnts, neckLeft, neckRight, factor);
|
|
860
|
+
const count = bodyPnts.length;
|
|
861
|
+
let leftPnts = [tailLeft].concat(bodyPnts.slice(0, count / 2));
|
|
862
|
+
leftPnts.push(neckLeft);
|
|
863
|
+
let rightPnts = [tailRight].concat(bodyPnts.slice(count / 2, count));
|
|
864
|
+
rightPnts.push(neckRight);
|
|
865
|
+
leftPnts = this.getQBSplinePoints(leftPnts);
|
|
866
|
+
rightPnts = this.getQBSplinePoints(rightPnts);
|
|
867
|
+
const result = this._closePolygonPoints(leftPnts.concat(headPnts, rightPnts.reverse(), [swallowTailPnt, leftPnts[0]]));
|
|
868
|
+
return this.toPositions(result, height);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* 集结箭头(SquadCombat)
|
|
873
|
+
* 控制点:≥2 个 [tailBase, bone..., head]
|
|
874
|
+
*/
|
|
875
|
+
static createSquadCombat(points, options = {}) {
|
|
876
|
+
if (!points || points.length < 2) return [];
|
|
877
|
+
|
|
878
|
+
const lnglatPoints = points.map(p => this.toLonLat(p));
|
|
879
|
+
const height = points[0][2] || 0;
|
|
880
|
+
|
|
881
|
+
const tailWidthFactor = options.tailWidthFactor ?? 0.1;
|
|
882
|
+
const tailPnts = this._getSquadTailPoints(lnglatPoints, tailWidthFactor);
|
|
883
|
+
const headPnts = this._getAttackArrowHeadPoints(lnglatPoints, {
|
|
884
|
+
...options,
|
|
885
|
+
tailLeft: tailPnts[0],
|
|
886
|
+
tailRight: tailPnts[1]
|
|
887
|
+
});
|
|
888
|
+
if (!headPnts || headPnts.length === 0) return this.toPositions(lnglatPoints, height);
|
|
889
|
+
|
|
890
|
+
const [neckLeft, neckRight] = [headPnts[0], headPnts[4]];
|
|
891
|
+
const bodyPnts = this._getAttackArrowBodyPoints(lnglatPoints, neckLeft, neckRight, tailWidthFactor);
|
|
892
|
+
const count = bodyPnts.length;
|
|
893
|
+
let leftPnts = [tailPnts[0]].concat(bodyPnts.slice(0, count / 2));
|
|
894
|
+
leftPnts.push(neckLeft);
|
|
895
|
+
let rightPnts = [tailPnts[1]].concat(bodyPnts.slice(count / 2, count));
|
|
896
|
+
rightPnts.push(neckRight);
|
|
897
|
+
leftPnts = this.getQBSplinePoints(leftPnts);
|
|
898
|
+
rightPnts = this.getQBSplinePoints(rightPnts);
|
|
899
|
+
const result = this._closePolygonPoints(leftPnts.concat(headPnts, rightPnts.reverse()));
|
|
900
|
+
return this.toPositions(result, height);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* 燕尾集结箭头(SwallowtailSquadCombat)
|
|
905
|
+
* 控制点:≥2 个
|
|
906
|
+
*/
|
|
907
|
+
static createSwallowtailSquadCombat(points, options = {}) {
|
|
908
|
+
if (!points || points.length < 2) return [];
|
|
909
|
+
|
|
910
|
+
const lnglatPoints = points.map(p => this.toLonLat(p));
|
|
911
|
+
const height = points[0][2] || 0;
|
|
912
|
+
|
|
913
|
+
const tailWidthFactor = options.tailWidthFactor ?? 0.1;
|
|
914
|
+
const swallowTailFactor = options.swallowTailFactor ?? 1;
|
|
915
|
+
const tailPnts = this._getSquadTailPoints(lnglatPoints, tailWidthFactor, swallowTailFactor);
|
|
916
|
+
const headPnts = this._getAttackArrowHeadPoints(lnglatPoints, {
|
|
917
|
+
...options,
|
|
918
|
+
tailLeft: tailPnts[0],
|
|
919
|
+
tailRight: tailPnts[2]
|
|
920
|
+
});
|
|
921
|
+
if (!headPnts || headPnts.length === 0) return this.toPositions(lnglatPoints, height);
|
|
922
|
+
|
|
923
|
+
const [neckLeft, neckRight] = [headPnts[0], headPnts[4]];
|
|
924
|
+
const bodyPnts = this._getAttackArrowBodyPoints(lnglatPoints, neckLeft, neckRight, tailWidthFactor);
|
|
925
|
+
const count = bodyPnts.length;
|
|
926
|
+
let leftPnts = [tailPnts[0]].concat(bodyPnts.slice(0, count / 2));
|
|
927
|
+
leftPnts.push(neckLeft);
|
|
928
|
+
let rightPnts = [tailPnts[2]].concat(bodyPnts.slice(count / 2, count));
|
|
929
|
+
rightPnts.push(neckRight);
|
|
930
|
+
leftPnts = this.getQBSplinePoints(leftPnts);
|
|
931
|
+
rightPnts = this.getQBSplinePoints(rightPnts);
|
|
932
|
+
const result = this._closePolygonPoints(leftPnts.concat(headPnts, rightPnts.reverse(), [tailPnts[1], leftPnts[0]]));
|
|
933
|
+
return this.toPositions(result, height);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* 双箭头(DoubleArrow)
|
|
938
|
+
* 控制点:3~5 个
|
|
939
|
+
* - 3 点:自动生成临时第四点和对称连接点
|
|
940
|
+
* - 4 点:第四点作为临时控制点
|
|
941
|
+
* - 5 点:第四点作为临时控制点,第五点作为连接点
|
|
942
|
+
*/
|
|
943
|
+
static createDoubleArrow(points, options = {}) {
|
|
944
|
+
if (!points || points.length < 3) return [];
|
|
945
|
+
|
|
946
|
+
const lnglatPoints = points.map(p => this.toLonLat(p));
|
|
947
|
+
const height = points[0][2] || 0;
|
|
948
|
+
const [pnt1, pnt2, pnt3] = lnglatPoints;
|
|
949
|
+
const count = lnglatPoints.length;
|
|
950
|
+
|
|
951
|
+
let tempPoint4;
|
|
952
|
+
let connPoint;
|
|
953
|
+
if (count === 3) {
|
|
954
|
+
tempPoint4 = this._getDoubleArrowTempPoint4(pnt1, pnt2, pnt3);
|
|
955
|
+
connPoint = this.mid(pnt1, pnt2);
|
|
956
|
+
} else if (count === 4) {
|
|
957
|
+
tempPoint4 = lnglatPoints[3];
|
|
958
|
+
connPoint = this.mid(pnt1, pnt2);
|
|
959
|
+
} else {
|
|
960
|
+
tempPoint4 = lnglatPoints[3];
|
|
961
|
+
connPoint = lnglatPoints[4];
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
const headHeightFactor = options.headHeightFactor ?? 0.25;
|
|
965
|
+
const headWidthFactor = options.headWidthFactor ?? 0.3;
|
|
966
|
+
const neckHeightFactor = options.neckHeightFactor ?? 0.85;
|
|
967
|
+
const neckWidthFactor = options.neckWidthFactor ?? 0.15;
|
|
968
|
+
|
|
969
|
+
const isClockWise = this.isClockWise(pnt1, pnt2, pnt3);
|
|
970
|
+
let leftArrowPnts, rightArrowPnts;
|
|
971
|
+
if (isClockWise) {
|
|
972
|
+
leftArrowPnts = this._getDoubleArrowPoints(pnt1, connPoint, tempPoint4, false, options);
|
|
973
|
+
rightArrowPnts = this._getDoubleArrowPoints(connPoint, pnt2, pnt3, true, options);
|
|
974
|
+
} else {
|
|
975
|
+
leftArrowPnts = this._getDoubleArrowPoints(pnt2, connPoint, pnt3, false, options);
|
|
976
|
+
rightArrowPnts = this._getDoubleArrowPoints(connPoint, pnt1, tempPoint4, true, options);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
const m = leftArrowPnts.length;
|
|
980
|
+
const t = (m - 5) / 2;
|
|
981
|
+
const llBodyPnts = leftArrowPnts.slice(0, t);
|
|
982
|
+
const lArrowPnts = leftArrowPnts.slice(t, t + 5);
|
|
983
|
+
let lrBodyPnts = leftArrowPnts.slice(t + 5, m);
|
|
984
|
+
let rlBodyPnts = rightArrowPnts.slice(0, t);
|
|
985
|
+
const rArrowPnts = rightArrowPnts.slice(t, t + 5);
|
|
986
|
+
const rrBodyPnts = rightArrowPnts.slice(t + 5, m);
|
|
987
|
+
|
|
988
|
+
rlBodyPnts = this.getBezierPoints(rlBodyPnts);
|
|
989
|
+
const bodyPnts = this.getBezierPoints(rrBodyPnts.concat(llBodyPnts.slice(1)));
|
|
990
|
+
lrBodyPnts = this.getBezierPoints(lrBodyPnts);
|
|
991
|
+
const result = this._closePolygonPoints(rlBodyPnts.concat(rArrowPnts, bodyPnts, lArrowPnts, lrBodyPnts));
|
|
992
|
+
return this.toPositions(result, height);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
static _getDoubleArrowTempPoint4(linePnt1, linePnt2, point) {
|
|
996
|
+
const midPnt = this.mid(linePnt1, linePnt2);
|
|
997
|
+
const len = this.mathDistance(midPnt, point);
|
|
998
|
+
const angle = this.getAngleOfThreePoints(linePnt1, midPnt, point);
|
|
999
|
+
let symPnt = [0, 0];
|
|
1000
|
+
let distance1, distance2, mid;
|
|
1001
|
+
if (angle < Math.PI / 2) {
|
|
1002
|
+
distance1 = len * Math.sin(angle);
|
|
1003
|
+
distance2 = len * Math.cos(angle);
|
|
1004
|
+
mid = this.getThirdPoint(linePnt1, midPnt, Math.PI / 2, distance1, false);
|
|
1005
|
+
symPnt = this.getThirdPoint(midPnt, mid, Math.PI / 2, distance2, true);
|
|
1006
|
+
} else if (angle < Math.PI) {
|
|
1007
|
+
distance1 = len * Math.sin(Math.PI - angle);
|
|
1008
|
+
distance2 = len * Math.cos(Math.PI - angle);
|
|
1009
|
+
mid = this.getThirdPoint(linePnt1, midPnt, Math.PI / 2, distance1, false);
|
|
1010
|
+
symPnt = this.getThirdPoint(midPnt, mid, Math.PI / 2, distance2, false);
|
|
1011
|
+
} else if (angle < Math.PI * 1.5) {
|
|
1012
|
+
distance1 = len * Math.sin(angle - Math.PI);
|
|
1013
|
+
distance2 = len * Math.cos(angle - Math.PI);
|
|
1014
|
+
mid = this.getThirdPoint(linePnt1, midPnt, Math.PI / 2, distance1, true);
|
|
1015
|
+
symPnt = this.getThirdPoint(midPnt, mid, Math.PI / 2, distance2, true);
|
|
1016
|
+
} else {
|
|
1017
|
+
distance1 = len * Math.sin(Math.PI * 2 - angle);
|
|
1018
|
+
distance2 = len * Math.cos(Math.PI * 2 - angle);
|
|
1019
|
+
mid = this.getThirdPoint(linePnt1, midPnt, Math.PI / 2, distance1, true);
|
|
1020
|
+
symPnt = this.getThirdPoint(midPnt, mid, Math.PI / 2, distance2, false);
|
|
1021
|
+
}
|
|
1022
|
+
return symPnt;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
static _getDoubleArrowPoints(pnt1, pnt2, pnt3, clockWise, options = {}) {
|
|
1026
|
+
const headHeightFactor = options.headHeightFactor ?? 0.25;
|
|
1027
|
+
const headWidthFactor = options.headWidthFactor ?? 0.3;
|
|
1028
|
+
const neckHeightFactor = options.neckHeightFactor ?? 0.85;
|
|
1029
|
+
const neckWidthFactor = options.neckWidthFactor ?? 0.15;
|
|
1030
|
+
|
|
1031
|
+
const midPnt = this.mid(pnt1, pnt2);
|
|
1032
|
+
const len = this.mathDistance(midPnt, pnt3);
|
|
1033
|
+
let midPnt1 = this.getThirdPoint(pnt3, midPnt, 0, len * 0.3, true);
|
|
1034
|
+
let midPnt2 = this.getThirdPoint(pnt3, midPnt, 0, len * 0.5, true);
|
|
1035
|
+
midPnt1 = this.getThirdPoint(midPnt, midPnt1, Math.PI / 2, len / 5, clockWise);
|
|
1036
|
+
midPnt2 = this.getThirdPoint(midPnt, midPnt2, Math.PI / 2, len / 4, clockWise);
|
|
1037
|
+
const points = [midPnt, midPnt1, midPnt2, pnt3];
|
|
1038
|
+
const arrowPnts = this._getDoubleArrowHeadPoints(points, options);
|
|
1039
|
+
if (!arrowPnts || arrowPnts.length === 0) return [];
|
|
1040
|
+
|
|
1041
|
+
const neckLeftPoint = arrowPnts[0];
|
|
1042
|
+
const neckRightPoint = arrowPnts[4];
|
|
1043
|
+
const tailWidthFactor = this.mathDistance(pnt1, pnt2) / this.getBaseLength(points) / 2;
|
|
1044
|
+
const bodyPnts = this._getAttackArrowBodyPoints(points, neckLeftPoint, neckRightPoint, tailWidthFactor);
|
|
1045
|
+
const n = bodyPnts.length;
|
|
1046
|
+
let lPoints = bodyPnts.slice(0, n / 2);
|
|
1047
|
+
let rPoints = bodyPnts.slice(n / 2, n);
|
|
1048
|
+
lPoints.push(neckLeftPoint);
|
|
1049
|
+
rPoints.push(neckRightPoint);
|
|
1050
|
+
lPoints = lPoints.reverse();
|
|
1051
|
+
lPoints.push(pnt2);
|
|
1052
|
+
rPoints = rPoints.reverse();
|
|
1053
|
+
rPoints.push(pnt1);
|
|
1054
|
+
return lPoints.reverse().concat(arrowPnts, rPoints);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
static _getDoubleArrowHeadPoints(points, options = {}) {
|
|
1058
|
+
const len = this.getBaseLength(points);
|
|
1059
|
+
const headHeight = len * (options.headHeightFactor ?? 0.25);
|
|
1060
|
+
const headPnt = points[points.length - 1];
|
|
1061
|
+
const headWidth = headHeight * (options.headWidthFactor ?? 0.3);
|
|
1062
|
+
const neckWidth = headHeight * (options.neckWidthFactor ?? 0.15);
|
|
1063
|
+
const neckHeight = headHeight * (options.neckHeightFactor ?? 0.85);
|
|
1064
|
+
const headEndPnt = this.getThirdPoint(points[points.length - 2], headPnt, 0, headHeight, true);
|
|
1065
|
+
const neckEndPnt = this.getThirdPoint(points[points.length - 2], headPnt, 0, neckHeight, true);
|
|
1066
|
+
const headLeft = this.getThirdPoint(headPnt, headEndPnt, Math.PI / 2, headWidth, false);
|
|
1067
|
+
const headRight = this.getThirdPoint(headPnt, headEndPnt, Math.PI / 2, headWidth, true);
|
|
1068
|
+
const neckLeft = this.getThirdPoint(headPnt, neckEndPnt, Math.PI / 2, neckWidth, false);
|
|
1069
|
+
const neckRight = this.getThirdPoint(headPnt, neckEndPnt, Math.PI / 2, neckWidth, true);
|
|
1070
|
+
return [neckLeft, headLeft, headPnt, headRight, neckRight];
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* 曲线箭头(CurvedArrow)
|
|
1075
|
+
* 控制点:≥2 个;2 个点时退化为细直箭头
|
|
1076
|
+
*/
|
|
1077
|
+
static createCurvedArrow(points, options = {}) {
|
|
1078
|
+
if (!points || points.length < 2) return [];
|
|
1079
|
+
|
|
1080
|
+
const lnglatPoints = points.map(p => this.toLonLat(p));
|
|
1081
|
+
const height = points[0][2] || 0;
|
|
1082
|
+
|
|
1083
|
+
if (points.length === 2) {
|
|
1084
|
+
return this.createStraightArrow(points, options);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
const t = options.t ?? 0.3;
|
|
1088
|
+
const arrowLengthScale = options.arrowLengthScale ?? 5;
|
|
1089
|
+
const maxArrowLength = options.maxArrowLength ?? 3000000;
|
|
1090
|
+
|
|
1091
|
+
const curvePoints = this.getCurvePoints(t, lnglatPoints);
|
|
1092
|
+
const pnt1 = lnglatPoints[lnglatPoints.length - 2];
|
|
1093
|
+
const pnt2 = lnglatPoints[lnglatPoints.length - 1];
|
|
1094
|
+
const distance = this.wholeDistance(lnglatPoints);
|
|
1095
|
+
let len = distance / arrowLengthScale;
|
|
1096
|
+
len = Math.min(len, maxArrowLength);
|
|
1097
|
+
const leftPnt = this.getThirdPoint(
|
|
1098
|
+
curvePoints[curvePoints.length - 2],
|
|
1099
|
+
curvePoints[curvePoints.length - 1],
|
|
1100
|
+
Math.PI / 6,
|
|
1101
|
+
len / 2,
|
|
1102
|
+
false
|
|
1103
|
+
);
|
|
1104
|
+
const rightPnt = this.getThirdPoint(
|
|
1105
|
+
curvePoints[curvePoints.length - 2],
|
|
1106
|
+
curvePoints[curvePoints.length - 1],
|
|
1107
|
+
Math.PI / 6,
|
|
1108
|
+
len / 2,
|
|
1109
|
+
true
|
|
1110
|
+
);
|
|
1111
|
+
const result = curvePoints.concat([leftPnt, pnt2, rightPnt]);
|
|
1112
|
+
return this.toPositions(result, height);
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
/**
|
|
1116
|
+
* 曲线(Curve)
|
|
1117
|
+
* 控制点:≥2 个;2 个点时退化为直线
|
|
1118
|
+
*/
|
|
1119
|
+
static createCurve(points, options = {}) {
|
|
1120
|
+
if (!points || points.length < 2) return [];
|
|
1121
|
+
|
|
1122
|
+
const lnglatPoints = points.map(p => this.toLonLat(p));
|
|
1123
|
+
const height = points[0][2] || 0;
|
|
1124
|
+
|
|
1125
|
+
// 2 个点时直接连成直线,避免曲线插值算法因点数不足报错
|
|
1126
|
+
if (points.length === 2) {
|
|
1127
|
+
return this.toPositions(lnglatPoints, height);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
const t = options.t ?? 0.3;
|
|
1131
|
+
const curvePoints = this.getCurvePoints(t, lnglatPoints);
|
|
1132
|
+
return this.toPositions(curvePoints, height);
|
|
1133
|
+
}
|
|
1134
|
+
}
|