@aibee/crc-bmap 0.8.44 → 0.8.46
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/lib/bmap.cjs.min.js +408 -401
- package/lib/bmap.esm.js +7644 -1526
- package/lib/bmap.esm.min.js +408 -401
- package/lib/bmap.min.js +408 -401
- package/lib/src/bmap.js +60 -54
- package/lib/src/context/OrbitControls.js +4 -1
- package/lib/src/context/context.js +54 -37
- package/lib/src/context/control.js +85 -83
- package/lib/src/context/scene.js +1 -0
- package/lib/src/elements/base-svg.js +5 -4
- package/lib/src/elements/floor.js +8 -0
- package/lib/src/elements/glb-model.js +14 -18
- package/lib/src/elements/graphic.js +18 -8
- package/lib/src/elements/ground-texture.js +41 -44
- package/lib/src/elements/heatmap.js +2 -1
- package/lib/src/elements/lane.js +5 -1
- package/lib/src/elements/merge-graphic.js +3 -28
- package/lib/src/elements/model.js +5 -9
- package/lib/src/elements/overlay.js +9 -7
- package/lib/src/elements/poi.js +55 -49
- package/lib/src/elements/poi2.js +52 -53
- package/lib/src/elements/shadow.js +3 -1
- package/lib/src/elements/svg-line.js +2 -0
- package/lib/src/elements/svg-polygon.js +1 -0
- package/lib/src/elements/text-texture.js +32 -36
- package/lib/src/elements/wall.js +3 -41
- package/lib/src/external/meshLine.js +17 -0
- package/lib/src/factory/img-texture.js +1 -0
- package/lib/src/factory/material.js +21 -51
- package/lib/src/factory/model.js +31 -33
- package/lib/src/factory/text-texture.js +17 -0
- package/lib/src/factory/unique-key.js +8 -0
- package/lib/src/layer/graphic-layer.js +1 -0
- package/lib/src/layer/parking-layer.d.ts +6 -0
- package/lib/src/layer/parking-layer.js +8 -0
- package/lib/src/layer/poi-layer.js +4 -1
- package/lib/src/layer/poi-layer2.js +2 -0
- package/lib/src/loader/AibeeLoader/index.js +230 -276
- package/lib/src/loader/AibeeLoader/layer.js +8 -6
- package/lib/src/loader/CrLoader/api/floor.js +45 -73
- package/lib/src/loader/CrLoader/index.js +88 -106
- package/lib/src/operations/hover/hover-helper.js +12 -2
- package/lib/src/operations/selection/selection.js +11 -1
- package/lib/src/plugins/car-inertial-position/car-inertial-position.d.ts +42 -10
- package/lib/src/plugins/car-inertial-position/car-inertial-position.js +285 -67
- package/lib/src/plugins/car-inertial-position/compass.d.ts +4 -5
- package/lib/src/plugins/car-inertial-position/compass.js +43 -38
- package/lib/src/plugins/car-inertial-position/kalman-filter.d.ts +14 -0
- package/lib/src/plugins/car-inertial-position/kalman-filter.js +30 -0
- package/lib/src/plugins/car-inertial-position/utils.d.ts +1 -1
- package/lib/src/plugins/car-inertial-position/utils.js +8 -5
- package/lib/src/plugins/cr-nav-path/cr-nav-path.js +47 -55
- package/lib/src/plugins/cr-nav-path/cr-path.worker.js +4 -2
- package/lib/src/plugins/equipment/equipment.js +20 -22
- package/lib/src/plugins/mul-floor-navigation/mul-floor-navigation.js +63 -61
- package/lib/src/plugins/mul-floor-navigation/path.js +34 -30
- package/lib/src/plugins/mul-floor-navigation/start-model.js +2 -1
- package/lib/src/plugins/mul-floor-select/mul-floor-select.js +2 -1
- package/lib/src/plugins/mul-floors/mul-floors.js +1 -0
- package/lib/src/plugins/nav-path/nav-path.js +51 -59
- package/lib/src/plugins/nav-path/path.worker.js +4 -2
- package/lib/src/plugins/navigation/navigation.d.ts +5 -0
- package/lib/src/plugins/navigation/navigation.js +240 -217
- package/lib/src/plugins/navigation/path.js +34 -30
- package/lib/src/plugins/navigation/position-navigation.d.ts +4 -0
- package/lib/src/plugins/navigation/position-navigation.js +96 -79
- package/lib/src/plugins/navigation/start-rotate-helper-poi.js +18 -6
- package/lib/src/plugins/pdr-position/imu-position.js +13 -9
- package/lib/src/plugins/pdr-position/particle.js +4 -2
- package/lib/src/plugins/pdr-position/pdr.js +5 -4
- package/lib/src/plugins/pdr-position/position.js +5 -2
- package/lib/src/plugins/pdr-position/sensor.js +20 -25
- package/lib/src/plugins/select/select.js +11 -1
- package/lib/src/utils/camera-bound.js +3 -1
- package/lib/src/utils/color.js +8 -4
- package/lib/src/utils/coordinate.js +1 -0
- package/lib/src/utils/create.js +4 -2
- package/lib/src/utils/events.js +15 -4
- package/lib/src/utils/index-db.js +18 -11
- package/lib/src/utils/init-helper.js +7 -2
- package/lib/src/utils/obj-utils.js +3 -2
- package/lib/src/utils/os.js +1 -0
- package/lib/src/utils/path.js +15 -4
- package/lib/src/utils/promise.js +3 -1
- package/lib/src/utils/proxy.js +2 -1
- package/lib/src/utils/road.js +20 -14
- package/lib/src/utils/road2.js +60 -39
- package/lib/src/utils/rules.js +1 -0
- package/lib/src/utils/string.js +3 -1
- package/lib/src/utils/svg.js +12 -11
- package/lib/src/utils/taskQueue.js +29 -29
- package/lib/src/utils/timer.js +8 -1
- package/lib/src/utils/translate.js +3 -1
- package/lib/src/utils/tween.js +8 -0
- package/lib/src/utils/webworker.js +10 -9
- package/package.json +2 -1
|
@@ -1,69 +1,298 @@
|
|
|
1
1
|
// 车辆惯性导航
|
|
2
|
+
/**
|
|
3
|
+
* 定位结果有三种:视觉定位结果、beacon定位结果、pdr定位结果
|
|
4
|
+
* 以 pdr 定位为主要流程,以视觉结果和beacon结果为校准,其中主视觉定位,beacon是在长期无视觉情况下才会采用的
|
|
5
|
+
* 以视觉定位结果计算角度、速度,通过卡尔曼滤波平滑速度和角度的变化
|
|
6
|
+
* 什么时候通过视觉、蓝牙校准pdr的结果???
|
|
7
|
+
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
8
|
+
import "core-js/modules/es.array.find-last-index.js";
|
|
9
|
+
import "core-js/modules/es.array.find-last.js";
|
|
10
|
+
import "core-js/modules/es.array.push.js";
|
|
2
11
|
import { EventDispatcher } from "three";
|
|
3
|
-
import {
|
|
4
|
-
import { calculateLineDirection, predictFuturePosition, predictFutureSpeed, transformSpeed } from "./utils";
|
|
12
|
+
import { calculateLineDirection, predictFuturePosition, transformSpeed } from "./utils";
|
|
5
13
|
import { getLength, Timer } from "../../utils";
|
|
14
|
+
import { KalmanFilter } from "./kalman-filter";
|
|
15
|
+
import { isNil } from "lodash";
|
|
6
16
|
export class CarInertialPosition extends EventDispatcher {
|
|
7
|
-
|
|
8
|
-
this.
|
|
17
|
+
setPathAngle(angle) {
|
|
18
|
+
this.pathAngle = angle;
|
|
19
|
+
this.dispatchEvent({
|
|
20
|
+
type: "change-compass",
|
|
21
|
+
value: this.pathAngle
|
|
22
|
+
});
|
|
23
|
+
if (isNil(this.angle)) {
|
|
24
|
+
this.angle = angle;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
changeSpeed() {
|
|
28
|
+
const serverHistory = this.history.filter((item)=>[
|
|
29
|
+
"vision"
|
|
30
|
+
].includes(item.type));
|
|
31
|
+
if (serverHistory.length < 2) {
|
|
32
|
+
this.speedFilter.filter(0);
|
|
33
|
+
this.speed = 0;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
;
|
|
37
|
+
// 计算速度:位置变化 / 时间差
|
|
38
|
+
// const speeds = serverHistory.slice(1).map((current, index) => {
|
|
39
|
+
// const previous = serverHistory[index];
|
|
40
|
+
// const timeDiff = current.time - previous.time;
|
|
41
|
+
// const distanceDiff = getLength(current.position, previous.position);
|
|
42
|
+
// // 防止除零
|
|
43
|
+
// return timeDiff > 0 ? (distanceDiff / timeDiff) : 0;
|
|
44
|
+
// });
|
|
45
|
+
// // 计算平均速度
|
|
46
|
+
// const speed = speeds.reduce((a, b) => a + b, 0) / speeds.length;
|
|
47
|
+
const first = serverHistory[0];
|
|
48
|
+
const last = serverHistory.slice(-1)[0];
|
|
49
|
+
const distance = getLength(last.position, first.position);
|
|
50
|
+
const time = last.time - first.time;
|
|
51
|
+
const speed = time > 0 ? distance / time : 0;
|
|
52
|
+
// 使用卡尔曼滤波器平滑速度
|
|
53
|
+
const smoothedSpeed = this.speedFilter.filter(speed);
|
|
54
|
+
this.speed = smoothedSpeed;
|
|
55
|
+
}
|
|
56
|
+
changeAngle(histories) {
|
|
57
|
+
if (histories === void 0) histories = this.visionHistory;
|
|
58
|
+
if (histories.length < 2) {
|
|
59
|
+
this.angle = null;
|
|
60
|
+
}
|
|
61
|
+
;
|
|
62
|
+
const angle = calculateLineDirection(histories.map((item)=>item.position));
|
|
63
|
+
if (angle !== null) {
|
|
64
|
+
this.setAngle(angle);
|
|
65
|
+
}
|
|
9
66
|
}
|
|
10
|
-
|
|
11
|
-
|
|
67
|
+
setAngle(angle, dispatch) {
|
|
68
|
+
if (dispatch === void 0) dispatch = true;
|
|
69
|
+
this.angle = angle;
|
|
70
|
+
if (dispatch) {
|
|
71
|
+
this.dispatchEvent({
|
|
72
|
+
type: "change-position-compass",
|
|
73
|
+
value: angle
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 添加视觉结果
|
|
79
|
+
* @param position
|
|
80
|
+
* @param time
|
|
81
|
+
* @param duration
|
|
82
|
+
* @returns
|
|
83
|
+
*/ setPosition(position, time, duration) {
|
|
84
|
+
const item = {
|
|
12
85
|
position,
|
|
13
86
|
time,
|
|
14
87
|
clientTime: Date.now() - duration,
|
|
15
88
|
type: "vision"
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
89
|
+
};
|
|
90
|
+
this._setVisionHistoryForAngle(item);
|
|
91
|
+
if (isNil(this.angle) || !this.speed) {
|
|
92
|
+
this.addHistory(item);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const lastIndex = this.history.findLastIndex((item)=>item.type === "vision");
|
|
96
|
+
const last = this.history[lastIndex];
|
|
97
|
+
if (last) {
|
|
98
|
+
if (last.time > time) {
|
|
99
|
+
// 这一桢的时间比本地最后一桢的时间还早,是个已经走过的点,废弃
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// 如果这个在pdr的点前面,说明pdr滞后了,采用视觉点
|
|
103
|
+
const angle = calculateLineDirection([
|
|
104
|
+
this.history.slice(-1)[0].position,
|
|
105
|
+
position
|
|
106
|
+
]);
|
|
107
|
+
if (angle && Math.abs(angle - this.angle) < 60) {
|
|
108
|
+
this.addHistory(item);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
// 判断视觉点连续三次超出最后的点5米,就采用视觉点
|
|
112
|
+
// 预估这个视觉点的正确位置
|
|
113
|
+
const predictPos = predictFuturePosition(position, this.speed, 360 - this.pathAngle, duration);
|
|
114
|
+
const distance = getLength(predictPos, this.history.slice(-1)[0].position);
|
|
115
|
+
// 超过10米就把位置拉过来
|
|
116
|
+
if (distance > 10) {
|
|
117
|
+
this.visionExcessesCount++;
|
|
24
118
|
} else {
|
|
25
|
-
|
|
119
|
+
this.visionExcessesCount = 0;
|
|
120
|
+
}
|
|
121
|
+
if (this.visionExcessesCount > 3) {
|
|
122
|
+
console.warn("连续三次视觉和pdr差距大于5米,使用视觉校准");
|
|
123
|
+
// 采用视觉结果
|
|
124
|
+
this.addHistory(item);
|
|
125
|
+
this.visionExcessesCount = 0;
|
|
26
126
|
}
|
|
27
127
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
128
|
+
}
|
|
129
|
+
resetPdrPosition() {
|
|
130
|
+
const lastVision = this.history.findLast((item)=>item.type === "vision");
|
|
131
|
+
if (lastVision && this.speed) {
|
|
132
|
+
// 预估这个视觉点的正确位置
|
|
133
|
+
const predictPos = predictFuturePosition(lastVision.position, this.speed, 360 - this.pathAngle, Date.now() - lastVision.clientTime);
|
|
134
|
+
if (predictPos) {
|
|
135
|
+
this.history.push({
|
|
136
|
+
position: predictPos,
|
|
137
|
+
time: Date.now(),
|
|
138
|
+
clientTime: Date.now(),
|
|
139
|
+
type: "pdr"
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* 获取 最后一个视觉坐标在加了pdr惯性的坐标
|
|
146
|
+
*/ getLastVisionPdrPos() {
|
|
147
|
+
const lastIndex = this.history.findLastIndex((item)=>item.type === "vision");
|
|
148
|
+
if (lastIndex !== -1) {
|
|
149
|
+
var _this_history_;
|
|
150
|
+
return (_this_history_ = this.history[lastIndex + 1]) != null ? _this_history_ : this.history[lastIndex];
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
_setVisionHistoryForAngle(history) {
|
|
155
|
+
if (!this.visionHistory.length) {
|
|
156
|
+
this.visionHistory.push(history);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const angle = calculateLineDirection([
|
|
160
|
+
this.visionHistory.slice(-1)[0].position,
|
|
161
|
+
history.position
|
|
162
|
+
]);
|
|
163
|
+
if (angle !== null) {
|
|
164
|
+
// console.error("角度差", this.angle, angle, Math.abs(this.angle! - angle))
|
|
165
|
+
if (this.angle && Math.abs(this.angle - angle) > 60) {
|
|
166
|
+
this.angleExcessesCount++;
|
|
167
|
+
} else {
|
|
168
|
+
this.angleExcessesCount = 0;
|
|
169
|
+
this.visionHistory.push(history);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// 连续三次角度大于90度就修改角度
|
|
173
|
+
if (this.angleExcessesCount > 3) {
|
|
174
|
+
console.warn("连续三次角度比较歪,重新矫正");
|
|
175
|
+
// this.angle = angle!;
|
|
176
|
+
this.visionHistory = [
|
|
177
|
+
history
|
|
178
|
+
];
|
|
179
|
+
this.angleExcessesCount = 0;
|
|
180
|
+
} else {
|
|
181
|
+
// 保留两米的点
|
|
182
|
+
let distance = getLength(this.visionHistory[0].position, this.visionHistory.slice(-1)[0].position);
|
|
183
|
+
while(distance > 5 && this.visionHistory.length > 15){
|
|
184
|
+
this.visionHistory.shift();
|
|
185
|
+
distance = getLength(this.visionHistory[0].position, this.visionHistory.slice(-1)[0].position);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// 更新角度
|
|
189
|
+
this.changeAngle();
|
|
190
|
+
}
|
|
191
|
+
addHistory(data) {
|
|
192
|
+
this.history.push(data);
|
|
193
|
+
// 处理history超出
|
|
194
|
+
let visionHistory = this.history.filter((item)=>item.type === 'vision');
|
|
195
|
+
// 最后一个视觉点的时间离当前时间的差值
|
|
196
|
+
const lastVisionTimeDelta = visionHistory.length ? Date.now() - visionHistory.slice(-1)[0].clientTime : 0;
|
|
197
|
+
let maxDistance = getLength(this.history[0].position, this.history.slice(-1)[0].position);
|
|
198
|
+
// 如果有5s内的视觉点,保留3m内的点,如果没有5s内的视觉点,保留10米的蓝牙点
|
|
199
|
+
const MAX_DISTANCE = lastVisionTimeDelta > 5000 ? 10 : 3;
|
|
200
|
+
// 最少保留5个点 但是如果已经5s没有视觉点了就全部抛弃,采用蓝牙点
|
|
201
|
+
const MIN_POSITION_HISTORY = lastVisionTimeDelta > 5000 ? 0 : 5;
|
|
202
|
+
if (lastVisionTimeDelta > 5000) {
|
|
203
|
+
console.warn("视觉结果超出5s不可用,全部清空");
|
|
204
|
+
}
|
|
205
|
+
while(visionHistory.length > MIN_POSITION_HISTORY && maxDistance > MAX_DISTANCE){
|
|
206
|
+
this.history.shift();
|
|
207
|
+
visionHistory = this.history.filter((item)=>item.type === 'vision');
|
|
208
|
+
maxDistance = getLength(this.history[0].position, this.history.slice(-1)[0].position);
|
|
209
|
+
}
|
|
210
|
+
// 如果视觉都失效了用蓝牙更新速度和角度
|
|
211
|
+
if (MIN_POSITION_HISTORY === 0) {
|
|
212
|
+
// this.changeAngle(this.history.filter(item => item.type === "beacon"))
|
|
213
|
+
this.changeSpeed();
|
|
214
|
+
} else if (data.type === "vision") {
|
|
215
|
+
// 如果是视觉点位就更新速度
|
|
216
|
+
this.changeSpeed();
|
|
217
|
+
}
|
|
218
|
+
this.changePosition(data.type);
|
|
34
219
|
}
|
|
35
220
|
setBeaconPosition(position, time, duration) {
|
|
36
|
-
|
|
221
|
+
const clientTime = Date.now() - duration;
|
|
222
|
+
const item = {
|
|
37
223
|
position,
|
|
38
224
|
time,
|
|
39
|
-
clientTime
|
|
225
|
+
clientTime,
|
|
40
226
|
type: "beacon"
|
|
41
|
-
}
|
|
42
|
-
const
|
|
43
|
-
this.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
227
|
+
};
|
|
228
|
+
const lastServerIndex = this.history.findLastIndex((item)=>item.type !== "pdr");
|
|
229
|
+
const lastServerItem = this.history[lastServerIndex];
|
|
230
|
+
if (!lastServerItem) {
|
|
231
|
+
// 之前没有定位结果采用蓝牙结果
|
|
232
|
+
this.addHistory(item);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (isNil(this.pathAngle) || !this.speed) {
|
|
236
|
+
// 定位结果还不足以支持pdr 采用后端的结果
|
|
237
|
+
this.addHistory(item);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
// 收到了一个已经过去了的点,弃用
|
|
241
|
+
if (lastServerItem && lastServerItem.time > time) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
// 预估这个视觉点的正确位置
|
|
245
|
+
const predictPos = predictFuturePosition(position, this.speed, 360 - this.pathAngle, duration);
|
|
246
|
+
const distance = getLength(predictPos, this.history.slice(-1)[0].position);
|
|
247
|
+
if (distance > 5) {
|
|
248
|
+
this.beaconExcessesCount++;
|
|
249
|
+
} else {
|
|
250
|
+
this.beaconExcessesCount = 0;
|
|
251
|
+
}
|
|
252
|
+
if (this.beaconExcessesCount > 3) {
|
|
253
|
+
console.warn("连续三次beacon和pdr差距大于5米,使用beacon校准");
|
|
254
|
+
// 采用beacon结果
|
|
255
|
+
this.addHistory(item);
|
|
256
|
+
this.beaconExcessesCount = 0;
|
|
257
|
+
}
|
|
48
258
|
}
|
|
49
259
|
startPositionTimer() {
|
|
50
260
|
if (this.positionTimer) {
|
|
51
261
|
this.timer.clearInterval(this.positionTimer);
|
|
52
262
|
}
|
|
53
|
-
if (this.autoCleanTimer) {
|
|
54
|
-
this.timer.clearTimeout(this.autoCleanTimer);
|
|
55
|
-
}
|
|
56
263
|
this.positionTimer = this.timer.setInterval(()=>{
|
|
57
|
-
|
|
58
|
-
this.dispatchEvent({
|
|
59
|
-
type: "change-pos",
|
|
60
|
-
value: posInfo
|
|
61
|
-
});
|
|
264
|
+
this.changePosition();
|
|
62
265
|
}, 20);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
266
|
+
}
|
|
267
|
+
changePosition(from) {
|
|
268
|
+
if (from === void 0) from = 'pdr';
|
|
269
|
+
const posInfo = this.getPosition(from);
|
|
270
|
+
if (posInfo.success) {
|
|
271
|
+
this.history.push({
|
|
272
|
+
position: posInfo.pos,
|
|
273
|
+
time: Date.now(),
|
|
274
|
+
clientTime: Date.now(),
|
|
275
|
+
type: "pdr"
|
|
276
|
+
});
|
|
277
|
+
if (this.speed) {
|
|
278
|
+
// 再往前补2米的车头的距离
|
|
279
|
+
// const headPos = predictFuturePosition(
|
|
280
|
+
// posInfo.pos,
|
|
281
|
+
// this.speed,
|
|
282
|
+
// 360 - this.pathAngle!,
|
|
283
|
+
// 2 / this.speed
|
|
284
|
+
// )
|
|
285
|
+
this.dispatchEvent({
|
|
286
|
+
type: "change-pos",
|
|
287
|
+
value: _extends({}, posInfo)
|
|
288
|
+
});
|
|
289
|
+
} else {
|
|
290
|
+
this.dispatchEvent({
|
|
291
|
+
type: "change-pos",
|
|
292
|
+
value: posInfo
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
67
296
|
}
|
|
68
297
|
getPosition(from) {
|
|
69
298
|
if (this.history.length === 0) {
|
|
@@ -82,45 +311,34 @@ export class CarInertialPosition extends EventDispatcher {
|
|
|
82
311
|
return {
|
|
83
312
|
success: true,
|
|
84
313
|
pos: lastHistory.position,
|
|
85
|
-
compass:
|
|
86
|
-
speed:
|
|
314
|
+
compass: this.pathAngle,
|
|
315
|
+
speed: this.speed
|
|
87
316
|
};
|
|
88
317
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
type: "change-compass",
|
|
97
|
-
value: angle
|
|
98
|
-
});
|
|
99
|
-
// this.compass.setAbsoluteCompass(angle, lastHistory.time);
|
|
318
|
+
if (isNil(this.pathAngle) || !this.speed) {
|
|
319
|
+
return {
|
|
320
|
+
success: true,
|
|
321
|
+
pos: lastHistory.position,
|
|
322
|
+
compass: this.pathAngle,
|
|
323
|
+
speed: this.speed
|
|
324
|
+
};
|
|
100
325
|
}
|
|
101
326
|
const lastTime = lastHistory.clientTime;
|
|
102
327
|
const deltaTime = Date.now() - lastTime;
|
|
103
|
-
// 预估后的速度
|
|
104
|
-
const speed = predictFutureSpeed(this.history, deltaTime);
|
|
105
|
-
// console.log("speed", from, speed, transformSpeed(speed), deltaTime, lastTime)
|
|
106
328
|
// 根据角度和速度时间计算预估后的位置
|
|
107
|
-
const pos = this.
|
|
329
|
+
const pos = this.pathAngle ? predictFuturePosition(lastHistory.position, this.speed, 360 - this.pathAngle, deltaTime) : lastHistory.position;
|
|
108
330
|
return {
|
|
109
331
|
success: true,
|
|
110
332
|
pos,
|
|
111
|
-
compass: this.
|
|
112
|
-
speed: transformSpeed(speed)
|
|
333
|
+
compass: this.pathAngle,
|
|
334
|
+
speed: transformSpeed(this.speed)
|
|
113
335
|
};
|
|
114
336
|
}
|
|
115
337
|
dispose() {
|
|
116
|
-
this.compass.stop();
|
|
117
338
|
this.timer.dispose();
|
|
118
339
|
}
|
|
119
340
|
constructor(){
|
|
120
|
-
super(), this.history = [], this.
|
|
121
|
-
|
|
122
|
-
// this.compassAngle = value;
|
|
123
|
-
// this.dispatchEvent({ type: "change-compass", value })
|
|
124
|
-
// })
|
|
341
|
+
super(), this.history = [], this.visionHistory = [], this.speed = 0, this.angle = null, this.timer = new Timer(), this.positionTimer = null, this.speedFilter = new KalmanFilter(), this.visionExcessesCount = 0, this.beaconExcessesCount = 0, this.angleExcessesCount = 0, this.pathAngle = 0;
|
|
342
|
+
this.startPositionTimer();
|
|
125
343
|
}
|
|
126
344
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EventDispatcher } from "three";
|
|
2
|
+
import { KalmanFilter } from "./kalman-filter";
|
|
2
3
|
interface SensorEventMap {
|
|
3
4
|
start: {};
|
|
4
5
|
stop: {};
|
|
@@ -15,12 +16,10 @@ interface CompassData {
|
|
|
15
16
|
*/
|
|
16
17
|
export declare class Compass extends EventDispatcher<SensorEventMap> {
|
|
17
18
|
compassData: CompassData[];
|
|
18
|
-
|
|
19
|
-
time: number;
|
|
20
|
-
compass: number;
|
|
21
|
-
};
|
|
22
|
-
delta: number;
|
|
19
|
+
delta: number | null;
|
|
23
20
|
deltas: number[];
|
|
21
|
+
kalmanFilter: KalmanFilter;
|
|
22
|
+
deltaExcessesCount: number;
|
|
24
23
|
constructor();
|
|
25
24
|
start(): void;
|
|
26
25
|
deviceOrientationAbsHandler: (e: DeviceOrientationEvent) => void;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "core-js/modules/es.array.push.js";
|
|
2
2
|
import { isNil } from "lodash";
|
|
3
3
|
import { isIphone } from "../../utils";
|
|
4
4
|
import { EventDispatcher } from "three";
|
|
5
|
-
import {
|
|
5
|
+
import { KalmanFilter } from "./kalman-filter";
|
|
6
6
|
/**
|
|
7
7
|
* 监听compass 在获取到视觉定位的角度之后,计算视觉和compass的误差,保存一下这个误差,在后续定位中不断的调整这个误差
|
|
8
8
|
*/ export class Compass extends EventDispatcher {
|
|
@@ -17,16 +17,12 @@ import { removeOutliers } from "./utils";
|
|
|
17
17
|
* @param compass
|
|
18
18
|
* @param time
|
|
19
19
|
*/ setAbsoluteCompass(compass, time) {
|
|
20
|
-
if (
|
|
20
|
+
if (isNil(this.delta)) {
|
|
21
21
|
this.emitCompass(compass);
|
|
22
22
|
}
|
|
23
23
|
if (!this.compassData.length) {
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
|
-
this.absoluteCompass = {
|
|
27
|
-
compass: compass,
|
|
28
|
-
time
|
|
29
|
-
};
|
|
30
26
|
let cutTimeCompassIndex = this.compassData.findIndex((item)=>item.timestamp >= time);
|
|
31
27
|
if (cutTimeCompassIndex === -1) {
|
|
32
28
|
cutTimeCompassIndex = this.compassData.length - 1;
|
|
@@ -42,18 +38,32 @@ import { removeOutliers } from "./utils";
|
|
|
42
38
|
curCompass = nextCompass.timestamp - time > prevCompass.timestamp - time ? prevCompass : nextCompass;
|
|
43
39
|
}
|
|
44
40
|
const delta = compass - curCompass.res;
|
|
45
|
-
this.
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
this.delta =
|
|
41
|
+
const filteredDelta = this.kalmanFilter.filter(delta);
|
|
42
|
+
this.deltas.push(filteredDelta);
|
|
43
|
+
if (isNil(this.delta)) {
|
|
44
|
+
this.delta = filteredDelta;
|
|
45
|
+
} else {
|
|
46
|
+
console.error(Math.abs(delta - this.delta), Math.abs(filteredDelta - this.delta), compass, curCompass.res);
|
|
47
|
+
if (Math.abs(filteredDelta - this.delta) > 10) {
|
|
48
|
+
this.deltaExcessesCount++;
|
|
49
|
+
} else {
|
|
50
|
+
this.deltaExcessesCount = 0;
|
|
51
|
+
this.delta = filteredDelta;
|
|
52
|
+
}
|
|
53
|
+
if (this.deltaExcessesCount > 10) {
|
|
54
|
+
console.warn("连续10次角度变化比较大", this.delta, filteredDelta);
|
|
55
|
+
this.delta = filteredDelta;
|
|
56
|
+
this.deltaExcessesCount = 0;
|
|
57
|
+
}
|
|
49
58
|
}
|
|
50
59
|
}
|
|
51
60
|
emitCompass(compass) {
|
|
61
|
+
var _this_delta;
|
|
52
62
|
// 把 compass 限制在 0 ~ 360
|
|
53
|
-
|
|
63
|
+
const delta = (_this_delta = this.delta) != null ? _this_delta : 0;
|
|
54
64
|
this.dispatchEvent({
|
|
55
65
|
type: "compass",
|
|
56
|
-
value: (compass +
|
|
66
|
+
value: (compass + delta + 360) % 360
|
|
57
67
|
});
|
|
58
68
|
}
|
|
59
69
|
/**
|
|
@@ -67,32 +77,27 @@ import { removeOutliers } from "./utils";
|
|
|
67
77
|
});
|
|
68
78
|
}
|
|
69
79
|
}
|
|
70
|
-
checkSensor() {
|
|
71
|
-
|
|
72
|
-
return
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
deviceOrientation
|
|
76
|
-
};
|
|
77
|
-
})();
|
|
80
|
+
async checkSensor() {
|
|
81
|
+
const deviceOrientation = await this.checkDeviceOrientation();
|
|
82
|
+
return {
|
|
83
|
+
deviceOrientation
|
|
84
|
+
};
|
|
78
85
|
}
|
|
79
|
-
checkDeviceOrientation() {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
86
|
+
async checkDeviceOrientation() {
|
|
87
|
+
var _window_DeviceOrientationEvent;
|
|
88
|
+
if (!isIphone) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
if (typeof window.DeviceOrientationEvent !== "undefined" && typeof ((_window_DeviceOrientationEvent = window.DeviceOrientationEvent) == null ? void 0 : _window_DeviceOrientationEvent.requestPermission) === "function") {
|
|
92
|
+
try {
|
|
93
|
+
var _window_DeviceOrientationEvent1;
|
|
94
|
+
const permission = await ((_window_DeviceOrientationEvent1 = window.DeviceOrientationEvent) == null ? void 0 : _window_DeviceOrientationEvent1.requestPermission());
|
|
95
|
+
return permission === "granted";
|
|
96
|
+
} catch (e) {
|
|
97
|
+
return false;
|
|
93
98
|
}
|
|
94
|
-
|
|
95
|
-
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
96
101
|
}
|
|
97
102
|
stop() {
|
|
98
103
|
if (isIphone) {
|
|
@@ -108,7 +113,7 @@ import { removeOutliers } from "./utils";
|
|
|
108
113
|
});
|
|
109
114
|
}
|
|
110
115
|
constructor(){
|
|
111
|
-
super(), this.compassData = [], this.
|
|
116
|
+
super(), this.compassData = [], this.delta = null, this.deltas = [], this.kalmanFilter = new KalmanFilter(), this.deltaExcessesCount = 0, this.deviceOrientationAbsHandler = (e)=>{
|
|
112
117
|
const currTime = Date.now();
|
|
113
118
|
const { alpha, beta, gamma } = e;
|
|
114
119
|
if (isNil(alpha) || isNil(beta) || isNil(gamma)) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface KalmanFilterOptions {
|
|
2
|
+
measurementNoise: number;
|
|
3
|
+
processNoise: number;
|
|
4
|
+
errorEstimate: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class KalmanFilter {
|
|
7
|
+
estimate: number;
|
|
8
|
+
errorEstimate: number;
|
|
9
|
+
processNoise: number;
|
|
10
|
+
measurementNoise: number;
|
|
11
|
+
constructor(options?: Partial<KalmanFilterOptions>);
|
|
12
|
+
filter(measurement: number): number;
|
|
13
|
+
}
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// 卡尔曼滤波器实现
|
|
2
|
+
export class KalmanFilter {
|
|
3
|
+
// 核心滤波方法
|
|
4
|
+
filter(measurement) {
|
|
5
|
+
// 1. 预测阶段
|
|
6
|
+
const prediction = this.estimate;
|
|
7
|
+
const predictionError = this.errorEstimate + this.processNoise;
|
|
8
|
+
// 2. 计算卡尔曼增益
|
|
9
|
+
const kalmanGain = predictionError / (predictionError + this.measurementNoise);
|
|
10
|
+
// 3. 更新估计
|
|
11
|
+
this.estimate = prediction + kalmanGain * (measurement - prediction);
|
|
12
|
+
// 4. 更新误差
|
|
13
|
+
this.errorEstimate = (1 - kalmanGain) * predictionError;
|
|
14
|
+
return this.estimate;
|
|
15
|
+
}
|
|
16
|
+
constructor(options = {}){
|
|
17
|
+
// 当前状态估计
|
|
18
|
+
this.estimate = 0;
|
|
19
|
+
// 估计误差
|
|
20
|
+
this.errorEstimate = 1;
|
|
21
|
+
// 过程噪声(系统内在变化)
|
|
22
|
+
this.processNoise = 0.1;
|
|
23
|
+
// 测量噪声(传感器不确定性)
|
|
24
|
+
this.measurementNoise = 1.0;
|
|
25
|
+
this.measurementNoise = options.measurementNoise || 1.0;
|
|
26
|
+
// 过程噪声(系统内在变化)
|
|
27
|
+
this.processNoise = options.processNoise || 0.1;
|
|
28
|
+
this.errorEstimate = options.errorEstimate || 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -17,7 +17,7 @@ export declare function calculateInstantaneousSpeed(positions: CarPosition[]): n
|
|
|
17
17
|
* @param timeIntervalMs
|
|
18
18
|
* @returns
|
|
19
19
|
*/
|
|
20
|
-
export declare function predictFutureSpeed(positions: CarPosition[], timeIntervalMs: number): number;
|
|
20
|
+
export declare function predictFutureSpeed(positions: CarPosition[], timeIntervalMs: number): number | null;
|
|
21
21
|
/**
|
|
22
22
|
* 转换速度从 m/ms 转成 km/h
|
|
23
23
|
* @param speed
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import "core-js/modules/web.dom-collections.iterator.js";
|
|
2
|
+
import "core-js/modules/es.array.push.js";
|
|
3
|
+
import "core-js/modules/es.array.sort.js";
|
|
1
4
|
import { Vector2 } from "three";
|
|
2
5
|
import { getLength } from "../../utils";
|
|
3
6
|
// 计算最小二乘法的斜率 (m) 和截距 (b)
|
|
@@ -113,11 +116,11 @@ function getLineEndpoints(m, b, points) {
|
|
|
113
116
|
// const n = speeds.length;
|
|
114
117
|
const first = positions[0];
|
|
115
118
|
const last = positions.slice(-1)[0];
|
|
116
|
-
const distance =
|
|
117
|
-
const l = getLength(cur.position, positions[index].position);
|
|
118
|
-
return sum + l;
|
|
119
|
-
}, 0);
|
|
119
|
+
const distance = getLength(last.position, first.position);
|
|
120
120
|
const time = last.time - first.time;
|
|
121
|
+
if (time < 100 || distance < 3) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
121
124
|
return distance / time;
|
|
122
125
|
// if (n === 0) {
|
|
123
126
|
// return 0; // 如果没有速度数据,返回0
|
|
@@ -165,7 +168,7 @@ function getLineEndpoints(m, b, points) {
|
|
|
165
168
|
const vY = Math.sin(angleInRadians);
|
|
166
169
|
const dir = new Vector2(vX, vY);
|
|
167
170
|
// 一般车头到四轮中间的位置是2米左右
|
|
168
|
-
vec.add(dir.normalize().multiplyScalar(distance
|
|
171
|
+
vec.add(dir.normalize().multiplyScalar(distance));
|
|
169
172
|
return [
|
|
170
173
|
vec.x,
|
|
171
174
|
vec.y
|