使用mapbox实现动态轨迹,设置对象图标,点击弹出框

可能对应梳理mapbox或者天地图的一些大佬而言,我写的毫无意义,但是作为菜鸟的我,还是觉得应该记录一下,毕竟太难了 /笑哭

主要需求就是根据后端接口返回的经纬度进行视频一样的播放效果,功能点截图在下方

这是一个播放的控件,都是自己写的,具体代码:

<div id="map-container">
        <div id="trackMapDiv" class="full-width full-height">
          <div class="track-control-box width-35" :class="{'track-control-box-hidden': !show}">
            <div class="tcb-title">
              <b>轨迹控制</b>
              <i class="fks-icon-caret-bottom tcb-icon" v-if="show" @click="show = !show"></i>
              <i class="fks-icon-caret-top tcb-icon" v-else @click="show = !show"></i>
            </div>
            <div v-show="show" class="width-35">
              <div class="tcb-line">
                <span>播放控制:</span>
                <fks-button type="primary" plain @click="pauseTrack" v-if="track.pause">播放</fks-button>
                <fks-button type="primary" plain @click="pauseTrack" v-else>暂停</fks-button>
                <fks-button type="primary" plain @click="restart">重播</fks-button>
                <fks-button type="primary" plain @click="stopTrack">停止</fks-button>
              </div>
              <div class="tcb-line">
                <span>播放速度:</span>
                <fks-input-number controls-position="right" :min="1" :max="10" class="tcb-inputNum" size="small" v-model="track.speed" @change="changeSpeed"></fks-input-number>
                <span class="tcb-left">显示轨迹:</span>
                <fks-switch v-model="track.show" @change="changeVisible">
                </fks-switch>
              </div>
              <div class="tcb-line">
                <span>当前时间:</span>
                <span>{{ track.end?animationDuration:secondToDate(new Date(routeTime * track.animationPhase).getTime())}}</span>
              </div>
              <div class="tcb-line">
                <fks-slider :min="0" :max="routeTime" v-model="track.step" class="tcb-slider" :format-tooltip="formatTooltip" @input="sliderChange"></fks-slider>
              </div>
              <div class="tcb-line">
                <span>轨迹时长:</span>
                <span>{{ animationDuration }}</span>
                <span class="tcb-mleft">剩余时长:</span>
                <span>{{ track.end?secondToDate(0):secondToDate(new Date(routeTime * (1-track.animationPhase)).getTime())}}</span>
              </div>
            </div>
          </div>
        </div>

// 这个是轨迹需要用到的字段,同时在计算当前时间、轨迹总时长、剩余时长会有秒数的差别,原因可能是因为计算时的值太小,所以做了个判断

track: {

        timer: 0, // 动画id

        show: true, //是否显示轨迹

        animationPhase: 0,  // 动画进度

        pause: true,        // 是否暂停

        end: false, //是否结束播放

        start: 0,        // 播放开始时间

        prevTime: 0,        // 上一个播放时间

        pauseTime: 0,        // 暂停的时间

        playTime: 0,               // 播放的时间

        speed: 4,        //播放速度

        step: 0

      },

接下来就是播放、暂停、重播、停止、播放速度、是否显示轨迹、拖动播放时间节点7个操作的方法

// 播放、暂停
pauseTrack(){
  this.track.pause = !this.track.pause
}
// 重播 直接调用播放方法(点击暂停后调用会出问题,加pause判断状态并设置)
restart() {
  this.stopTrack();
  if(this.track.pause) {
    this.pauseTrack();
  }
  this.start();
},
// 停止动画,设为初始值
stopTrack(){
  this.track.end = true
  window.cancelAnimationFrame(this.track.timer)
  MapObj.map.setPaintProperty('track-line-layer', 'line-gradient', [
    'step',
    ['line-progress'],
    '#027AFF',
    1,
    'rgba(255, 0, 0, 0)'
  ]);
},
// 设置播放速度
changeSpeed() {
  if (this.track.pause) this.start();
},
// 是否显示轨迹
changeVisible() {
  MapObj.map.setLayoutProperty('track-line-layer', 'visibility', this.track.show ? 'visible' : 'none');
},
//拖动播放器,设置播放时间
sliderChange(e) {
  // 不加判断时初始化报错
  if(!MapObj.map || !MapObj.map.getLayer('track-line-layer')) return;
    let step = e / 100;
    MapObj.map.setPaintProperty('track-line-layer', 'line-gradient', [
    'step',
    ['line-progress'],
    '#027AFF',
    step,
    'rgba(255, 0, 0, 0)'
  ]);
},

以上的都是播放的大概逻辑,具体的整改文件,我会在后面粘贴出来,以免看不明白

接下来就是根据经纬度设置标识,不同的对象不同的标识

1、首先要获取需要渲染的对象,必须要有经纬度的,同时设置一个可以判断标识的字段,用于不同对象展示不一样的标识判断条件(我用的meta)

// activities是数据源,如果是有大量数据需要渲染,可直接让后端存储成这种格式的数据返回,这样前
// 端就不用做处理了
let points = this.activities.map((t, index)=>{
    return {
      type: 'Feature',
      properties: {
      meta: !t.scanType? 'red':t.scanType === 1?'orange':'green', //用于判断显示那种图标
      name: t.capitalModel.capitalName,
      ...t
    },
    geometry: {
      coordinates: [t.capitalModel.smx, t.capitalModel.smy], //该对象的经纬度
      type: 'Point'    //数据类型
      }
    }
})

接下来就是初始化图标(这里可以不需要循环,但是写的时候不知道那里有问题,不循环不显示,所以就先这样了,有空的可以自己研究一下)

const imgs = [{
        id: 'start',
        url: require('@/assets/img/GIS/start.png')
      },{
        id: 'end',
        url: require('@/assets/img/GIS/end.png')
      },{
        id: 'green',
        url: require('@/assets/img/GIS/green.png')
      },{
        id: 'orange',
        url: require('@/assets/img/GIS/orange.png')
      },{
        id: 'red',
        url: require('@/assets/img/GIS/red.png')
      },{
        id: 'photograph',
        url: require('@/assets/img/GIS/photograph.png')
      }]
// 创建图标layer, track-source是addSource方法中定义的名字
imgs.forEach(img=>{
  if(!MapObj.map.hasImage(img.id)){
    MapObj.map.loadImage(img.url, (error, image) => {
      if (error) throw error;
         MapObj.map.addImage(img.id, image);
      if(!MapObj.map.getLayer(img.id + 'layer')){
          MapObj.map.addLayer({
          type: 'symbol',
          source: 'track-source',
          id: img.id + 'layer',
          paint: {    // 图标下字体的样式
          'icon-opacity': 1,
          'text-color': '#404040',
          'text-halo-color': '#ffffff',
          'text-halo-width': 1,
          'text-opacity': 1,
       },
       layout: {    // 图标和字体样式
         'text-anchor': 'center',
         'icon-allow-overlap': true,
         'icon-image': img.id,
         'icon-size': 0.5,
         // 'icon-color': '#027AFF',
         'text-field': ['get', 'name'],
         'text-offset': [0, 0], // 文本偏移
         'text-size': 12,
         'icon-offset': [0, img.id === 'start' || img.id === 'end'?0:-50]
        },
        filter: ['==', 'meta', img.id] // 这里就是用来判断显示那个图标的
        })
      }
    });
  }
})

第三点就是弹出框了,弹出框主要用到 就是map中的click方法,点击的时候会获取一些经纬度信息,通过MapObj.map.queryRenderedFeatures(bbox, {})拿到对应盒子的数据

使用MapObj.popup = new mapboxgl.Popup({closeButton: true}).setLngLat(hasFeature.geometry.coordinates).setHTML(content).addTo(MapObj.map);打开弹出框

MapObj.map.on('click', (mapEvent)=>{
  const META_TYPES = [ //这个可以不需要
      'green',
      'orange',
      'red'
  ]
  let buffer = 2          // 点击误差
  let bbox = [            // 点击的originalEvent拿坐标所在的点位信息,point有问题
      [mapEvent.originalEvent.layerX - buffer, mapEvent.originalEvent.layerY - buffer],
      [mapEvent.originalEvent.layerX + buffer, mapEvent.originalEvent.layerY + buffer]
  ];
  const features = MapObj.map.queryRenderedFeatures(bbox, {}).filter(feature => {
    return META_TYPES.indexOf(feature.properties.meta) !== -1;
  });
  let feature
  if(features.length){
    feature = features[0]
    // 以免出错判断必须是有的点
    let hasFeature = this.features.find(t=> t.properties.objectId === feature.properties.objectId);
    if(hasFeature){
      let { contentName, resultContent, capitalName, kindName } = { ...hasFeature.properties.contentRecordInfos[0], ...hasFeature.properties.capitalModel };
      this.currentProblem = {capitalName, bugIds: hasFeature.properties.bugIds, bugCount: hasFeature.properties.bugCount};
      let content = `
        <div class="mapboxgl-popup-mycontent">
          <span class="font-weight-bold">${capitalName}</span>
          <p><span>对象类型</span>&nbsp;&nbsp;<span class="text-primary">${kindName}</span></p>
          <p class="max-height-5 ofy-scroll">
            <fks-row>
              <fks-col :span="6">
                检查内容&nbsp;&nbsp;
              </fks-col>
              <fks-col :span="18" class="width-10 text-primary">
                ${contentName}
              </fks-col>
            </fks-row>
          </p>
          <p><span>结果描述</span>&nbsp;&nbsp;<span class="text-primary">${resultContent}</span></p>
          <p><span>问题数量</span>&nbsp;&nbsp;<span class="text-primary cursor-pointer" onclick="getDefectDetails()">${hasFeature.properties.bugCount || '-'}</span></p>
        </div>
      `
      MapObj.popup = new mapboxgl.Popup({closeButton: true}).setLngLat(hasFeature.geometry.coordinates).setHTML(content).addTo(MapObj.map);
    }else{
      MapObj.closePopup()
    }
  }
})

到此,以上说的三个功能就实现了,而外还有的一个点就是在map自带的弹出框里面点击接着弹出自定义的弹出框,这个功能也是有的。

下面是整个模块的代码,提供大家参考,有写不好的地方也请大家多多指教

<template>
<section class="bg-white">
  <fks-row class="full-width full-height" v-loading="loading">
    <fks-col :span="6" class="full-height">
      <fks-timeline class="p-4 pl-20">
        <fks-timeline-item
          v-for="(activity, index) in activities"
          :key="index"
          placement="top"
          icon="fks-icon-circle-check"
          type="primary"
          :timestamp="activity.scanTime || '2022-01-15 18:00:00'">
          <p class="font-weight-6">{{activity.capitalModel && activity.capitalModel.capitalName}}</p>
          <p>
            <span class="mr-1" :class="activity.scanType === 2?'text-success-green': activity.scanType?'text-third':'text-dangerous'">
              <img :src="require(activity.scanType === 2?'@/assets/img/GIS/photograph.png':activity.scanType?'@/assets/img/GIS/scan.png':'@/assets/img/GIS/manual.png')" alt="图片" width="20px" class="align-middle"/>
              {{activity.scanType === 2?'已拍照定位':activity.scanType?'已扫码定位':'手动打卡'}}
            </span>
            <span v-if="activity.scanType === 2" class="text-primary">查看照片</span>
          </p>
        </fks-timeline-item>
      </fks-timeline>
    </fks-col>
    <fks-col :span="18" class="full-height">
      <div id="map-container">
        <div id="trackMapDiv" class="full-width full-height">
          <div class="track-control-box width-35" :class="{'track-control-box-hidden': !show}">
            <div class="tcb-title">
              <b>轨迹控制</b>
              <i class="fks-icon-caret-bottom tcb-icon" v-if="show" @click="show = !show"></i>
              <i class="fks-icon-caret-top tcb-icon" v-else @click="show = !show"></i>
            </div>
            <div v-show="show" class="width-35">
              <div class="tcb-line">
                <span>播放控制:</span>
                <fks-button type="primary" plain @click="pauseTrack" v-if="track.pause">播放</fks-button>
                <fks-button type="primary" plain @click="pauseTrack" v-else>暂停</fks-button>
                <fks-button type="primary" plain @click="restart">重播</fks-button>
                <fks-button type="primary" plain @click="stopTrack">停止</fks-button>
              </div>
              <div class="tcb-line">
                <span>播放速度:</span>
                <fks-input-number controls-position="right" :min="1" :max="10" class="tcb-inputNum" size="small" v-model="track.speed" @change="changeSpeed"></fks-input-number>
                <span class="tcb-left">显示轨迹:</span>
                <fks-switch v-model="track.show" @change="changeVisible">
                </fks-switch>
              </div>
              <div class="tcb-line">
                <span>当前时间:</span>
                <span>{{ track.end?animationDuration:secondToDate(new Date(routeTime * track.animationPhase).getTime())}}</span>
              </div>
              <div class="tcb-line">
                <fks-slider :min="0" :max="routeTime" v-model="track.step" class="tcb-slider" :format-tooltip="formatTooltip" @input="sliderChange"></fks-slider>
              </div>
              <div class="tcb-line">
                <span>轨迹时长:</span>
                <span>{{ animationDuration }}</span>
                <span class="tcb-mleft">剩余时长:</span>
                <span>{{ track.end?secondToDate(0):secondToDate(new Date(routeTime * (1-track.animationPhase)).getTime())}}</span>
              </div>
            </div>
          </div>
        </div>
        <!-- 图标说明 -->
        <div class="identification width-35 position-absolute bg-white text-center">
          <fks-row>
            <fks-col :span="8"><img :src="imgs[2].url" width="25px" class="align-middle mr-1"/>拍照定位</fks-col>
            <fks-col :span="8"><img :src="imgs[3].url" width="25px" class="align-middle mr-1"/>NFC定位</fks-col>
            <fks-col :span="8"><img :src="imgs[4].url" width="25px" class="align-middle mr-1"/>手动打卡</fks-col>
          </fks-row>
        </div>
      </div>
    </fks-col>
  </fks-row>
  <!-- <div v-else class="full-height d-flex justify-content-center align-items-center text-gray font-weight-normal'" v-loading="loading">
    <p class="text-14">暂无数据</p>
  </div> -->
  <fks-dialog :visible.sync="showDialog" width="865px" :before-close="closeProblem">
    <div slot="title" class="d-flex full-width justify-content-between align-items-center">
      <p class="text-16 font-weight-bold">
        <i class="iconfont text-primary icon-jibenxinxi text-14"></i>
        {{ currentProblem.capitalName + '-' + `${currentProblem.checkResult === '0' ? '正常' : '异常'}` }}(
        <span class="text-danger">{{ currentProblem.bugCount }}</span>)
      </p>
      <span class="text-primary cursor-pointer text-14 mr-6" @click="changeShowType">
        <i class="iconfont icon-qiehuan1 text-12"></i> 切换{{ showType === 'line' ? '列表' : '时间线' }}查看
      </span>
    </div>
    <div style="max-height: calc(75vh - 123px);min-height: 100px" v-loading="defectLoading">
      <div v-for="(item, index) in problemList" :key="index">
        <defectDetail :defect-info="item.defectDetails || []"
                      :basic-info="item"
                      :showType="showType"
                      :class="index === 0 ? '' : 'mt-4'"/>
      </div>
    </div>
  </fks-dialog>
</section>
</template>
<script>
/* eslint-disable */
import { mapActions } from 'vuex';
import mapboxgl from 'mapbox-gl';
import defectDetail from '@/modules/Patrol/components/DefectDetail';
import MapboxLanguage  from '@mapbox/mapbox-gl-language'
// 地图
const imgs = [{
        id: 'start',
        url: require('@/assets/img/GIS/start.png')
      },{
        id: 'end',
        url: require('@/assets/img/GIS/end.png')
      },{
        id: 'green',
        url: require('@/assets/img/GIS/green.png')
      },{
        id: 'orange',
        url: require('@/assets/img/GIS/orange.png')
      },{
        id: 'red',
        url: require('@/assets/img/GIS/red.png')
      },{
        id: 'photograph',
        url: require('@/assets/img/GIS/photograph.png')
      }]
const MapObj = {
  map: null,
  popup: null,
  /**
   * 显示一个气泡
   * @param lngLat    气泡位置
   * @param content   气泡内容
   */
  showPopup(lngLat, content){
    if(!MapObj.popup){
        MapObj.popup = new mapboxgl.Popup().setLngLat(lngLat).setHTML(content).addTo(MapObj.map)
    }else{
      if(MapObj.popup.isOpen()){
          MapObj.popup.setLngLat(lngLat).setHTML(content)
      }else{
          MapObj.popup = new mapboxgl.Popup().setLngLat(lngLat).setHTML(content).addTo(MapObj.map)
      }
    }
  },
  closePopup(){
    if(MapObj.popup && MapObj.popup.isOpen()){
      MapObj.popup.remove()
      MapObj.popup = null
    }
  },
}

export default {
  components: { defectDetail },
  data() {
    return {
      imgs,
      loading: false,
      // 起点时间(毫秒)
      startTime: null,
      // 路线总时间(秒)
      routeTime: 0,
      curTimeStr: '',
      otherTimeStr: '',
      status: true,
      // 是否显示轨迹控件
      show: false,
      routeVisible: true,
      curTime: 0,
      practicalLine: [],
      positions: [],
      taskId: this.$route.params.id,
      track: {
        timer: 0,
        date: 0,
        show: true,
        animationPhase: 0,
        pause: true,
        end: false,
        start: 0,
        prevTime: 0,
        pauseTime: 0,
        playTime: 0,
        speed: 4,
        step: 0
      },
      // 拍照1、NFC2、手动打卡3
      activities: [],
      features: [],
      resDataInspectionRoute: [],
      problemList: [],
      showType: 'line',
      showDialog: false,
      currentProblem: {},
      problemList: [],
      defectLoading: false
    };
  },
  computed: {
    // 轨迹时长
    animationDuration() {
      let result = this.secondToDate(this.routeTime);
      return result;
    }
  },
  mounted() {
    this.getTaskObject();
    this.drawRouteByTaskId();
  },
  methods: {
    ...mapActions('patrol/defect', [
      'getProblemInfo'
    ]),
    ...mapActions('patrol/record', [
      'findCoordinateInfoBytaskId',
      'objectCoordinateInfo'
    ]),
    // 初始化地图
    initMap() {
      mapboxgl.accessToken = 'mapbox的注册的token';
      MapObj.map = new mapboxgl.Map({
        container: 'map-container',
        style: 'mapbox://styles/mapbox/streets-v11',
        center:  this.practicalLine[0] || [106.11966911319149, 29.63211347103112],
        zoom: 8.152743104851709
      });
      // 地图语言设置为中文
      var language = new MapboxLanguage({ defaultLanguage: "zh-Hans" });
      MapObj.map.addControl(language);
      // 设置导航
      MapObj.map.addControl(new mapboxgl.NavigationControl({showCompass: true,showZoom: true}),'top-left');
      // 是否全局显示
      MapObj.map.addControl(new mapboxgl.FullscreenControl(), "top-right"); 
      MapObj.map.on('load', () => {
        if(this.practicalLine.length === 0) return
        this.addAllLayer();
      });
    },
    // 根据taskId获取巡检路线,初始化调用
    drawRouteByTaskId() {
      this.loading = true;
      this.findCoordinateInfoBytaskId(this.taskId)
      .then((res) => {
        this.resDataInspectionRoute = res.result || [];
        this.practicalLine = this.resDataInspectionRoute.map((el) =>{
          return [el.longitude, el.latitude];
        });
        if(this.practicalLine.length === 0) {
          this.$message.warning('当前路线暂时没有轨迹');
          this.loading = false;
        } else {
          this.loading = false;
          this.calculatePos(this.resDataInspectionRoute);
        }
        this.initMap();
      }).catch(() => {
        this.loading = false;
      });
    },
    // 根据taskId获取巡检对象,获取对象和默认对象位置、打卡标识
    getTaskObject() {
      this.activities = [];
      this.objectCoordinateInfo(this.taskId).then((res) => {
        if(res.code === 8000000){
          this.activities = res.result || [];
          this.positions = this.activities.map((el) =>{
            return [el.capitalModel.smx, el.capitalModel.smy]
          });
          return
        }
      }).catch(() => {});
    },
    // 加载图层
    addAllLayer() {
      // 去除mapbox的logo
      document.getElementsByClassName('mapboxgl-ctrl-logo')[0].style.display = 'none';
      let points = this.activities.map((t, index)=>{
        return {
          type: 'Feature',
          properties: {
            meta: !t.scanType? 'red':t.scanType === 1?'orange':'green', //用于判断显示那种图标
            name: t.capitalModel.capitalName,
            ...t
          },
          geometry: {
            coordinates: [t.capitalModel.smx, t.capitalModel.smy], //该对象的经纬度
            type: 'Point'
          }
        }
      })
      // 拍照的点位信息
      let pointArr = this.activities.filter(el =>{
        return el.scanType === 2
      }).map(els =>{
        return {
          type: 'Feature',
          properties: {
            id: 1006,
            meta: 'photograph',
          },
          geometry: {
            coordinates:  [els.codeX, els.codeY],
            type: 'Point'
          }
        }
      });
      // 起点和终点
      let EndAndBeginning = [
        {
          type: 'Feature',
          properties: {
            meta: 'start',
          },
          geometry: {
            type: 'Point',
            coordinates: this.practicalLine[0]
          }
        },
        {
          type: 'Feature',
          properties: {
            meta: 'end',
          },
          geometry: {
            type: 'Point',
            coordinates: this.practicalLine[this.practicalLine.length - 1]
          },
        },
      ];
      let line = {
        type: 'Feature',
        properties: {
          name: 'name',
          meta: 'line'
        },
        geometry: {
          coordinates: this.practicalLine,
          type: 'LineString'
        }
      }
      // 组装feature
      let features = [line].concat(points, pointArr, EndAndBeginning)

      this.features = features
      // 创建默认路线source
      if(!MapObj.map.getSource('track-source')){
        MapObj.map.addSource('track-source',{
          type: 'geojson',
          lineMetrics: true,
          data: {
            'type': 'FeatureCollection',
            'features': features,
          }
        })
      }
      // 创建默认layer
      if(!MapObj.map.getLayer('track-line-layer')){
        MapObj.map.addLayer({
          type: 'line',
          source: 'track-source',
          id: 'track-line-layer',
          paint: {
              'line-color': '#027AFF',
              'line-width': 5,
              'line-gradient': [
                'step',
                ['line-progress'],
                '#027AFF',
                1,
                'rgba(255, 0, 0, 0)'
              ]
          },
          layout: {
              'line-cap': 'round',
              'line-join': 'round'
          },
          filter: ['==', '$type', 'LineString']
        })
      }
      // 默认的插入巡检起始点连接
      this.positions.unshift(this.practicalLine[0]);
      this.positions.push(this.practicalLine[this.practicalLine.length - 1]);
      MapObj.map.addLayer({
        "id": "route",
        "type": "line",
        "source": {
          "type": "geojson",
          "data": {
            "type": "Feature",
            "properties": {},
            "geometry": {
              "type": "LineString",
              "coordinates": this.positions
            }
          }
        },
        "layout": {
          "line-join": "round",
          "line-cap": "round"
        },
        "paint": {
          "line-color": "#ccc",
          "line-width": 5
        }
      });
      // 创建图标layer
      imgs.forEach(img=>{
        if(!MapObj.map.hasImage(img.id)){
          MapObj.map.loadImage(img.url, (error, image) => {
              if (error) throw error;
              MapObj.map.addImage(img.id, image);
              if(!MapObj.map.getLayer(img.id + 'layer')){
                MapObj.map.addLayer({
                    type: 'symbol',
                    source: 'track-source',
                    id: img.id + 'layer',
                    paint: {
                        'icon-opacity': 1,
                        'text-color': '#404040',
                        'text-halo-color': '#ffffff',
                        'text-halo-width': 1,
                        'text-opacity': 1,
                    },
                    layout: {
                        'text-anchor': 'center',
                        'icon-allow-overlap': true,
                        'icon-image': img.id,
                        'icon-size': 0.5,
                        // 'icon-color': '#027AFF',
                        'text-field': ['get', 'name'],
                        'text-offset': [0, 0], // 文本偏移
                        'text-size': 12,
                        'icon-offset': [0, img.id === 'start' || img.id === 'end'?0:-50]
                    },
                    filter: ['==', 'meta', img.id]
                })
              }
          });
        }
      })
      // 开始时间固有的 markers
      new mapboxgl.Popup({anchor: 'top',offset: [0, 20], className: 'info', closeButton:false, closeOnClick:false })
      .setLngLat(this.positions[0])
      .setHTML(`<div class="height-2"><i class="fks-icon-time text-primary text-14"></i> 开始时间:${this.resDataInspectionRoute[0].createTime}</div>`)
      .addTo(MapObj.map);
      // 结束时间
      new mapboxgl.Popup({anchor: 'top',offset: [0, 20], className: 'info', closeButton:false, closeOnClick:false })
      .setLngLat(this.positions[this.positions.length - 1])
      .setHTML(`<div class="height-2"><i class="fks-icon-warning-outline text-primary text-14"></i> 结束时间:${this.resDataInspectionRoute[this.resDataInspectionRoute.length - 1].createTime}</div>`)
      .addTo(MapObj.map);
      MapObj.map.on('click', (mapEvent)=>{
        const META_TYPES = [
            'green',
            'orange',
            'red'
        ]
        let buffer = 2          // 点击误差
        let bbox = [            // 点击的originalEvent拿坐标所在的点位信息,point有问题
            [mapEvent.originalEvent.layerX - buffer, mapEvent.originalEvent.layerY - buffer],
            [mapEvent.originalEvent.layerX + buffer, mapEvent.originalEvent.layerY + buffer]
        ];
        const features = MapObj.map.queryRenderedFeatures(bbox, {}).filter(feature => {
          return META_TYPES.indexOf(feature.properties.meta) !== -1;
        });
        let feature
        if(features.length){
          feature = features[0]
          // 以免出错判断必须是有的点
          let hasFeature = this.features.find(t=> t.properties.objectId === feature.properties.objectId);
          if(hasFeature){
            let { contentName, resultContent, capitalName, kindName } = { ...hasFeature.properties.contentRecordInfos[0], ...hasFeature.properties.capitalModel };
            this.currentProblem = {capitalName, bugIds: hasFeature.properties.bugIds, bugCount: hasFeature.properties.bugCount};
            let content = `
              <div class="mapboxgl-popup-mycontent">
                <span class="font-weight-bold">${capitalName}</span>
                <p><span>对象类型</span>&nbsp;&nbsp;<span class="text-primary">${kindName}</span></p>
                <p class="max-height-5 ofy-scroll">
                  <fks-row>
                    <fks-col :span="6">
                      检查内容&nbsp;&nbsp;
                    </fks-col>
                    <fks-col :span="18" class="width-10 text-primary">
                      ${contentName}
                    </fks-col>
                  </fks-row>
                </p>
                <p><span>结果描述</span>&nbsp;&nbsp;<span class="text-primary">${resultContent}</span></p>
                <p><span>问题数量</span>&nbsp;&nbsp;<span class="text-primary cursor-pointer" onclick="getDefectDetails()">${hasFeature.properties.bugCount || '-'}</span></p>
              </div>
            `
            MapObj.popup = new mapboxgl.Popup({closeButton: true}).setLngLat(hasFeature.geometry.coordinates).setHTML(content).addTo(MapObj.map);
          }else{
            MapObj.closePopup()
          }
        }
      })
      // 显示缺陷详情
      let that = this;
      window.getDefectDetails = () => {
        if(!that.currentProblem.bugCount) return
        that.showDialog = true;
        that.defectLoading = true;
        that.getProblemInfo({bugId: that.currentProblem.bugIds}).then(res => {
          that.problemList = res.result;
          that.problemList.forEach((el) => {
            el.defectDetails.forEach((els) => {
              els.bugName = el.bugName;
              els.bugCode = el.bugCode;
              els.bugDescription = el.bugDescription;
              els.bringDefect = el.bringDefect;
              els.bugTypeName = el.bugTypeName;
            });
          });
        }).finally(() => {
          that.defectLoading = false;
        });
      }
    },
    closeProblem() {
      this.showDialog = false;
      this.problemList = [];
      this.currentProblem = {};
    },
    changeShowType() {
      this.showType = this.showType === 'line' ? 'table' : 'line';
    },
    // 计算每个时间点对应的坐标 routeTime:轨迹时长
    calculatePos(positions) {
      this.startTime = new Date(positions[0].createTime).getTime();
      this.routeTime = new Date(positions[positions.length - 1].createTime).getTime() / 1000 - new Date(positions[0].createTime).getTime() / 1000;
    },
    // 计算时间
    secondToDate(result) {
      var h = Math.floor(result / 3600) < 10 ? '0' + Math.floor(result / 3600) : Math.floor(result / 3600);
      var m = Math.floor((result / 60) % 60) < 10 ? '0' + Math.floor((result / 60) % 60) : Math.floor((result / 60) % 60);
      var s = Math.floor(result % 60) < 10 ? '0' + Math.floor(result % 60) : Math.floor(result % 60);
      return h + ':' + m + ':' + s;
    },
     // 停止动画,设为初始值
    stopTrack(){
      this.track.end = true
      window.cancelAnimationFrame(this.track.timer)
      MapObj.map.setPaintProperty('track-line-layer', 'line-gradient', [
          'step',
          ['line-progress'],
          '#027AFF',
          1,
          'rgba(255, 0, 0, 0)'
      ]);
    },
    // 重播 直接调用播放方法(点击暂停后调用会出问题,加pause判断状态并设置)
    restart() {
      this.stopTrack();
      if(this.track.pause) {
        this.pauseTrack();
      }
      this.start();
    },
    // 播放
    start() {
      let _this = this
      if(!this.track.end) return
      this.track.start = 0
      this.track.animationPhase = 0
      this.track.pauseTime = 0;
      this.track.prevTime = 0;
      function frame(time) {
        if (_this.track.start === 0)
            _this.track.start = time;
        if(_this.track.pause){
            _this.track.pauseTime += (time - _this.track.prevTime)
        }else{
            _this.track.animationPhase = _this.track.speed * (time - _this.track.start - _this.track.pauseTime) / _this.routeTime;
        }
        _this.track.step = _this.track.animationPhase * 100
        _this.track.prevTime = time
        // 播放时长=总时长*当前播放进度
        // 剩余时长=总时长-播放时长
        // 剩余距离同理
        _this.track.playTime = _this.track.animationPhase * _this.routeTime

        if (_this.track.animationPhase > 1) {
            _this.stopTrack()
            return
        }
        MapObj.map.setPaintProperty('track-line-layer', 'line-gradient', [
            'step',
            ['line-progress'],
            '#027AFF',
            _this.track.animationPhase,
            'rgba(255, 0, 0, 0)'
        ]);
        _this.track.timer = window.requestAnimationFrame(frame);
      }
      this.track.timer = window.requestAnimationFrame(frame);
      this.track.end = false
    },
    changeSpeed() {
      if (this.track.pause) this.start();
    },
    // 是否显示轨迹
    changeVisible() {
      MapObj.map.setLayoutProperty('track-line-layer', 'visibility', this.track.show ? 'visible' : 'none');
    },
    sliderChange(e) {
      // 不加判断时初始化报错
      if(!MapObj.map || !MapObj.map.getLayer('track-line-layer')) return;
      let step = e / 100;
      MapObj.map.setPaintProperty('track-line-layer', 'line-gradient', [
        'step',
        ['line-progress'],
        '#027AFF',
        step,
        'rgba(255, 0, 0, 0)'
      ]);
    },
    formatTooltip(val) {
      let result = this.getDateString(this.startTime + val * 1000);
      return result;
    },
    getDateString(stamp) {
      let d = new Date(parseInt(stamp));
      let month =
          d.getMonth() + 1 < 10 ? 0 + '' + (d.getMonth() + 1) : d.getMonth() + 1;
      let day = d.getDate() < 10 ? 0 + '' + d.getDate() : d.getDate();
      let hour = d.getHours() < 10 ? 0 + '' + d.getHours() : d.getHours();
      let minute =
          d.getMinutes() < 10 ? 0 + '' + d.getMinutes() : d.getMinutes();
      let second =
          d.getSeconds() < 10 ? 0 + '' + d.getSeconds() : d.getSeconds();
      let dateString =
          d.getFullYear() + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second;
      return dateString;
    },
    // 播放、暂停
    pauseTrack(){
      this.track.pause = !this.track.pause
    }
  }
};
</script>
<style lang="scss" scoped>
/deep/.fks-timeline {
  margin-left: 80px;
  .fks-timeline-item {
    display: flex;
    align-items: center;
  }
  .fks-timeline-item__tail{
    left: 10px !important;
    height: 70% !important;
    top: 60px !important;
  }
  .fks-timeline-item__node--normal {
    width: 25px !important;
    height: 25px !important;
  }
  .fks-timeline-item__icon {
    font-size: 26px !important;
  }
  .fks-timeline-item__timestamp.is-top {
    position: absolute;
    left: -94px !important;
    width: 90px !important;
    text-align: center;
    margin-top: 30px;
  }
  .fks-timeline-item__content{
    width: 250px;
    background-color: #E7F4FF;
    padding: 1px 10px;
    border-radius: 5px;
    margin-left: 10px;
  }
}
#map-container {
  height: 100%;
  width: 100%;
  position: relative;
  overflow: hidden;
  -webkit-touch-callout: none; /*系统默认菜单被禁用*/
  -webkit-user-select: none; /*webkit浏览器*/
  -khtml-user-select: none; /*早期浏览器*/
  -moz-user-select: none; /*火狐*/
  -ms-user-select: none; /*IE10*/
  user-select: none;
  .track-control-box {
    width: 380px;
    height: 270px;
    position: absolute;
    bottom: 20px;
    right: 20px;
    z-index: 1;
    background: white;
    border-radius: 5px;
    .tcb-title {
      height: 40px;
      line-height: 40px;
      font-size: 15px;
      margin-left: 15px;
      .tcb-icon {
        float: right;
        font-size: 19px;
        margin-top: 8px;
        margin-right: 15px;
        color: #2ba3fd;
        cursor: pointer;
      }
    }
    .tcb-line {
      margin-left: 25px;
      margin-bottom: 10px;
      line-height: 36px;
      height: 36px;
      &/deep/ .fks-button--primary.is-plain:active{
        color: #2BA3FD;
        background: #eaf6ff;
        border-color: #aadafe;
      }
      &/deep/ .fks-button--primary.is-plain:focus{
        color: #2BA3FD;
        background: #eaf6ff;
        border-color: #aadafe;
      }
      &/deep/ .fks-switch__label--left {
        display: none;
      }
      &/deep/ .fks-switch__label--left.is-active {
        position: absolute;
        color: white !important;
        z-index: 6;
        left: 23px;
        display: block;
      }
      &/deep/ .fks-switch__label--right {
        display: none;
      }
      &/deep/ .fks-switch__label--right.is-active {
        position: absolute;
        color: white !important;
        left: -2px;
        display: block;
      }
      .tcb-left {
        margin-left: 10px;
      }
      .tcb-mleft {
        margin-left: 28px;
      }
      .tcb-slider {
        width: 310px;
      }
    }
    .tcb-inputNum {
      width: 70px;
      &/deep/ input {
        padding-left: 10px !important;
        padding-right: 40px !important;
      }
    }
  }
  .track-control-box-hidden {
    height: 40px;
    width: 130px;
  }
  .identification {
    left: 10px;
    bottom: 20px;
    z-index: 9;
    border-radius: 5px;
    height: 40px;
    line-height: 40px;
    // text-align: center;
  }
  .mapboxgl-popup-mycontent{
    background-color: #FFFFFF;
    z-index: 99999;
    height: 100px;
    width: 400px;
    padding: 20px;
  }
}
</style>

官网地址:http://www.mapbox.cn/mapbox-gl-js/api/

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现这个功能,可以按照以下步骤进行操作: 1. 在微信开发者工具中创建一个新的小程序项目,并且勾选上“使用微信云开发”选项。 2. 在小程序页面中引入地图组件。可以使用微信提供的组件`<map>`来实现。 3. 在小程序页面中添加标点。可以使用微信提供的`<map>`组件的`markers`属性来添加标点。 4. 为每一个标点添加点击事件。可以使用微信提供的`<map>`组件的`bindmarkertap`属性来添加点击事件,并且指定一个函数来处理点击事件。 5. 在点击事件的处理函数中,使用微信提供的`wx.navigateTo`函数来跳转到指定页面。 下面是一个示例代码,可以帮助你实现这个功能: ```html <!-- 在小程序页面中添加地图组件 --> <map id="myMap" latitude="{{latitude}}" longitude="{{longitude}}" markers="{{markers}}" bindmarkertap="onMarkerTap"></map> ``` ```javascript // 在小程序页面中添加以下代码 Page({ data: { latitude: 39.912345, // 初始化地图的中心点纬度 longitude: 116.123456, // 初始化地图的中心点经度 markers: [{ id: 1, latitude: 39.912345, longitude: 116.123456, title: '这是一个标点', iconPath: '/images/marker.png' }] }, onMarkerTap: function(event) { // 获取点击的标点的信息 var markerId = event.markerId; var marker = this.data.markers[markerId]; // 跳转到指定页面 wx.navigateTo({ url: '/pages/detail/detail?latitude=' + marker.latitude + '&longitude=' + marker.longitude + '&title=' + marker.title }) } }) ``` 在上面的代码中,我们首先在小程序页面中添加了一个地图组件,并且设置了地图的中心点和一个标点。然后为`<map>`组件添加了`bindmarkertap`属性,并且指定了一个`onMarkerTap`函数来处理点击事件。当用户点击了某一个标点时,`onMarkerTap`函数会被触发,然后获取点击的标点的信息,并且使用`wx.navigateTo`函数跳转到一个指定的页面。在这个页面中,我们可以根据传入的经纬度和标题等信息来显示地图的具体位置和相关信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值