仿高德地图实现输入起点和终点规划路径并可切换

概述

本文结合高德API和MapboxGL,仿照手机版高德地图实现用户输入起点和终点位置并模糊搜索选择具体位置,根据选择的起始点位置规划路径,并实现多条路径的切换展示。

实现效果

仿高德地图实现输入起点和终点规划路径并可切换

仿高德地图实现输入起点和终点规划路径并可切换

代码实现

1. 实现思路

  1. 页面初始化的时候获取用户当前的位置,并设置为地图的初始化位置;
  2. 调用regeo接口设置当前城市;
  3. 通过inputtips实现起始点的模糊查询;
  4. 调用v5/direction/driving接口实现路径规划,根据文档:
  • v5版本的接口可返回多条路径,v3貌似只返回一条
  • 需传入参数show_fields: polyline,cost
  • polyline用于展示路径
  • cost用于展示耗时
  1. 通过三个图层实现规划路径的展示
  • 最底层的是路径描边,颜色设置最深
  • 中间层是未选择的路径
  • 最上面的是选择的路径

2.实现代码

<template>
  <div class="container">
    <div class="query">
      <el-form :model="form" label-width="auto" class="query-form">
        <el-form-item label="起点" class="form-start-position">
          <el-autocomplete
            v-model="form.startPosition"
            :fetch-suggestions="querySearchStart"
            clearable
            placeholder="请输入起点位置"
            @select="handleSelectStart"
          >
            <template #default="{ item }">
              <div class="autocomplete-value">{{ item.value }}</div>
              <div class="autocomplete-address">{{ item.address }}</div>
            </template>
          </el-autocomplete>
        </el-form-item>
        <el-form-item label="终点" style="margin-bottom: 0" class="form-end-position">
          <el-autocomplete
            v-model="form.endPosition"
            :fetch-suggestions="querySearchEnd"
            clearable
            placeholder="请输入终点位置"
            @select="handleSelectEnd"
          >
            <template #default="{ item }">
              <div class="autocomplete-value">{{ item.value }}</div>
              <div class="autocomplete-address">{{ item.address }}</div>
            </template>
          </el-autocomplete>
        </el-form-item>
      </el-form>
      <div class="query-button">
        <el-button :disabled="!form.startPosition || !form.endPosition" class="query-button-inner" @click="queryRoute">
          查询
        </el-button>
        <!-- <el-button class="query-button-inner" @click="queryRoute">查询</el-button> -->
      </div>
    </div>
    <div class="map" id="map">
      <div class="routes" v-show="features.length > 0">
        <div
          v-for="(feature, index) of features"
          :key="index"
          :class="index === selectedIndex ?  route active  :  route "
          @click="updateIndex(index)"
        >
          <div class="cost">{{ Math.ceil(feature.properties.cost / 60) }}分钟</div>
          <div>{{ feature.properties.distance }}公里</div>
          <div>{{ index === 0 ?  大众常选  :  方案  + index }}</div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>


import { ElMessage } from  element-plus 
import * as turf from  @turf/turf 

let map = null
const layerId =  my-route 

function request(url, params) {
  const AK =  你申请的AK值 
  const BASE_URL =  https://restapi.amap.com/ 
  let fullUrl = url.indexOf( json ) === -1 ? `${BASE_URL}${url}?key=${AK}` : url
  for (const key in params) {
    fullUrl += `&${key}=${params[key]}`
  }
  return new Promise((resolve, reject) => {
    fetch(fullUrl)
      .then(res => res.json())
      .then(res => {
        if (res.status ===  1 ) resolve(res)
        else {
          ElMessage.error( 接口请求失败 )
          reject(res)
        }
      })
  })
}

export default {
  data() {
    return {
      center: [113.94150905808976, 22.523881824251347],
      form: {
        startPosition:   ,
        endPosition:   ,
        startCoord: [],
        endCoord: [],
      },
      cityInfo: {},
      features: [],
      selectedIndex: 0,
      path: null,
    }
  },
  mounted() {
    const that = this
    function successFunc(position) {
      const { longitude, latitude } = position.coords
      that.center = [longitude, latitude]
      that.initMap()
    }
    function errFunc() {
      that.initMap()
    }
    if (navigator.geolocation) {
      try {
        errFunc()
        navigator.geolocation.getCurrentPosition(successFunc, errFunc)
      } catch (e) {
        errFunc()
      }
    } else {
      errFunc()
    }
  },
  methods: {
    initMap() {
      map = new SFMap.Map({
        container:  map ,
        center: this.center,
        zoom: 17.1,
      })
      map.on( load , e => {
        map.addSource(layerId, {
          type:  geojson ,
          data: turf.featureCollection([]),
        })
        map.addSource(layerId +  -line , {
          type:  geojson ,
          data: turf.featureCollection([]),
        })
        map.addSource(layerId +  -border , {
          type:  geojson ,
          data: turf.featureCollection([]),
        })
        map.addLayer({
          id: layerId +  -border ,
          type:  line ,
          source: layerId +  -border ,
          layout: {
             line-cap :  round ,
             line-join :  round ,
          },
          paint: {
             line-color :  #056113 ,
             line-width : 13,
          },
        })
        map.addLayer({
          id: layerId +  -line ,
          type:  line ,
          source: layerId +  -line ,
          layout: {
             line-cap :  round ,
             line-join :  round ,
          },
          paint: {
             line-color :  #80d18c ,
             line-width : 10,
          },
        })
        map.addLayer({
          id: layerId,
          type:  line ,
          source: layerId,
          layout: {
             line-cap :  round ,
             line-join :  round ,
          },
          paint: {
             line-color :  #16ab2d ,
             line-width : 10,
          },
        })

        map.on( mousemove , layerId +  -line , e => {
          map.getCanvas().style.cursor =  pointer 
        })
        map.on( mouseout , layerId +  -line , e => {
          map.getCanvas().style.cursor =   
        })
        map.on( click , layerId +  -line , e => {
          const feature = e.features[0]
          this.selectedIndex = feature.properties.index
          this.setShowRoute()
        })
        const arrow =  data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAnElEQVQ4T63TsQ0CMQyF4f/NgMQQ0CBR0FIx190cFIiWhhFoKdgEiRUeSoF0gO8cjkub+Evs2OLPpc9422dgBRwlNZn/BtjeApdOUJsh0QuuwKYWiYAFcAKWHaSR1EbpfAHlkO0ICdMJgV+QXmAAmUu6v9IZA8wkPVKgtg7TF7H25t4UbN+A9ahGmqqVD8AO2GdzUF45+I3ZJJb9JxbwRhEhB66xAAAAAElFTkSuQmCC 
        map.loadImage(arrow, function (error, image) {
          if (error) throw error
          map.addImage(layerId +  -arrow , image)
          map.addLayer({
            id: layerId +  -line-arrow ,
            source: layerId +  -line ,
            type:  symbol ,
            layout: {
               symbol-placement :  line ,
               symbol-spacing : 50,
               icon-image : layerId +  -arrow ,
               icon-size : 0.6,
               icon-allow-overlap : true,
            },
          })
          map.addLayer({
            id: layerId +  -arrow ,
            source: layerId,
            type:  symbol ,
            layout: {
               symbol-placement :  line ,
               symbol-spacing : 50,
               icon-image : layerId +  -arrow ,
               icon-size : 0.6,
               icon-allow-overlap : true,
            },
          })
        })
        request( v3/geocode/regeo , {
          location: this.center.join( , ),
        }).then(res => {
          this.cityInfo = res.regeocode.addressComponent
        })
      })
    },
    querySearchStart(str, cb) {
      if (str) {
        request( v3/assistant/inputtips , {
          keywords: str,
          city: this.cityInfo?.city,
        }).then(res => {
          cb(
            res.tips.map(t => {
              t.value = t.name
              return t
            }),
          )
        })
      } else {
        cb([])
      }
    },
    querySearchEnd(str, cb) {
      if (str) {
        request( v3/assistant/inputtips , {
          keywords: str,
          city: this.cityInfo?.city,
        }).then(res => {
          cb(
            res.tips.map(t => {
              t.value = t.name
              return t
            }),
          )
        })
      } else {
        cb([])
      }
    },
    handleSelectStart(item) {
      this.form.startCoord = item.location.split( , ).map(Number)
    },
    handleSelectEnd(item) {
      this.form.endCoord = item.location.split( , ).map(Number)
    },
    queryRoute() {
      const { startCoord, endCoord } = this.form
      // request( ./data.json , {
      // }).then(res => {
      request( v5/direction/driving , {
        origin: startCoord.join( , ),
        destination: endCoord.join( , ),
        show_fields:  polyline,cost ,
      }).then(res => {
        const paths = res.route.paths
        let features = paths.map((path, index) => {
          let coordinates = []
          path.steps.forEach(step => {
            const polyline = step.polyline.split( ; ).map(c => c.split( , ).map(Number))
            coordinates = [...coordinates, ...polyline]
          })
          return {
            type:  Feature ,
            properties: {
              index,
              distance: path.distance,
              cost: path?.duration || path?.cost.duration,
            },
            geometry: {
              type:  LineString ,
              coordinates: coordinates,
            },
          }
        })
        this.features = features
        map.getSource(layerId +  -border ).setData(turf.featureCollection(features))
        this.setShowRoute()
      })
    },
    updateIndex(index) {
      if (this.selectedIndex === index) return
      this.selectedIndex = index
      this.setShowRoute()
    },
    setShowRoute() {
      const feature = this.features[this.selectedIndex]
      this.path = feature.properties
      map.getSource(layerId).setData(feature)
      const features = this.features.filter((f, i) => i !== this.selectedIndex)
      map.getSource(layerId +  -line ).setData(turf.featureCollection(features))
      map.fitBounds(turf.bbox(feature), { padding: { top: 50, bottom: 220, left: 50, right: 50 } })
    },
  },
}
</script>

<style scoped lang="scss">
.container {
  width: 100%;
  height: 100vh;
  overflow: hidden;
}
.query {
  padding: 0.8rem;
  overflow: hidden;
  background: #ccc;
  .query-form {
    width: calc(100% - 4rem);
    float: left;
  }
  .query-button {
    width: 4rem;
    height: 4.7rem;
    float: right;
    display: flex;
    justify-content: flex-end;
    align-items: center;
    .query-button-inner {
      width: 3.8rem;
      height: 100%;
    }
  }
}
.map {
  height: calc(100% - 6.4rem);
}

.autocomplete-value,
.autocomplete-address {
  white-space: nowrap;
  width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
.autocomplete-address {
  font-size: 0.8rem;
  color: #999;
  margin-top: -0.8rem;
  border-bottom: 1px solid #efefef;
}
.routes {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  z-index: 99;
  padding: 1rem;
  box-sizing: border-box;
  background: rgba(255, 255, 255, 0.9);
  text-align: left;
  display: flex;
  flex-direction: row;
  font-size: 0.95rem;
  .route {
    flex-grow: 1;
    border-radius: 0.5rem;
    line-height: 1.8;
    padding: 0.5rem 1rem;
    cursor: pointer;
    border-right: 1px solid #efefef;
    &:last-child {
      margin-right: 0;
      border-right: none;
    }
    .cost {
      font-size: 1.35rem;
      font-weight: bold;
    }
    &.active {
      color: #409eff;
      background: #e3effa;
    }
  }
}
:deep .el-form-item {
  margin-bottom: 0.5rem;
}
</style>
<style lang="scss">
.my-custom-popover-class {
  background-color: rgba(1, 122, 242, 0.8);
  color: #fff;

  .driver-popover-arrow-side-top {
    border-top-color: rgba(1, 122, 242, 0.8);
  }
  .driver-popover-arrow-side-bottom {
    border-bottom-color: rgba(1, 122, 242, 0.8);
  }
  .driver-popover-arrow-side-left {
    border-left-color: rgba(1, 122, 242, 0.8);
  }
  .driver-popover-arrow-side-right {
    border-right-color: rgba(1, 122, 242, 0.8);
  }

  .driver-popover-progress-text,
  .driver-popover-close-btn {
    color: #fff;
  }

  .driver-popover-prev-btn,
  .driver-popover-next-btn {
    border: 1px solid #fff;
    color: rgba(1, 122, 242, 1);
  }
}
</style>

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
逗比小学弟的头像 - 宋马
评论 抢沙发

请登录后发表评论

    暂无评论内容