maptalks.routeplayer.js 魔改版
maptalks.routeplayer.js 魔改版
浮川的小窝

maptalks.routeplayer.js 魔改版

面壁人浮川
2023-02-17 发布 / 正在检测是否收录...

原项目地址
45591786-16929580-b98e-11e8-95fe-83ee73a15d1b.png

https://github.com/maptalks/maptalks.routeplayer/

魔改后效果
www.alltoall.net_6a2ae055-f679-483b-bf5d-30d8e913ceae_fSvl4P7MCa.gif

因为项目需求这个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);
© 版权声明
THE END
喜欢就支持一下吧
点赞 0 分享 收藏

评论 (0)

取消