<template>
<view>
<view class="map" id="mapDiv" style="width: 100%;height: 67vh;z-index: 0;position: relative;" :prop="dataOption"
:change:prop="Trenderjs.initTMap" :change:renderTrigger="Trenderjs.handleMessage"
:renderTrigger="triggerCounter">
</view>
</view>
</template>
<script>
export default {
data() {
return {
triggerCounter: 0,
isProcessing: false,
message: null
};
},
props: {
dataOption: {
type: String,
default: ''
},
},
methods: {
renderjsData() {
if (this.isProcessing) return;
this.isProcessing = true;
this.triggerCounter++;
},
someMethodInVue(newVal) {
this.$emit('childEvent', newVal);
this.message = newVal;
this.isProcessing = false;
}
}
};
</script>
<script module="Trenderjs" lang="renderjs">
var control;
export default {
data() {
return {
tk: '27657afc3318f8f2166fceba83427734',
options: {
lng: '104.397894',
lat: '31.126855',
},
Tmap: null,
gisDataObj: {},
gisData: {},
geocode: null,
gisPointLngLat: {},
// 定位优化相关变量
positionHistory: [], // 位置历史记录
maxHistorySize: 10, // 最大历史记录数
realTimeMarker: null, // 实时定位标记
realTimeLabel: null, // 实时定位标签
// 动画相关变量
animationTarget: null, // 动画目标位置
animationStep: 0, // 动画步数
animationTimer: null, // 动画计时器
animationDuration: 500, // 动画时长(ms)
hasFitViewOnFirstLocation: false,
// 方向相关变量
currentHeading: 0, // 当前设备方向(度数)
compassWatchId: null, // 指南针监听ID
isCompassAvailable: false, // 是否支持指南针
lastHeadingUpdate: 0, // 上次方向更新时间
headingFilterFactor: 0.1, // 方向滤波系数
// 修改 baseIcon 为更明显的箭头图标
baseIcon: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiIgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIj48Y2lyY2xlIGN4PSIxNiIgY3k9IjE2IiByPSIxMCIgZmlsbD0iIzNEODVGRiIgZmlsbC1vcGFjaXR5PSIwLjgiLz48cGF0aCBkPSJNMTYgMkwyOCAyOEg0eiIgZmlsbD0iIzNEODVGRiIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjIiLz48L3N2Zz4=',
iconCache: {}, // 缓存不同角度的图标
lastRenderedHeading: null // 上次渲染的角度
}
},
mounted() {
if (!window.T) {
const script = document.createElement('script')
script.src = 'http://api.tianditu.gov.cn/api?v=4.0&tk=' + this.tk
document.head.appendChild(script)
script.onload = this.initChartsRender.bind()
} else {
this.initMap();
}
this.initCompass();
},
methods: {
// 初始化地图
initMap() {
const {
lng,
lat
} = this.options;
this.Tmap = new T.Map('mapDiv');
this.Tmap.centerAndZoom(new T.LngLat(lng, lat), 15);
this.Tmap.setStyle("indigo");
// this.Tmap.disableDoubleClickZoom();
// this.Tmap.disableScrollWheelZoom();
// this.Tmap.disableInertia();
// this.Tmap.disableKeyboard();
this.geocode = new T.Geocoder();
// control = new T.Control.Zoom();
// this.Tmap.addControl(control);
// control.setPosition(T_ANCHOR_BOTTOM_RIGHT);
},
// 若没有加载地图方法
initChartsRender() {
this.initMap();
},
// 初始化地图数据
initTMap(newValue, oldValue) {
let that = this;
// 清除旧的定位监听
that._watchId && plus.geolocation.clearWatch(that._watchId);
// 启动新的定位监听
that._watchId = plus.geolocation.watchPosition(
position => that._handlePosition(position),
err => console.log('err', err), {
provider: 'system',
enableHighAccuracy: true,
maximumAge: 0,
timeout: 10000
}
);
// 处理传入的地图数据
if (newValue) {
setTimeout(function() {
try {
that.gisDataObj = JSON.parse(newValue);
if (that.gisDataObj.hasOwnProperty('data') && that.gisDataObj.data) {
that.gisData = JSON.parse(that.gisDataObj.data);
if (that.gisData.hasOwnProperty('data') && that.gisData.data) {
let gisPoint = JSON.parse(that.gisData.data);
that.gisPointLngLat = gisPoint;
// 设置中心点
let centerLng, centerLat;
if (that.gisData.type == 'marker') {
centerLng = gisPoint.lng;
centerLat = gisPoint.lat;
} else if (that.gisData.type == 'polyline' || that.gisData.type == 'polygon') {
centerLng = gisPoint[0].lng;
centerLat = gisPoint[0].lat;
} else {
centerLng = '104.397894';
centerLat = '31.126855';
}
that.Tmap.centerAndZoom(new T.LngLat(centerLng, centerLat), 15);
// 绘制地图元素
that._drawMapElements();
}
}
} catch (e) {
console.error('数据解析错误:', e);
}
}, 1000);
}
},
// 处理定位点 - 核心优化
_handlePosition(position) {
const coords = position.coords;
// console.log("position: " + JSON.stringify(position))
const now = Date.now();
const newPoint = {
lng: coords.longitude,
lat: coords.latitude,
accuracy: coords.accuracy,
timestamp: now
};
// 添加到历史记录
this.positionHistory.push(newPoint);
if (this.positionHistory.length > this.maxHistorySize) {
this.positionHistory.shift();
}
// 使用卡尔曼滤波平滑位置
const smoothedPoint = this._kalmanFilter(newPoint);
// 启动平滑动画
this._startAnimation(smoothedPoint);
// 只在第一次定位时适配视野
if (!this.hasFitViewOnFirstLocation) {
this._fitAllPoints();
this.hasFitViewOnFirstLocation = true;
}
},
// 卡尔曼滤波算法
_kalmanFilter(newPoint) {
// 初始化状态
if (!this.kalmanState) {
this.kalmanState = {
x: newPoint.lng,
y: newPoint.lat,
vx: 0, // x方向速度
vy: 0, // y方向速度
P: [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
], // 协方差矩阵
lastTime: newPoint.timestamp
};
return newPoint;
}
// 时间差(秒)
const dt = (newPoint.timestamp - this.kalmanState.lastTime) / 1000;
this.kalmanState.lastTime = newPoint.timestamp;
// 状态转移矩阵
const F = [
[1, 0, dt, 0],
[0, 1, 0, dt],
[0, 0, 1, 0],
[0, 0, 0, 1]
];
// 过程噪声协方差矩阵
const Q = [
[Math.pow(dt, 4) / 4, 0, Math.pow(dt, 3) / 2, 0],
[0, Math.pow(dt, 4) / 4, 0, Math.pow(dt, 3) / 2],
[Math.pow(dt, 3) / 2, 0, Math.pow(dt, 2), 0],
[0, Math.pow(dt, 3) / 2, 0, Math.pow(dt, 2)]
].map(row => row.map(val => val * 0.1)); // 噪声系数
// 预测状态
const x_pred = [
this.kalmanState.x + this.kalmanState.vx * dt,
this.kalmanState.y + this.kalmanState.vy * dt,
this.kalmanState.vx,
this.kalmanState.vy
];
// 预测协方差
let P_pred = this._matrixMultiply(F, this.kalmanState.P);
P_pred = this._matrixMultiply(P_pred, this._matrixTranspose(F));
P_pred = this._matrixAdd(P_pred, Q);
// 测量矩阵
const H = [
[1, 0, 0, 0],
[0, 1, 0, 0]
];
// 测量噪声协方差
const R = [
[Math.pow(newPoint.accuracy / 1000000, 2), 0],
[0, Math.pow(newPoint.accuracy / 1000000, 2)]
];
// 卡尔曼增益
const S = this._matrixAdd(
this._matrixMultiply(H, this._matrixMultiply(P_pred, this._matrixTranspose(H))),
R
);
const K = this._matrixMultiply(
this._matrixMultiply(P_pred, this._matrixTranspose(H)),
this._matrixInverse2x2(S)
);
// 更新状态
const z = [newPoint.lng, newPoint.lat];
const y = [
z[0] - (H[0][0] * x_pred[0] + H[0][1] * x_pred[1] + H[0][2] * x_pred[2] + H[0][3] * x_pred[3]),
z[1] - (H[1][0] * x_pred[0] + H[1][1] * x_pred[1] + H[1][2] * x_pred[2] + H[1][3] * x_pred[3])
];
const x_upd = [
x_pred[0] + K[0][0] * y[0] + K[0][1] * y[1],
x_pred[1] + K[1][0] * y[0] + K[1][1] * y[1],
x_pred[2] + K[2][0] * y[0] + K[2][1] * y[1],
x_pred[3] + K[3][0] * y[0] + K[3][1] * y[1]
];
// 更新协方差
const I = [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
];
const KH = this._matrixMultiply(K, H);
const I_KH = this._matrixSubtract(I, KH);
const P_upd = this._matrixMultiply(I_KH, P_pred);
// 更新状态
this.kalmanState = {
x: x_upd[0],
y: x_upd[1],
vx: x_upd[2],
vy: x_upd[3],
P: P_upd,
lastTime: newPoint.timestamp
};
return {
lng: x_upd[0],
lat: x_upd[1],
accuracy: newPoint.accuracy
};
},
// 矩阵乘法
_matrixMultiply(A, B) {
const result = [];
for (let i = 0; i < A.length; i++) {
result[i] = [];
for (let j = 0; j < B[0].length; j++) {
let sum = 0;
for (let k = 0; k < A[0].length; k++) {
sum += A[i][k] * B[k][j];
}
result[i][j] = sum;
}
}
return result;
},
// 矩阵转置
_matrixTranspose(A) {
return A[0].map((_, colIndex) => A.map(row => row[colIndex]));
},
// 矩阵加法
_matrixAdd(A, B) {
return A.map((row, i) => row.map((val, j) => val + B[i][j]));
},
// 矩阵减法
_matrixSubtract(A, B) {
return A.map((row, i) => row.map((val, j) => val - B[i][j]));
},
// 2x2矩阵求逆
_matrixInverse2x2(A) {
const det = A[0][0] * A[1][1] - A[0][1] * A[1][0];
return [
[A[1][1] / det, -A[0][1] / det],
[-A[1][0] / det, A[0][0] / det]
];
},
// 开始平滑动画
_startAnimation(targetPoint) {
// 取消之前的动画
if (this.animationTimer) {
clearInterval(this.animationTimer);
this.animationTimer = null;
}
// 设置目标位置
this.animationTarget = new T.LngLat(targetPoint.lng, targetPoint.lat);
// 如果没有当前位置,直接设置
if (!this.currentPosition) {
this.currentPosition = this.animationTarget;
this._updateRealTimeMarker(targetPoint);
return;
}
// 计算起点和终点
const startLng = this.currentPosition.getLng();
const startLat = this.currentPosition.getLat();
const endLng = this.animationTarget.getLng();
const endLat = this.animationTarget.getLat();
// 动画参数
const steps = Math.max(5, Math.min(20, this.animationDuration / 50));
const stepSize = 1 / steps;
let progress = 0;
// 启动动画
this.animationTimer = setInterval(() => {
progress += stepSize;
if (progress >= 1) {
clearInterval(this.animationTimer);
this.animationTimer = null;
this.currentPosition = this.animationTarget;
this._updateRealTimeMarker(targetPoint);
// 定位点动画结束后,适配视野
// this._fitAllPoints();
return;
}
// 使用缓动函数使动画更平滑
const easedProgress = this._easeInOutCubic(progress);
// 计算中间点
const currentLng = startLng + (endLng - startLng) * easedProgress;
const currentLat = startLat + (endLat - startLat) * easedProgress;
// 更新位置
this.currentPosition = new T.LngLat(currentLng, currentLat);
this._updateRealTimeMarker({
lng: currentLng,
lat: currentLat,
accuracy: targetPoint.accuracy
});
}, this.animationDuration / steps);
},
// 缓动函数(三次缓动)
_easeInOutCubic(t) {
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
},
// 更新实时定位标记
// _updateRealTimeMarker(point) {
// const mapPoint = new T.LngLat(point.lng, point.lat);
// if (!this.realTimeMarker) {
// // 创建标记
// this.realTimeMarker = new T.Marker(mapPoint);
// this.Tmap.addOverLay(this.realTimeMarker);
// // 创建文本标签
// this.realTimeLabel = new T.Label({
// // text: `实时定位:${point.lng.toFixed(6)},${point.lat.toFixed(6)}`,
// text: '实时定位',
// position: mapPoint,
// offset: new T.Point(-40, 10)
// });
// this.Tmap.addOverLay(this.realTimeLabel);
// } else {
// // 更新位置
// this.realTimeMarker.setLngLat(mapPoint);
// this.realTimeLabel.setLngLat(mapPoint);
// // this.realTimeLabel.setLabel(`实时定位:${point.lng.toFixed(6)},${point.lat.toFixed(6)}`);
// }
// // 平滑移动地图中心
// // this.Tmap.panTo(mapPoint);
// },
_fitAllPoints() {
let points = [];
// 点/线/面
if (this.gisData && this.gisPointLngLat) {
if (this.gisData.type === 'marker') {
points.push(new T.LngLat(this.gisPointLngLat.lng, this.gisPointLngLat.lat));
} else if (this.gisData.type === 'polyline' || this.gisData.type === 'polygon') {
points = points.concat(
this.gisPointLngLat.map(p => new T.LngLat(p.lng, p.lat))
);
}
}
// 当前定位点
if (this.currentPosition) {
points.push(this.currentPosition);
}
// 过滤无效点
points = points.filter(p => p && typeof p.getLng === 'function' && typeof p.getLat === 'function');
if (points.length > 0) {
this.Tmap.setViewport(points);
}
},
// 绘制地图元素(折线、多边形等)
_drawMapElements() {
if (!this.gisData || !this.gisPointLngLat) return;
switch (this.gisData.type) {
case 'marker':
this._drawMarker();
break;
case 'polyline':
this._drawPolyline();
break;
case 'polygon':
this._drawPolygon();
break;
}
// 新增:绘制完后适配视野
this._fitAllPoints();
},
// 绘制标记点
_drawMarker() {
const point = new T.LngLat(this.gisPointLngLat.lng, this.gisPointLngLat.lat);
const marker = new T.Marker(point);
this.Tmap.addOverLay(marker);
const label = new T.Label({
text: this.gisData.name,
position: point,
offset: new T.Point(-40, 10)
});
this.Tmap.addOverLay(label);
},
// 绘制折线
_drawPolyline() {
const points = this.gisPointLngLat.map(p =>
new T.LngLat(p.lng, p.lat)
);
const polyline = new T.Polyline(points, {
color: this.gisDataObj.color || '#FF0000',
weight: 6,
opacity: 0.7
});
this.Tmap.addOverLay(polyline);
// 添加终点标签
const endPoint = points[points.length - 1];
const label = new T.Label({
text: this.gisData.name,
position: endPoint,
offset: new T.Point(-40, 10)
});
this.Tmap.addOverLay(label);
},
// 绘制多边形
_drawPolygon() {
const points = this.gisPointLngLat.map(p =>
new T.LngLat(p.lng, p.lat)
);
const polygon = new T.Polygon(points, {
color: this.gisDataObj.color || '#1E90FF',
weight: 3,
fillColor: this.gisDataObj.color || '#1E90FF',
fillOpacity: 0.3
});
this.Tmap.addOverLay(polygon);
// 添加中心点标签
const center = this._calculateCenter(points);
const label = new T.Label({
text: this.gisData.name,
position: center,
offset: new T.Point(-40, 10)
});
this.Tmap.addOverLay(label);
},
// 计算多边形中心点
_calculateCenter(points) {
const sum = points.reduce((acc, point) => {
acc.lng += point.getLng();
acc.lat += point.getLat();
return acc;
}, {
lng: 0,
lat: 0
});
return new T.LngLat(
sum.lng / points.length,
sum.lat / points.length
);
},
// RenderJS事件处理器
handleMessage(newVal, oldVal) {
if (newVal !== oldVal && this.gisPointLngLat) {
const geolocation = new T.Geolocation();
geolocation.getCurrentPosition(e => {
const distance = this.getDistance(
e.lnglat.lat,
e.lnglat.lng,
this.gisPointLngLat.lat,
this.gisPointLngLat.lng
);
this.$ownerInstance.callMethod('someMethodInVue', {
lng: e.lnglat.lng,
lat: e.lnglat.lat,
distance: distance * 1000
});
});
}
},
// 计算两点间距离(Haversine公式)
getDistance(lat1, lng1, lat2, lng2) {
const radLat1 = (lat1 * Math.PI) / 180.0;
const radLat2 = (lat2 * Math.PI) / 180.0;
const a = radLat1 - radLat2;
const b = (lng1 * Math.PI) / 180.0 - (lng2 * Math.PI) / 180.0;
const s = 2 * Math.asin(
Math.sqrt(
Math.pow(Math.sin(a / 2), 2) +
Math.cos(radLat1) *
Math.cos(radLat2) *
Math.pow(Math.sin(b / 2), 2)
)
);
return s * 6378.137; // 返回公里数
},
// 初始化指南针
initCompass() {
if (plus.orientation && plus.orientation.watchOrientation) {
this.isCompassAvailable = true;
this.compassWatchId = plus.orientation.watchOrientation(
(orientation) => this._handleCompass(orientation),
(err) => console.error('指南针错误:', err)
);
} else {
console.warn('设备不支持指南针功能');
this.isCompassAvailable = false;
}
},
// 处理指南针数据
_handleCompass(orientation) {
const now = Date.now();
// 限制更新频率(100ms/次)
if (now - this.lastHeadingUpdate < 100) return;
this.lastHeadingUpdate = now;
let heading = orientation.magneticHeading;
// 平滑处理
if (this.currentHeading === undefined) {
this.currentHeading = heading;
} else {
let diff = heading - this.currentHeading;
if (diff > 180) diff -= 360;
else if (diff < -180) diff += 360;
this.currentHeading += diff * this.headingFilterFactor;
if (this.currentHeading >= 360) this.currentHeading -= 360;
else if (this.currentHeading < 0) this.currentHeading += 360;
}
// 更新标记方向
this._updateMarkerDirection();
},
// 更新标记方向
_updateMarkerDirection() {
if (!this.realTimeMarker || this.currentHeading === undefined) return;
// 检查是否需要更新
if (this.lastRenderedHeading === this.currentHeading) return;
// 尝试从缓存获取
if (this.iconCache[this.currentHeading]) {
this._setMarkerIcon(this.iconCache[this.currentHeading]);
this.lastRenderedHeading = this.currentHeading;
return;
}
// 动态生成旋转图标
this._generateRotatedIcon(this.currentHeading)
.then(rotatedIcon => {
this.iconCache[this.currentHeading] = rotatedIcon;
this._setMarkerIcon(rotatedIcon);
this.lastRenderedHeading = this.currentHeading;
});
},
// 生成旋转后的图标
_generateRotatedIcon(heading) {
return new Promise((resolve) => {
const size = 30;
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
const img = new Image();
img.src = this.baseIcon;
img.onload = () => {
// 清除画布
ctx.clearRect(0, 0, size, size);
// 移动到中心点
ctx.translate(size / 2, size / 2);
// 旋转画布
ctx.rotate((heading * Math.PI) / 180);
// 绘制图像(注意调整位置)
ctx.drawImage(img, -size / 2, -size / 2, size, size);
// 恢复画布状态
ctx.setTransform(1, 0, 0, 1, 0, 0);
// 获取Base64数据
resolve(canvas.toDataURL('image/png'));
};
});
},
// 设置标记图标
_setMarkerIcon(iconUrl) {
if (!this.realTimeMarker) return;
this.realTimeMarker.setIcon(new T.Icon({
iconUrl: iconUrl,
iconSize: new T.Point(30, 30),
className: 'direction-marker'
}));
},
// 在_updateRealTimeMarker中添加方向支持
_updateRealTimeMarker(point) {
const mapPoint = new T.LngLat(point.lng, point.lat);
if (!this.realTimeMarker) {
// 使用基础图标创建标记
this.realTimeMarker = new T.Marker(mapPoint, {
icon: new T.Icon({
iconUrl: this.baseIcon,
iconSize: new T.Point(30, 30),
className: 'direction-marker'
})
});
this.Tmap.addOverLay(this.realTimeMarker);
// 创建文本标签
this.realTimeLabel = new T.Label({
text: '实时定位',
position: mapPoint,
offset: new T.Point(-40, 10)
});
this.Tmap.addOverLay(this.realTimeLabel);
} else {
// 更新位置
this.realTimeMarker.setLngLat(mapPoint);
this.realTimeLabel.setLngLat(mapPoint);
}
// 如果支持指南针,更新方向
if (this.isCompassAvailable) {
this._updateMarkerDirection();
}
},
},
// 在组件销毁时清理
beforeDestroy() {
// 清除位置监听
this._watchId && plus.geolocation.clearWatch(this._watchId);
// 清除指南针监听
if (this.compassWatchId && plus.orientation.clearWatch) {
plus.orientation.clearWatch(this.compassWatchId);
}
this.iconCache = {};
}
};
</script>
两个问题
1.icon我想变得更明显一点,和打车软件自身的icon一样,有指向有圆球,指向是可以转动的
2.现在的转动一点都不丝滑,我想转动和市面上的软件一样,比如高德,百度,美团等