图标定位修改

This commit is contained in:
cwchen 2026-01-14 15:47:21 +08:00
parent 6a6c6b1ac5
commit 0ca54d558d
1 changed files with 345 additions and 90 deletions

View File

@ -414,6 +414,10 @@
baiduGeolocation: null,
lastHeading: null, // 记录上一次的方向,用于避免频繁更新图标
updateMarkerTimer: null, // 防抖定时器
headingHistory: [], // 方向历史记录,用于平滑滤波(移动平均)
headingHistorySize: 5, // 历史记录大小5个样本的移动平均
lastOrientationUpdateTime: 0, // 上次方向更新时间,用于节流
headingOffset: 0, // 方向偏移量用于校正方向偏差如果需要反转方向可以设置为180
elements: {
panel: null,
tree: null,
@ -549,7 +553,7 @@
// 开始监听位置变化(异步获取位置,不会立即有位置信息)
this.startWatchingPosition();
// 开始监听设备方向变化
// 开始监听设备方向变化(使用设备方向传感器)
this.startWatchingOrientation();
},
@ -657,6 +661,8 @@
lat: point.lat,
accuracy: accuracy
};
// 方向由设备方向传感器提供,不在这里设置
this.updateLocationMarker();
isFirstLocation = false;
@ -702,8 +708,10 @@
point.lat
);
// 如果位置变化超过3米或者精度提升超过10米则更新
if (distance > 3 || accuracy < this.state.currentLocation.accuracy - 10) {
// 位置变化超过2米就更新或者精度提升超过5米
if (distance > 2 ||
(accuracy < this.state.currentLocation.accuracy - 5) ||
(accuracy < 50 && this.state.currentLocation.accuracy >= 50)) {
shouldUpdate = true;
}
}
@ -715,6 +723,8 @@
lat: point.lat,
accuracy: accuracy
};
// 方向由设备方向传感器提供,不在这里更新
this.updateLocationMarker();
}
}
@ -754,9 +764,9 @@
point.lat
);
// 如果位置变化超过5米或者精度提升超过10米则更新
if (distance > 5 ||
(accuracy < this.state.currentLocation.accuracy - 10) ||
// 位置变化超过2米就更新或者精度提升超过5米
if (distance > 2 ||
(accuracy < this.state.currentLocation.accuracy - 5) ||
(accuracy < 50 && this.state.currentLocation.accuracy >= 50)) {
shouldUpdate = true;
}
@ -777,15 +787,17 @@
lat: point.lat,
accuracy: accuracy
};
// 方向由设备方向传感器实时提供,不在这里更新
this.updateLocationMarker();
}
}
}
}, {
enableHighAccuracy: true, // 使用GPS精确定位
timeout: 8000 // 8秒超时
timeout: 6000 // 6秒超时
});
}, 2000); // 每2秒更新一次实时跟踪移动
}, 500); // 每0.5秒更新一次,提高实时性和灵敏度
// 保存geolocation实例以便后续使用
this.baiduGeolocation = geolocation;
@ -1285,7 +1297,7 @@
},
/**
* 更新定位标记(使用requestAnimationFrame实现平滑更新避免闪烁
* 更新定位标记(实时更新,优先更新位置
*/
updateLocationMarker() {
// 确保地图已加载完成
@ -1304,6 +1316,7 @@
}
// 使用requestAnimationFrame实现平滑更新保持实时性
// 对于位置更新,立即执行;对于方向更新,异步执行
this.updateMarkerTimer = requestAnimationFrame(() => {
this.updateMarkerTimer = null;
this._doUpdateLocationMarker();
@ -1320,35 +1333,33 @@
// 如果标记已存在,直接更新(避免删除重建)
if (this.overlays.locationMarker) {
try {
// 更新位置百度地图GL API支持setPosition
// 优先更新位置(立即执行,不等待方向更新
if (typeof this.overlays.locationMarker.setPosition === 'function') {
// 直接更新位置,实时跟随移动(不检查距离,每次都更新以确保平滑)
this.overlays.locationMarker.setPosition(point);
} else {
// 如果不支持setPosition检查位置是否变化
// 如果不支持setPosition检查位置是否变化(降低阈值,提高灵敏度)
const currentPoint = this.overlays.locationMarker.getPosition();
// 降低坐标变化阈值从0.000001约0.1米到0.000005约0.5米),但实际应该总是更新
if (!currentPoint ||
Math.abs(currentPoint.lng - lng) > 0.00001 ||
Math.abs(currentPoint.lat - lat) > 0.00001) {
Math.abs(currentPoint.lng - lng) > 0.000005 ||
Math.abs(currentPoint.lat - lat) > 0.000005) {
// 位置变化,需要更新(但尽量不删除重建)
const needsRecreate = true;
if (needsRecreate) {
// 保存当前图标,避免重新创建
const currentIcon = this.overlays.locationMarker.getIcon();
this.map.removeOverlay(this.overlays.locationMarker);
this.overlays.locationMarker = new BMapGL.Marker(point, { icon: currentIcon });
this.map.addOverlay(this.overlays.locationMarker);
}
const currentIcon = this.overlays.locationMarker.getIcon();
this.map.removeOverlay(this.overlays.locationMarker);
this.overlays.locationMarker = new BMapGL.Marker(point, { icon: currentIcon });
this.map.addOverlay(this.overlays.locationMarker);
}
}
// 实时更新方向(降低阈值到1度保持实时性
const headingChanged = !this.lastHeading || Math.abs(this.state.currentHeading - this.lastHeading) >= 1;
// 实时更新方向使用角度差值计算考虑0-360度边界
const headingChanged = !this.lastHeading || Math.abs(this.angleDifference(this.state.currentHeading, this.lastHeading)) >= 1;
if (headingChanged) {
const oldHeading = this.lastHeading;
this.lastHeading = this.state.currentHeading;
// 实时更新图标使用requestAnimationFrame确保平滑
// 异步更新图标使用requestAnimationFrame确保平滑,不阻塞位置更新
requestAnimationFrame(() => {
this.createRotatedLocationIcon(this.state.currentHeading).then((icon) => {
if (this.overlays.locationMarker) {
@ -1492,10 +1503,24 @@
// 旋转画布heading是相对于正北的角度需要转换为相对于画布的角度
// 百度地图中heading是相对于正北的角度0-360顺时针为正
// 画布中0度是向右需要转换
// 如果方向反了,尝试反转:使用 (90 - heading) 或 (270 - heading)
// 或者添加180度来反转heading + 180
const rotation = (90 - heading) * Math.PI / 180; // 反转方向:从 (heading - 90) 改为 (90 - heading)
// 画布坐标系0度向右90度向下180度向左270度向上
// 地图坐标系0度正北90度正东180度正南270度正西
// 转换公式:画布角度 = 90度 - 地图角度因为画布的0度对应地图的90度
// 应用方向偏移量(用于校正方向偏差)
const adjustedHeading = this.normalizeHeading(heading + (this.headingOffset || 0));
// 转换为画布旋转角度
// 标准转换画布的0度向右对应地图的90度正东
// 所以:画布角度 = 90度 - 地图角度
let rotation = (90 - adjustedHeading) * Math.PI / 180;
// 如果方向仍然不对,可以尝试以下方案(取消注释):
// 方案1反转180度
// rotation = (270 - adjustedHeading) * Math.PI / 180;
// 方案2直接使用heading如果图标本身已经指向正确方向
// rotation = -adjustedHeading * Math.PI / 180;
ctx.rotate(rotation);
// 绘制图片(居中,保持原始宽高比)
@ -1512,85 +1537,306 @@
},
/**
* 开始监听设备方向变化
* 平滑方向角度处理0-360度边界问题
*/
normalizeHeading(heading) {
// 确保角度在0-360范围内
while (heading < 0) heading += 360;
while (heading >= 360) heading -= 360;
return heading;
},
/**
* 计算两个角度之间的最短差值考虑0-360度边界
*/
angleDifference(a, b) {
let diff = a - b;
if (diff > 180) diff -= 360;
if (diff < -180) diff += 360;
return diff;
},
/**
* 移动平均滤波(减少抖动)
*/
smoothHeading(rawHeading) {
// 初始化历史记录数组和大小(如果不存在)
if (!this.headingHistory) {
this.headingHistory = [];
}
if (!this.headingHistorySize) {
this.headingHistorySize = 5; // 默认5个样本的移动平均
}
// 添加到历史记录
this.headingHistory.push(rawHeading);
if (this.headingHistory.length > this.headingHistorySize) {
this.headingHistory.shift();
}
// 如果历史记录不足,直接返回原始值
if (this.headingHistory.length < 3) {
return rawHeading;
}
// 处理0-360度边界问题计算相对于第一个值的差值
const baseHeading = this.headingHistory[0];
const normalizedHistory = this.headingHistory.map(h => {
let diff = this.angleDifference(h, baseHeading);
return baseHeading + diff;
});
// 计算平均值
const sum = normalizedHistory.reduce((a, b) => a + b, 0);
const avg = sum / normalizedHistory.length;
return this.normalizeHeading(avg);
},
/**
* 从四元数计算方向角heading- 修正版本
*/
quaternionToHeading(qx, qy, qz, qw) {
// 四元数到欧拉角转换提取heading/azimuth
// 使用标准公式计算Z轴旋转角度heading
// 注意:四元数顺序可能是 [w, x, y, z] 或 [x, y, z, w],需要根据实际情况调整
// 这里假设是 [x, y, z, w] 格式
// 方法1使用标准四元数到欧拉角转换公式ZYX顺序
// 计算headingazimuth/yaw- 绕Z轴旋转
const siny_cosp = 2 * (qw * qz + qx * qy);
const cosy_cosp = 1 - 2 * (qy * qy + qz * qz);
let heading = Math.atan2(siny_cosp, cosy_cosp) * 180 / Math.PI;
// 转换为0-360度范围
heading = this.normalizeHeading(heading);
// 如果方向反了可以添加180度校正
// heading = (heading + 180) % 360;
return heading;
},
/**
* 开始监听设备方向变化(优化:优先使用旋转矢量传感器)
*/
startWatchingOrientation() {
// 绑定方法,确保在事件监听器中能正确访问 this
this.handleOrientation = this.handleOrientation.bind(this);
this.handleMotion = this.handleMotion.bind(this);
this.updateHeadingIfChanged = this.updateHeadingIfChanged.bind(this);
console.log('开始监听设备方向...');
// 初始化方向历史记录
this.headingHistory = [];
this.lastOrientationUpdateTime = 0;
// 检查是否支持 DeviceOrientationEvent
if (typeof DeviceOrientationEvent !== 'undefined' && typeof DeviceOrientationEvent.requestPermission === 'function') {
// iOS 13+ 需要请求权限
console.log('请求设备方向权限...');
DeviceOrientationEvent.requestPermission()
.then(response => {
if (response === 'granted') {
console.log('设备方向权限已授予');
window.addEventListener('deviceorientation', this.handleOrientation);
} else {
console.warn('设备方向权限被拒绝');
}
})
.catch(error => {
console.error('请求设备方向权限失败:', error);
});
} else if (window.DeviceOrientationEvent) {
// 其他设备直接监听
console.log('直接监听设备方向事件');
window.addEventListener('deviceorientation', this.handleOrientation);
} else {
console.log('DeviceOrientationEvent不支持尝试使用uni-app Compass API');
// 如果不支持,尝试使用 Compass APIuni-app
if (typeof plus !== 'undefined' && plus.compass) {
this.state.watchOrientationId = plus.compass.watchHeading(
(heading) => {
const newHeading = heading.magneticHeading;
if (Math.abs(newHeading - this.state.currentHeading) > 1) {
this.state.currentHeading = newHeading;
console.log('方向更新来自Compass:', newHeading);
// 只有在有位置信息时才更新标记
if (this.state.currentLocation) {
this.updateLocationMarker();
}
console.log('开始监听设备方向(优先使用旋转矢量传感器)...');
// 1. 优先尝试使用旋转矢量传感器通过DeviceOrientationEvent的quaternion
if (window.DeviceOrientationEvent) {
// 检查是否支持quaternion旋转矢量传感器
const testEvent = new DeviceOrientationEvent('test', {});
if (typeof DeviceOrientationEvent !== 'undefined' && typeof DeviceOrientationEvent.requestPermission === 'function') {
// iOS 13+ 需要请求权限
console.log('请求设备方向权限(尝试使用旋转矢量传感器)...');
DeviceOrientationEvent.requestPermission()
.then(response => {
if (response === 'granted') {
console.log('设备方向权限已授予,监听旋转矢量传感器');
window.addEventListener('deviceorientation', this.handleOrientation, { passive: false });
} else {
console.warn('设备方向权限被拒绝,尝试传统传感器');
this.fallbackToTraditionalSensor();
}
},
(error) => console.error('获取方向失败:', error),
{ frequency: 100 }
);
})
.catch(error => {
console.error('请求设备方向权限失败:', error);
this.fallbackToTraditionalSensor();
});
} else {
console.warn('设备方向功能不可用');
// 其他设备直接监听
console.log('直接监听设备方向事件(尝试旋转矢量传感器)');
window.addEventListener('deviceorientation', this.handleOrientation, { passive: false });
}
} else {
// 2. 尝试使用DeviceMotionEvent可能包含旋转矢量数据
if (window.DeviceMotionEvent) {
console.log('尝试使用DeviceMotionEvent旋转矢量传感器');
if (typeof DeviceMotionEvent !== 'undefined' && typeof DeviceMotionEvent.requestPermission === 'function') {
DeviceMotionEvent.requestPermission()
.then(response => {
if (response === 'granted') {
window.addEventListener('devicemotion', this.handleMotion, { passive: false });
} else {
this.fallbackToTraditionalSensor();
}
})
.catch(() => this.fallbackToTraditionalSensor());
} else {
window.addEventListener('devicemotion', this.handleMotion, { passive: false });
}
} else {
// 3. 降级到传统传感器
console.log('旋转矢量传感器不可用,使用传统传感器');
this.fallbackToTraditionalSensor();
}
}
},
/**
* 处理设备方向变化
* 处理DeviceMotion事件可能包含旋转矢量数据
*/
handleMotion(event) {
const now = Date.now();
if (now - this.lastOrientationUpdateTime < 50) {
return;
}
this.lastOrientationUpdateTime = now;
// 检查是否有旋转速率数据(融合传感器)
if (event.rotationRate) {
const { alpha, beta, gamma } = event.rotationRate;
if (alpha !== null && alpha !== undefined && !isNaN(alpha)) {
// 使用旋转速率计算方向(需要积分,这里简化处理)
// 实际应用中旋转矢量传感器通常通过DeviceOrientationEvent的quaternion提供
const rawHeading = this.normalizeHeading(alpha);
const smoothedHeading = this.smoothHeading(rawHeading);
this.updateHeadingIfChanged(smoothedHeading);
}
}
},
/**
* 降级到传统传感器
*/
fallbackToTraditionalSensor() {
console.log('使用传统方向传感器DeviceOrientationEvent alpha');
// 使用传统的DeviceOrientationEvent如果没有quaternion
if (window.DeviceOrientationEvent) {
if (typeof DeviceOrientationEvent !== 'undefined' && typeof DeviceOrientationEvent.requestPermission === 'function') {
DeviceOrientationEvent.requestPermission()
.then(response => {
if (response === 'granted') {
window.addEventListener('deviceorientation', this.handleOrientation, { passive: false });
} else {
this.fallbackToCompassAPI();
}
})
.catch(() => this.fallbackToCompassAPI());
} else {
window.addEventListener('deviceorientation', this.handleOrientation, { passive: false });
}
} else {
this.fallbackToCompassAPI();
}
},
/**
* 更新方向(如果变化足够大)
*/
updateHeadingIfChanged(newHeading) {
const oldHeading = this.state.currentHeading;
const diff = Math.abs(this.angleDifference(newHeading, oldHeading));
if (diff >= 1 || oldHeading === 0) {
this.state.currentHeading = newHeading;
if (this.state.currentLocation) {
requestAnimationFrame(() => {
this.updateLocationMarker();
});
}
}
},
/**
* 降级到Compass APIuni-app
*/
fallbackToCompassAPI() {
if (typeof plus !== 'undefined' && plus.compass) {
this.state.watchOrientationId = plus.compass.watchHeading(
(heading) => {
const rawHeading = heading.magneticHeading;
// 使用平滑滤波处理Compass数据
const smoothedHeading = this.smoothHeading(rawHeading);
const oldHeading = this.state.currentHeading;
const diff = Math.abs(this.angleDifference(smoothedHeading, oldHeading));
// 只有当方向变化超过1度时才更新
if (diff >= 1 || oldHeading === 0) {
this.state.currentHeading = smoothedHeading;
// 只有在有位置信息时才更新标记
if (this.state.currentLocation) {
requestAnimationFrame(() => {
this.updateLocationMarker();
});
}
}
},
(error) => console.error('获取方向失败:', error),
{ frequency: 100 } // 100ms更新一次
);
} else {
console.warn('设备方向功能完全不可用');
}
},
/**
* 处理设备方向变化(优化:优先使用旋转矢量传感器)
*/
handleOrientation(event) {
// alpha: 绕Z轴旋转指南针方向0-360度
// 注意:某些设备可能使用不同的属性
let newHeading = null;
const now = Date.now();
// 节流限制更新频率到每50ms一次20Hz平衡实时性和性能
if (now - this.lastOrientationUpdateTime < 50) {
return;
}
this.lastOrientationUpdateTime = now;
let rawHeading = null;
if (event.alpha !== null && event.alpha !== undefined && !isNaN(event.alpha)) {
newHeading = event.alpha;
} else if (event.webkitCompassHeading !== null && event.webkitCompassHeading !== undefined) {
// iOS Safari 可能使用 webkitCompassHeading
newHeading = event.webkitCompassHeading;
// 1. 优先使用旋转矢量传感器(四元数数据)- 最稳定
if (event.quaternion && Array.isArray(event.quaternion) && event.quaternion.length >= 4) {
// 旋转矢量传感器提供的四元数数据 [x, y, z, w]
const [qx, qy, qz, qw] = event.quaternion;
rawHeading = this.quaternionToHeading(qx, qy, qz, qw);
console.log('使用旋转矢量传感器(四元数):', rawHeading.toFixed(1), '度');
}
// 2. 检查是否有quaternion属性某些浏览器可能以对象形式提供
else if (event.quaternion && typeof event.quaternion === 'object') {
const q = event.quaternion;
if (q.x !== undefined && q.y !== undefined && q.z !== undefined && q.w !== undefined) {
rawHeading = this.quaternionToHeading(q.x, q.y, q.z, q.w);
console.log('使用旋转矢量传感器(四元数对象):', rawHeading.toFixed(1), '度');
}
}
// 3. 降级到传统传感器alpha
else if (event.alpha !== null && event.alpha !== undefined && !isNaN(event.alpha)) {
// event.alpha 是绕Z轴旋转的角度0-360度
// 在某些设备上alpha是逆时针的需要转换为顺时针
// 根据设备类型调整Android通常需要反转iOS可能不需要
// 先尝试不反转
rawHeading = event.alpha;
// 如果方向反了,可以尝试反转(取消下面的注释):
// rawHeading = (360 - event.alpha) % 360;
// 或者使用headingOffset来校正更灵活
// this.headingOffset = 180; // 在初始化时设置
}
// 4. iOS Safari 使用 webkitCompassHeading
else if (event.webkitCompassHeading !== null && event.webkitCompassHeading !== undefined) {
rawHeading = event.webkitCompassHeading;
}
// 5. 某些平板设备使用 absolute 模式
else if (event.absolute !== undefined && event.absolute && event.alpha !== null) {
rawHeading = (360 - event.alpha) % 360;
}
if (newHeading !== null && !isNaN(newHeading)) {
// 只有当方向变化超过1度时才更新避免频繁更新
const oldHeading = this.state.currentHeading;
if (Math.abs(newHeading - oldHeading) > 1 || oldHeading === 0) {
this.state.currentHeading = newHeading;
console.log('方向更新:', newHeading, '度');
// 只有在有位置信息时才更新标记
if (this.state.currentLocation) {
this.updateLocationMarker();
}
}
if (rawHeading !== null && !isNaN(rawHeading)) {
// 使用移动平均滤波平滑方向数据(减少抖动)
const smoothedHeading = this.smoothHeading(rawHeading);
// 更新方向(如果变化足够大)
this.updateHeadingIfChanged(smoothedHeading);
}
},
@ -1653,10 +1899,19 @@
this.state.watchOrientationId = null;
}
// 移除方向事件监听
// 移除方向事件监听(包括旋转矢量传感器和传统传感器)
if (this.handleOrientation) {
window.removeEventListener('deviceorientation', this.handleOrientation);
}
if (this.handleMotion) {
window.removeEventListener('devicemotion', this.handleMotion);
}
// 清理方向历史记录
if (this.headingHistory) {
this.headingHistory = [];
}
this.lastOrientationUpdateTime = 0;
// 移除定位标记
if (this.overlays.locationMarker && this.map) {