@aibee/crc-bmap 0.8.39 → 0.8.40

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.
@@ -0,0 +1,33 @@
1
+ import { EventDispatcher } from "three";
2
+ import { Compass } from "./compass";
3
+ export interface CarPosition {
4
+ position: [number, number];
5
+ time: number;
6
+ type: "vision" | "beacon";
7
+ }
8
+ export interface CarPosInfo {
9
+ success: boolean;
10
+ pos: [number, number];
11
+ compass: null | number;
12
+ speed: number;
13
+ }
14
+ interface CarInertialPositionEventMap {
15
+ 'change-compass': {
16
+ value: number;
17
+ };
18
+ 'change-pos': {
19
+ value: CarPosInfo;
20
+ };
21
+ }
22
+ export declare class CarInertialPosition extends EventDispatcher<CarInertialPositionEventMap> {
23
+ history: CarPosition[];
24
+ compass: Compass;
25
+ compassAngle: number;
26
+ constructor();
27
+ startCompass(): void;
28
+ setPosition(position: CarPosition['position'], time: number): void;
29
+ setBeaconPosition(position: CarPosition['position'], time: number): void;
30
+ getPosition(): CarPosInfo;
31
+ dispose(): void;
32
+ }
33
+ export {};
@@ -0,0 +1,90 @@
1
+ // 车辆惯性导航
2
+ import { EventDispatcher } from "three";
3
+ import { Compass } from "./compass";
4
+ import { calculateLineDirection, predictFuturePosition, predictFutureSpeed, transformSpeed } from "./utils";
5
+ export class CarInertialPosition extends EventDispatcher {
6
+ startCompass() {
7
+ this.compass.start();
8
+ }
9
+ setPosition(position, time) {
10
+ this.history.push({
11
+ position,
12
+ time,
13
+ type: "vision"
14
+ });
15
+ // 保留最近的6条数据
16
+ while(this.history.length > 6){
17
+ this.history.shift();
18
+ }
19
+ const posInfo = this.getPosition();
20
+ this.dispatchEvent({
21
+ type: "change-pos",
22
+ value: posInfo
23
+ });
24
+ }
25
+ setBeaconPosition(position, time) {
26
+ this.history.push({
27
+ position,
28
+ time,
29
+ type: "beacon"
30
+ });
31
+ const posInfo = this.getPosition();
32
+ this.dispatchEvent({
33
+ type: "change-pos",
34
+ value: posInfo
35
+ });
36
+ }
37
+ getPosition() {
38
+ if (this.history.length === 0) {
39
+ return {
40
+ success: false,
41
+ pos: [
42
+ 0,
43
+ 0
44
+ ],
45
+ compass: null,
46
+ speed: 0
47
+ };
48
+ }
49
+ const lastHistory = this.history.slice(-1)[0];
50
+ if (this.history.length < 2) {
51
+ return {
52
+ success: true,
53
+ pos: lastHistory.position,
54
+ compass: null,
55
+ speed: 0
56
+ };
57
+ }
58
+ const visionHistory = this.history.filter((item)=>item.type === "vision");
59
+ // 推导到当前时间距离最后一个定位点的时间
60
+ const angle = calculateLineDirection(visionHistory.map((history)=>history.position));
61
+ if (angle !== null) {
62
+ this.compass.setAbsoluteCompass(angle, lastHistory.time);
63
+ }
64
+ const lastTime = lastHistory.time;
65
+ const deltaTime = Date.now() - lastTime;
66
+ // 预估后的速度
67
+ const speed = predictFutureSpeed(this.history, deltaTime);
68
+ // 根据角度和速度时间计算预估后的位置
69
+ const pos = this.compassAngle ? predictFuturePosition(lastHistory.position, speed, 360 - this.compassAngle, deltaTime) : lastHistory.position;
70
+ return {
71
+ success: true,
72
+ pos,
73
+ compass: this.compassAngle,
74
+ speed: transformSpeed(speed)
75
+ };
76
+ }
77
+ dispose() {
78
+ this.compass.stop();
79
+ }
80
+ constructor(){
81
+ super(), this.history = [], this.compass = new Compass(), this.compassAngle = 0;
82
+ this.compass.addEventListener("compass", ({ value })=>{
83
+ this.compassAngle = value;
84
+ this.dispatchEvent({
85
+ type: "change-compass",
86
+ value
87
+ });
88
+ });
89
+ }
90
+ }
@@ -0,0 +1,44 @@
1
+ import { EventDispatcher } from "three";
2
+ interface SensorEventMap {
3
+ start: {};
4
+ stop: {};
5
+ compass: {
6
+ value: number;
7
+ };
8
+ }
9
+ interface CompassData {
10
+ timestamp: number;
11
+ res: number;
12
+ }
13
+ /**
14
+ * 监听compass 在获取到视觉定位的角度之后,计算视觉和compass的误差,保存一下这个误差,在后续定位中不断的调整这个误差
15
+ */
16
+ export declare class Compass extends EventDispatcher<SensorEventMap> {
17
+ compassData: CompassData[];
18
+ absoluteCompass: null | {
19
+ time: number;
20
+ compass: number;
21
+ };
22
+ delta: number;
23
+ deltas: number[];
24
+ constructor();
25
+ start(): void;
26
+ deviceOrientationAbsHandler: (e: DeviceOrientationEvent) => void;
27
+ /**
28
+ * 设置一个根据视觉定位返回的绝对角度 校准delta
29
+ * @param compass
30
+ * @param time
31
+ */
32
+ setAbsoluteCompass(compass: number, time: number): void;
33
+ emitCompass(compass: number): void;
34
+ /**
35
+ * 获取 compass 和 deviceMotion
36
+ */
37
+ listenDeviceOrientation(): void;
38
+ checkSensor(): Promise<{
39
+ deviceOrientation: boolean;
40
+ }>;
41
+ checkDeviceOrientation(): Promise<boolean>;
42
+ stop(): void;
43
+ }
44
+ export {};
@@ -0,0 +1,119 @@
1
+ import { _ as _async_to_generator } from "@swc/helpers/_/_async_to_generator";
2
+ import { isNil } from "lodash";
3
+ import { isIphone } from "../../utils";
4
+ import { EventDispatcher } from "three";
5
+ import { removeOutliers } from "./utils";
6
+ /**
7
+ * 监听compass 在获取到视觉定位的角度之后,计算视觉和compass的误差,保存一下这个误差,在后续定位中不断的调整这个误差
8
+ */ export class Compass extends EventDispatcher {
9
+ start() {
10
+ this.listenDeviceOrientation();
11
+ this.dispatchEvent({
12
+ type: "start"
13
+ });
14
+ }
15
+ /**
16
+ * 设置一个根据视觉定位返回的绝对角度 校准delta
17
+ * @param compass
18
+ * @param time
19
+ */ setAbsoluteCompass(compass, time) {
20
+ if (!this.compassData.length) {
21
+ return;
22
+ }
23
+ this.absoluteCompass = {
24
+ compass: compass,
25
+ time
26
+ };
27
+ let cutTimeCompassIndex = this.compassData.findIndex((item)=>item.timestamp >= time);
28
+ if (cutTimeCompassIndex === -1) {
29
+ cutTimeCompassIndex = this.compassData.length - 1;
30
+ }
31
+ const prevCompass = this.compassData[cutTimeCompassIndex - 1];
32
+ const nextCompass = this.compassData[cutTimeCompassIndex];
33
+ let curCompass;
34
+ if (!prevCompass) {
35
+ curCompass = nextCompass;
36
+ } else if (!nextCompass) {
37
+ return;
38
+ } else {
39
+ curCompass = nextCompass.timestamp - time > prevCompass.timestamp - time ? prevCompass : nextCompass;
40
+ }
41
+ const delta = compass - curCompass.res;
42
+ this.deltas.push(delta);
43
+ const deltas = removeOutliers(this.deltas);
44
+ this.delta = deltas.reduce((sum, cur)=>sum + cur, 0) / deltas.length;
45
+ }
46
+ emitCompass(compass) {
47
+ // 把 compass 限制在 0 ~ 360
48
+ this.dispatchEvent({
49
+ type: "compass",
50
+ value: (compass + this.delta + 360) % 360
51
+ });
52
+ }
53
+ /**
54
+ * 获取 compass 和 deviceMotion
55
+ */ listenDeviceOrientation() {
56
+ if (isIphone) {
57
+ window.addEventListener("deviceorientation", this.deviceOrientationAbsHandler, false);
58
+ } else {
59
+ window.addEventListener("deviceorientationabsolute", this.deviceOrientationAbsHandler, {
60
+ absolute: true
61
+ });
62
+ }
63
+ }
64
+ checkSensor() {
65
+ var _this = this;
66
+ return _async_to_generator(function*() {
67
+ const deviceOrientation = yield _this.checkDeviceOrientation();
68
+ return {
69
+ deviceOrientation
70
+ };
71
+ })();
72
+ }
73
+ checkDeviceOrientation() {
74
+ return _async_to_generator(function*() {
75
+ var _window_DeviceOrientationEvent;
76
+ if (!isIphone) {
77
+ return true;
78
+ }
79
+ if (typeof window.DeviceOrientationEvent !== "undefined" && typeof ((_window_DeviceOrientationEvent = window.DeviceOrientationEvent) == null ? void 0 : _window_DeviceOrientationEvent.requestPermission) === "function") {
80
+ try {
81
+ var _window_DeviceOrientationEvent1;
82
+ const permission = yield (_window_DeviceOrientationEvent1 = window.DeviceOrientationEvent) == null ? void 0 : _window_DeviceOrientationEvent1.requestPermission();
83
+ return permission === "granted";
84
+ } catch (e) {
85
+ return false;
86
+ }
87
+ }
88
+ return false;
89
+ })();
90
+ }
91
+ stop() {
92
+ if (isIphone) {
93
+ window.removeEventListener("deviceorientation", this.deviceOrientationAbsHandler, false);
94
+ } else {
95
+ window.removeEventListener("deviceorientationabsolute", this.deviceOrientationAbsHandler, {
96
+ absolute: true
97
+ });
98
+ }
99
+ this.compassData = [];
100
+ this.dispatchEvent({
101
+ type: "stop"
102
+ });
103
+ }
104
+ constructor(){
105
+ super(), this.compassData = [], this.absoluteCompass = null, this.delta = 0, this.deltas = [], this.deviceOrientationAbsHandler = (e)=>{
106
+ const currTime = Date.now();
107
+ const { alpha, beta, gamma } = e;
108
+ if (isNil(alpha) || isNil(beta) || isNil(gamma)) {
109
+ return;
110
+ }
111
+ const compass = isIphone ? e.webkitCompassHeading : 360 - alpha;
112
+ this.compassData.push({
113
+ timestamp: currTime,
114
+ res: compass
115
+ });
116
+ this.emitCompass(compass);
117
+ };
118
+ }
119
+ }
@@ -0,0 +1,2 @@
1
+ export * from './car-inertial-position';
2
+ export * from './utils';
@@ -0,0 +1,3 @@
1
+ // 车辆惯性导航
2
+ export * from './car-inertial-position';
3
+ export * from './utils';
@@ -0,0 +1,41 @@
1
+ import { CarPosition } from "./car-inertial-position";
2
+ /**
3
+ * 计算一组点位的斜率和方向
4
+ * @param points
5
+ * @returns
6
+ */
7
+ export declare function calculateLineDirection(points: [number, number][]): number | null;
8
+ /**
9
+ * 计算速度,单位是m/ms km/s
10
+ * @param positions
11
+ * @returns
12
+ */
13
+ export declare function calculateInstantaneousSpeed(positions: CarPosition[]): number[];
14
+ /**
15
+ * 预测一段时间后的速度
16
+ * @param speeds
17
+ * @param timeIntervalMs
18
+ * @returns
19
+ */
20
+ export declare function predictFutureSpeed(positions: CarPosition[], timeIntervalMs: number): number;
21
+ /**
22
+ * 转换速度从 m/ms 转成 km/h
23
+ * @param speed
24
+ * @returns
25
+ */
26
+ export declare function transformSpeed(speed: number): number;
27
+ /**
28
+ * 预估未来的位置
29
+ * @param lastPosition
30
+ * @param speed
31
+ * @param angle
32
+ * @param time
33
+ * @returns
34
+ */
35
+ export declare function predictFuturePosition(lastPosition: [number, number], speed: number, angle: number, time: number): [number, number];
36
+ /**
37
+ * 过滤偏差大的值
38
+ * @param data
39
+ * @returns
40
+ */
41
+ export declare function removeOutliers(data: number[]): number[];
@@ -0,0 +1,150 @@
1
+ import { Vector2 } from "three";
2
+ /**
3
+ * 计算一组点位的斜率和方向
4
+ * @param points
5
+ * @returns
6
+ */ export function calculateLineDirection(points) {
7
+ const n = points.length;
8
+ if (n < 2) {
9
+ return null;
10
+ }
11
+ const angles = [];
12
+ for(let i = 1; i < points.length; i++){
13
+ const point1 = new Vector2(points[i][0], points[i][1]);
14
+ const point0 = new Vector2(points[i - 1][0], points[i - 1][1]);
15
+ if (point1.distanceTo(point0) === 0) {
16
+ continue;
17
+ }
18
+ const radians = new Vector2().subVectors(point1, point0).angle() // 于X轴的正方向的夹角 这个是顺时针的角度
19
+ ;
20
+ const angle = 360 - (radians / Math.PI * 180 - 90 + 360) % 360;
21
+ angles.push(angle);
22
+ }
23
+ const filteredAngles = removeOutliers(angles);
24
+ if (!filteredAngles.length) {
25
+ return null;
26
+ }
27
+ let weight = 0;
28
+ // 最近的方向权重更大
29
+ const averageAngle = filteredAngles.reduce((sum, angle, i)=>{
30
+ weight += i + 1;
31
+ return sum + angle * (i + 1);
32
+ }, 0) / weight;
33
+ return averageAngle;
34
+ }
35
+ /**
36
+ * 计算速度,单位是m/ms km/s
37
+ * @param positions
38
+ * @returns
39
+ */ export function calculateInstantaneousSpeed(positions) {
40
+ const speeds = [];
41
+ for(let i = 1; i < positions.length; i++){
42
+ const prev = positions[i - 1];
43
+ const current = positions[i];
44
+ // 计算位置变化
45
+ const deltaX = current.position[0] - prev.position[0];
46
+ const deltaY = current.position[1] - prev.position[1];
47
+ const distance = Math.sqrt(deltaX ** 2 + deltaY ** 2);
48
+ // 计算时间变化
49
+ const deltaTime = current.time - prev.time; // 时间单位为毫秒
50
+ // 计算瞬时速度
51
+ if (deltaTime > 0) {
52
+ const speed = distance / deltaTime;
53
+ speeds.push(speed);
54
+ } else {
55
+ speeds.push(0); // 如果时间变化为0,速度为0
56
+ }
57
+ }
58
+ return speeds;
59
+ }
60
+ /**
61
+ * 预测一段时间后的速度
62
+ * @param speeds
63
+ * @param timeIntervalMs
64
+ * @returns
65
+ */ export function predictFutureSpeed(positions, timeIntervalMs) {
66
+ const speeds = calculateInstantaneousSpeed(positions);
67
+ const n = speeds.length;
68
+ if (n === 0) {
69
+ return 0; // 如果没有速度数据,返回0
70
+ }
71
+ // 获取当前速度
72
+ const currentSpeed = speeds[n - 1];
73
+ // 计算加速度(简单的平均加速度)
74
+ let acceleration = 0;
75
+ if (n > 1) {
76
+ const speedDifferences = speeds.slice(1).map((speed, index)=>speed - speeds[index]);
77
+ const time = positions.slice(1).reduce((sum, cur, index)=>sum + (cur.time - positions[index].time), 0);
78
+ acceleration = speedDifferences.reduce((sum, value)=>sum + value, 0) / time;
79
+ }
80
+ // 预测未来速度 m/ms
81
+ const futureSpeed = Math.max(currentSpeed + acceleration * timeIntervalMs, 0);
82
+ return futureSpeed;
83
+ }
84
+ /**
85
+ * 转换速度从 m/ms 转成 km/h
86
+ * @param speed
87
+ * @returns
88
+ */ export function transformSpeed(speed) {
89
+ // speed m/ms km/s
90
+ return speed * 60 * 60; // km/h
91
+ }
92
+ /**
93
+ * 预估未来的位置
94
+ * @param lastPosition
95
+ * @param speed
96
+ * @param angle
97
+ * @param time
98
+ * @returns
99
+ */ export function predictFuturePosition(lastPosition, speed, angle, time) {
100
+ // 将角度转换为弧度
101
+ const angleInRadians = angle * (Math.PI / 180);
102
+ const distance = speed * time;
103
+ console.log("angle speed", speed, distance);
104
+ const vec = new Vector2(lastPosition[0], lastPosition[1]);
105
+ // 计算角度在x和y方向的分量
106
+ const vX = Math.cos(angleInRadians);
107
+ const vY = Math.sin(angleInRadians);
108
+ const dir = new Vector2(vX, vY);
109
+ vec.add(dir.normalize().multiplyScalar(distance));
110
+ // // 计算未来位置
111
+ // const futureX = lastPosition[0] + vX * time;
112
+ // const futureY = lastPosition[1] + vY * time;
113
+ return [
114
+ vec.x,
115
+ vec.y
116
+ ];
117
+ }
118
+ /**
119
+ * 过滤偏差大的值
120
+ * @param data
121
+ * @returns
122
+ */ export function removeOutliers(data) {
123
+ // 排序数据
124
+ const d = [
125
+ ...data
126
+ ].sort((a, b)=>a - b);
127
+ // 计算四分位数 Q1 和 Q3
128
+ const q1 = percentile(d, 25);
129
+ const q3 = percentile(d, 75);
130
+ // 计算四分位距 IQR
131
+ const iqr = q3 - q1;
132
+ // 计算上下界限
133
+ const lowerBound = q1 - 1.5 * iqr;
134
+ const upperBound = q3 + 1.5 * iqr;
135
+ // 筛选出正常范围内的数据
136
+ const filteredData = d.filter((value)=>value >= lowerBound && value <= upperBound);
137
+ const originSortData = data.filter((item)=>filteredData.includes(item));
138
+ return originSortData;
139
+ }
140
+ // 计算百分位数(分位数)
141
+ function percentile(arr, p) {
142
+ const index = p / 100 * (arr.length - 1);
143
+ const lower = Math.floor(index);
144
+ const upper = Math.ceil(index);
145
+ if (lower === upper) {
146
+ return arr[lower];
147
+ }
148
+ const weight = index - lower;
149
+ return arr[lower] * (1 - weight) + arr[upper] * weight;
150
+ }
@@ -7,3 +7,4 @@ export * from './select';
7
7
  export * from './pdr-position';
8
8
  export * from './mul-floor-navigation';
9
9
  export * from './mul-floor-select';
10
+ export * from './car-inertial-position';
@@ -7,3 +7,4 @@ export * from './select';
7
7
  export * from './pdr-position';
8
8
  export * from './mul-floor-navigation';
9
9
  export * from './mul-floor-select';
10
+ export * from './car-inertial-position';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aibee/crc-bmap",
3
- "version": "0.8.39",
3
+ "version": "0.8.40",
4
4
  "description": "",
5
5
  "main": "lib/bmap.min.js",
6
6
  "module": "lib/bmap.esm.js",