原项目地址
https://github.com/maptalks/maptalks.routeplayer/
魔改后效果
因为项目需求这个3年前就停止更新的插件已经没法满足饥渴难耐的产品了(旺财),所以在此基础上做了一些修改 将点位和时间关联在了一起 加上进度条可以进行点位的前进和后退 类似一个视频动画的回放 不说了上代码
import * as maptalks from "maptalks";
const options = {
unitTime: 1 * 1000,
showRoutes: true,
showTrail: true,
maxTrailLine: 0,
markerSymbol: null,
lineSymbol: {
lineWidth: 2,
lineColor: "#004A8D",
},
trailLineSymbol: {
lineColor: "rgba(250,0,0,1)",
lineWidth: 4,
lineJoin: "round", //miter, round, bevel
lineCap: "round", //butt, round, square
lineDasharray: null, //dasharray, e.g. [10, 5, 5]
"lineOpacity ": 1,
},
};
export class Route {
constructor(r) {
this.route = r;
this.path = r.path;
}
// 计算方位角
getAzimuth(prvCoordinate, curCoordinate) {
let azimuth = 0;
const [lat1, lon1] = prvCoordinate,
[lat2, lon2] = curCoordinate,
dlat = lat2 - lat1,
dlon = lon2 - lon1;
if (dlon === 0) {
azimuth = dlat > 0 ? 0 : -90;
} else if (dlat === 0) {
azimuth = dlon > 0 ? 90 : 270;
} else {
azimuth = (Math.atan(dlat / dlon) * 180) / Math.PI;
if (dlon < 0) {
azimuth += 180;
} else if (dlon >= 0 && dlat < 0) {
azimuth += 360;
}
}
return azimuth;
}
// _total_distance
getCoordinates(distance, map) {
if (distance < this.getStart() || distance > this.getEnd()) {
return null;
}
var idx = null;
let payload = null,
currentCoordinate = null;
for (let i = 0, l = this.path.length; i < l; i++) {
if (distance < this.path[i][2]) {
idx = i;
payload = this.path[i][2];
currentCoordinate = this.path[i - 1];
// console.log("this.path", this.path[i]);
break;
}
if (distance == this.path[i][2]) {
payload = this.path[i][2];
currentCoordinate = this.path[i - 1];
// console.log("this.path2", this.path[i]);
}
}
if (idx === null) {
idx = this.path.length - 1;
}
const p1 = this.path[idx - 1],
p2 = this.path[idx],
span = distance - p1[2],
r = span / (p2[2] - p1[2]);
const x = p1[0] + (p2[0] - p1[0]) * r,
y = p1[1] + (p2[1] - p1[1]) * r,
coord = new maptalks.Coordinate(x, y),
vp = map.coordinateToViewPoint(coord);
const degree = this.getAzimuth([p1[0], p1[1]], [x, y]);
// maptalks.Util.computeDegree(
// map.coordinateToViewPoint(new maptalks.Coordinate(p1)),
// vp
// );
// console.log("coord", coord);
return {
coordinate: coord,
// now: p
currentCoordinate, // 当前数组中的点位对象
viewPoint: vp,
degree: degree,
index: idx,
payload,
};
}
getStart() {
return this.path[0][2];
}
getEnd() {
return this.path[this.getCount() - 1][2];
}
getStartTimeStamp() {
return this.path[0][3];
}
getEndTimeStamp() {
return this.path[this.getCount() - 1][3];
}
getCount() {
return this.path.length;
}
get markerSymbol() {
return this.route.markerSymbol;
}
set markerSymbol(symbol) {
this.route.markerSymbol = symbol;
if (this._painter && this._painter.marker) {
this._painter.marker.setSymbol(symbol);
}
}
get lineSymbol() {
return this.route.lineSymbol;
}
set lineSymbol(symbol) {
this.route.lineSymbol = symbol;
if (this._painter && this._painter.marker) {
this._painter.line.setSymbol(symbol);
}
}
get trailLineSymbol() {
return this.route.trailLineSymbol;
}
set trailLineSymbol(symbol) {
this.route.trailLineSymbol = symbol;
if (this._painter && this._painter.marker) {
this._painter.trailLine.setSymbol(symbol);
}
}
}
export class RoutePlayer extends maptalks.Eventable(maptalks.Class) {
constructor(routes, map, timeCost, opts) {
super(opts);
if (!Array.isArray(routes)) {
routes = [routes];
}
this.id = maptalks.Util.UID();
this._map = map;
this._routers = [];
// 记录总用时时间缓存 方便倍速计算取用
this.timeCost_temp = routes[0].timeCost;
// 记录总用时时间
this._timeCost = routes[0].timeCost;
// 倍速
this._rat = 1;
// 已经跑完的路程
this.played = 0;
// 总路程
this._total_distance = 0;
/**
* 操作是否为点击倍速 当选中倍速是需要重绘player 需要将旧player进行 finish终止动画
* 后重新创建新的 避免内存消耗 会出发动画回调_step方法 将无法判断是重绘引发的动画播放完成
* 还是动画真正播放完成
*/
this._ratMode = false;
// 是否为拖动进度条操作
this._barMode = false;
// 动画播放完成的回调
this.finishCallback = routes[0].finishCallback || null;
// path点位变化回调
this.pathCoordinateChange = routes[0].pathCoordinateChange || null;
// 是否获取物理地址
this._address = routes[0].address || false;
// 记录上一次获取的物理地址值
this._addressTemp = null;
// 记录上一次调用方法时间戳
this._prvTimestamp = 0;
// 记录当前累加时间出
this._accumulateTimestamp = 0;
// 降低地理位置请求频率
this._timer = null;
// 降低当前时间请求频率
this._timer_time = null;
// 地理位置延时时间
this._timerDelay = routes[0].timerDelay || 1 * 1000;
this._self = this;
this._setup(routes);
}
remove() {
if (!this.markerLayer) {
return this;
}
this.finish();
this.markerLayer.remove();
this.lineLayer.remove();
this.trailLineLayer.remove();
delete this.markerLayer;
delete this.lineLayer;
delete this.trailLineLayer;
delete this._map;
return this;
}
play(delay, callback) {
if (this.player.playState === "running") {
return this;
}
this.player.play();
this.fire("playstart");
if (delay) {
setTimeout(() => {
this.player.pause();
setTimeout(callback, delay);
}, 1);
}
return this;
}
pause() {
if (this.player.playState === "paused") {
return this;
}
this.player.pause();
this.fire("playpause");
return this;
}
cancel() {
this.player.cancel();
this.played = 0;
this._timeCost = this.timeCost_temp;
this.trailLinePoints = [];
let line = this.trailLineLayer.getGeometries()[0];
if (line !== undefined) line.setCoordinates(this.trailLinePoints);
setTimeout(() => {
this._createPlayer();
this._step({ styles: { t: 0 } });
this.fire("playcancel");
// console.log("cancel");
}, 10);
return this;
}
finish() {
if (this.player.playState === "finished") {
return this;
}
// complete trail line
let line = this.trailLineLayer.getGeometries()[0];
let coors = this.routes[0].path.map((item) => {
return [item[0], item[1]];
});
this.trailLinePoints = coors;
line.setCoordinates(this.trailLinePoints);
this._timeCost = this.timeCost_temp;
this.player.finish();
this._step({ styles: { t: 1 } });
this.fire("playfinish");
return this;
}
getStartTime() {
return this.startTime || 0;
}
getEndTime() {
return this.endTime || 0;
}
setTime(t) {
this.played = t - this.startTime;
if (this.played < 0) {
this.played = 0;
}
this._resetPlayer();
return this;
}
getUnitTime() {
return this.options["unitTime"];
}
setUnitTime(ut) {
this.options["unitTime"] = +ut;
this._resetPlayer();
}
getCurrentProperties(index) {
if (!index) {
index = 0;
}
if (!this.routes[index] || !this.routes[index]._painter) {
return null;
}
return this.routes[index]._painter.marker.getProperties();
}
getCurrentCoordinates(index) {
if (!index) {
index = 0;
}
if (!this.routes[index] || !this.routes[index]._painter) {
return null;
}
return this.routes[index]._painter.marker.getCoordinates();
}
getMarkerSymbol(idx) {
if (this.routes && this.routes[idx]) {
return this.routes[idx].markerSymbol;
}
return null;
}
setMarkerSymbol(idx, symbol) {
if (this.routes && this.routes[idx]) {
this.routes[idx].markerSymbol = symbol;
}
return this;
}
getLineSymbol(idx) {
if (this.routes && this.routes[idx]) {
return this.routes[idx].lineSymbol;
}
return null;
}
setLineSymbol(idx, symbol) {
if (this.routes && this.routes[idx]) {
this.routes[idx].lineSymbol = symbol;
}
return this;
}
showRoute() {
this.lineLayer.show();
}
showTrail() {
this.trailLineLayer.show();
}
hideRoute() {
this.lineLayer.hide();
}
hideTrail() {
this.trailLineLayer.hide();
}
_resetPlayer(play) {
const playing = this.player && this.player.playState === "running";
if (playing) {
this.player.finish();
this.trailLinePoints = [];
let line = this.trailLineLayer.getGeometries()[0];
if (line !== undefined) line.setCoordinates(this.trailLinePoints);
}
this._createPlayer();
if (playing && play) {
this.player.play();
}
}
/** 倍速 */
setRat(rat) {
// 将2x 4x 等转换为数字
rat = parseFloat(rat);
this._rat = rat;
this._timeCost = this.timeCost_temp;
this._ratMode = true;
// 重新计算倍速用时
if (rat !== 1) this._timeCost = this._timeCost * (1 / rat);
this._resetPlayer(false);
}
/** 拖动进度条更新点位和轨迹 */
_updateMarkerAndTrailLineString(schedule) {
const arr = this.routes[0].path;
this._barMode = true;
this.trailLinePoints = [];
this.pause();
this.played = this._total_distance * schedule;
/**
* 注:
* 1.点位每两秒1个(不是精准2秒)
* 2.timeCost_temp为点位真实时长的总和
* 3.地图时间跟点位真实时间不是一个概念
* 4.maptalks.animation.Animation.animate duration用时要跟进度条时间一致
*/
//查找传入点位集合 点位每两秒推送一次 故进度条step为2 所以接受的index要除2拿到对应点位的数组下标
for (let i = 0; i < arr.length; i++) {
// console.log("_updateMarkerAndTrailLineString", arr[i]);
if (this.played <= arr[i][2]) {
// return console.log(
// "_updateMarkerAndTrailLineString",
// this.played,
// arr,
// arr[i]
// );
let coordinates = this.routes[0].getCoordinates(this.played, this._map);
// 总时间减当前选中进度条点位的下标计算剩余时间
this._timeCost =
(this.timeCost_temp - this.timeCost_temp * schedule) *
(1 / this._rat);
this._accumulateTimestamp = coordinates.currentCoordinate.slice(-1)[0];
// 点位marker定位
this._step({ styles: { t: schedule } });
// 创建新的player动画 [this.played / this.duration, 1] 代表动画点位的起点
// this.played / this.duration 为小于零的百分比
return this._createPlayer(this._timeCost, [schedule, 1]);
}
}
}
_createPlayer(timeCost, t = [0, 1]) {
this.player = null;
let duration = this._timeCost;
// 如果有timeCost 代表是进度条变更
if (timeCost) duration = timeCost;
// 没有则进行倍速计算 防止进度条与动画不同步
else {
duration = this.timeCost_temp * (1 / this._rat);
}
this.player = maptalks.animation.Animation.animate(
{
t,
},
{
duration,
easing: "easing",
},
this._step.bind(this)
);
}
_step(frame) {
if (frame && frame.state && frame.state.playState !== "running") {
if (frame.state.playState === "finished" && !this._barMode) {
if (this.finishCallback) {
this.finishCallback();
}
this.cancel();
if (this._ratMode) {
return (this._ratMode = false);
}
}
return;
}
if (this._barMode) {
for (let i = 0, l = this.routes.length; i < l; i++) {
this._drawRoute(this.routes[i], this.played, frame.styles.t);
this._barMode = false;
}
} else {
this.played = this._total_distance * frame.styles.t;
for (let i = 0, l = this.routes.length; i < l; i++) {
this._drawRoute(this.routes[i], this.played, frame.styles.t);
this.fire("playing");
}
}
}
_http(long, lat) {
const httpRequest = new XMLHttpRequest();
httpRequest.open(
"GET",
`https://restapi.amap.com/v3/geocode/regeo?key=a5432ed29b40ef351524ea736aa7aca5&location=${long},${lat}`,
true
); //第二步:打开连接 将请求参数写在url中 ps:"http://localhost:8080/rest/xxx"
httpRequest.send(); //第三步:发送请求 将请求参数写在URL中
/**
* 获取数据后的处理程序
*/
return new Promise((reslove) => {
httpRequest.onreadystatechange = function () {
if (httpRequest.readyState == 4 && httpRequest.status == 200) {
var json = httpRequest.responseText; //获取到json字符串,还需解析
console.log(JSON.parse(json));
const { status, regeocode: { formatted_address = "" } = {} } =
JSON.parse(json);
if (status === "1") {
reslove(formatted_address);
}
}
};
});
}
async _addressFun(long, lat) {
if (this._timer) {
clearTimeout(this._timer);
this._timer = null;
}
this._addressTemp = await this._http(long, lat);
}
/** 延迟获取逆向物理地址 */
_throttle(coordinates) {
const [long, lat] = coordinates.currentCoordinate.slice(0, 2);
this._timer = setTimeout(async () => {
this._addressFun(long, lat);
}, this._timerDelay);
}
_throttle_time(func, delay) {
if (!this._timer_time) {
this._timer_time = setTimeout(async () => {
func();
clearTimeout(this._timer_time);
this._timer_time = null;
}, delay);
}
}
_drawRoute(route, distance, t) {
if (!this._map) return;
let coordinates = route.getCoordinates(distance, this._map),
timestamp = new Date().getTime(),
duration = 0;
// 计算_drawRoute回调方法调用时间段
if (!this._prvTimestamp) {
this._prvTimestamp = timestamp;
this._nowTimestamp = timestamp;
this._accumulateTimestamp = coordinates.currentCoordinate.slice(-1)[0];
} else this._nowTimestamp = timestamp;
duration = this._nowTimestamp - this._prvTimestamp;
this._prvTimestamp = timestamp;
if (!coordinates) {
if (route._painter && route._painter.marker) {
route._painter.marker.remove();
delete route._painter.marker;
}
return;
}
if (!route._painter) {
route._painter = {};
}
if (!route._painter.marker) {
const marker = new maptalks.Marker(coordinates.coordinate, {
zIndex: 10,
symbol: Object.assign(
route.markerSymbol || this.options["markerSymbol"],
{
markerRotation: -coordinates.degree,
}
),
}).addTo(this.markerLayer);
route._painter.marker = marker;
} else {
// route._painter.marker.setProperties(coordinates.payload);
route._painter.marker.setCoordinates(coordinates.coordinate);
route._painter.marker.updateSymbol({
markerRotation: -coordinates.degree,
});
if (this.pathCoordinateChange) {
if (this._address) {
if (!this._timer) {
this._addressFun(
coordinates.coordinate.x,
coordinates.coordinate.y
);
this._throttle(coordinates);
}
// coordinates.currentCoordinate [long, lat,distance,timestamp]
// [long, lat,distance,timestamp,schedule,timeCost,address]
this.pathCoordinateChange([
coordinates.coordinate.x,
coordinates.coordinate.y,
this._total_distance,
(this._accumulateTimestamp += duration),
t,
this.timeCost_temp,
this._addressTemp,
]);
} else {
this.pathCoordinateChange([
coordinates.coordinate.x,
coordinates.coordinate.y,
this._total_distance,
(this._accumulateTimestamp += duration),
t,
this.timeCost_temp,
"",
]);
}
}
}
if (!route._painter.line) {
const line = new maptalks.LineString(route.path, {
symbol: route.lineSymbol || this.options["lineSymbol"],
zIndex: 5,
}).addTo(this.lineLayer);
route._painter.line = line;
}
if (!route._painter.trailLine) {
this.trailLinePoints = [coordinates.coordinate];
const trailLine = new maptalks.LineString([], {
symbol: route.trailLineSymbol || this.options["trailLineSymbol"],
zIndex: 5,
}).addTo(this.trailLineLayer);
route._painter.trailLine = trailLine;
} else {
// remove extra trail point by maxTrailLine, 0 => disable
const maxLineCount = this.options["maxTrailLine"];
if (maxLineCount !== 0 && this.trailLinePoints.length > maxLineCount) {
this.trailLinePoints.shift();
}
this.trailLinePoints.push(coordinates.coordinate);
if (this.trailLinePoints.length > 1) {
route._painter.trailLine.setCoordinates(this.trailLinePoints);
}
}
}
_setup(rs) {
const routes = rs.map((r) => new Route(r));
this._routers = routes[0];
var start = routes[0].getStart(),
end = routes[0].getEnd();
console.log("this._routers", this._routers);
for (let i = 1; i < routes.length; i++) {
let route = routes[i];
if (route.getStart() < start) {
start = route.getStart();
}
if (route.getEnd() > end) {
end = route.getEnd();
}
}
this.trailLinePoints = [];
this.routes = routes;
this.startTime = start;
this.endTime = end;
this.played = 0;
this._total_distance = end - start;
this._createLayers();
this._createPlayer();
}
_createLayers() {
this.lineLayer = new maptalks.VectorLayer(
maptalks.INTERNAL_LAYER_PREFIX + "_routeplay_r_" + this.id,
[],
{ visible: this.options["showRoutes"], enableSimplify: false, zIndex: 5 }
).addTo(this._map);
this.trailLineLayer = new maptalks.VectorLayer(
maptalks.INTERNAL_LAYER_PREFIX + "_routeplay_t_" + this.id,
[],
{ visible: this.options["showTrail"], enableSimplify: false, zIndex: 6 }
).addTo(this._map);
this.markerLayer = new maptalks.VectorLayer(
maptalks.INTERNAL_LAYER_PREFIX + "_routeplay_m_" + this.id,
[],
{
zIndex: 6,
}
).addTo(this._map);
}
_clearLayers() {
this.lineLayer.clear();
this.trailLineLayer.clear();
this.markerLayer.clear();
this.played = 0;
this.player.finish();
this.trailLinePoints = [];
let line = this.trailLineLayer.getGeometries()[0];
if (line !== undefined) line.setCoordinates(this.trailLinePoints);
}
}
RoutePlayer.mergeOptions(options);
/** 使用方式 */
animatePath(
timeCost,
path,
() => {
setPlayerStatus("stop");
setHistoryCarInfo(null);
},
// path点位变化
(coordinate) => pathCoordinateChange(coordinate),
carCode,
carTypeId,
trakCollection,
uploadTime,
HBMap
);
/** 轨迹动画点位path */
function animatePath(
timeCost: number,
path: number[],
finishCallback: () => void,
pathCoordinateChange: (
coordinate: [number, number, number, number, number, number, string]
) => void,
carCode: string,
carTypeId: number,
trakCollection: Coordinate[],
uploadTime: number[],
HBMap: HBMap
) {
HBMap.addNewCarMapObject({
carCode,
carGeo: trakCollection[0],
workStatus: "",
follow: false,
trakCollection,
uploadTime,
line: null,
carMarker: null,
azimuth: [],
} as unknown as CarMarkerData);
return [
{
// 动画执行完的回调
finishCallback: () => {
console.log("finishCallback");
finishCallback();
},
// path点位变化
pathCoordinateChange,
// 是否获取物理地址
address: false,
// 获取地址延迟
timerDelay: path.length * 2,
// (path.length / 2) * 10,
// 总用时 毫秒
timeCost,
// 路线
path,
//marker's symbol
markerSymbol: {
markerFile: utils.getCarStatusSvg(Number(carTypeId), "ONLINE"),
markerHeight: 60,
markerWidth: 60,
markerOpacity: 1,
markerVerticalAlignment: "middle",
// markerRotation: azimuth.concat().pop(),
},
//route line's symbol
lineSymbol: { lineColor: "#03B86C", lineWidth: 8 },
//route line's trail symbol
maxTrailLine: 2,
trailLineSymbol: {
lineColor: "#1d51d2",
lineWidth: 10,
lineJoin: "round", //miter, round, bevel
lineCap: "round", //butt, round, square
lineDasharray: null, //dasharray, e.g. [10, 5, 5]
"lineOpacity ": 1,
},
},
];
}
// 重绘动画点位 进度条拖动 @params:param 进度条当前位置 百分比或1之内的小数
player.value._updateMarkerAndTrailLineString(param);
评论 (0)