分享

零基础地图开发教程系列|如何在Vue和React中利用地图组件库实现路线规划与模拟小车移动

 ZhouAndrew 2024-10-24

今年7月份,腾讯位置服务开源了tlbs-map地图组件库,提供了丰富多样的地图组件,同时该组件库支持主流前端框架Vue和React,灵活的定制化能力满足了开发者广泛的开发需求。

本文将使用地图组件库,实现路线规划和模拟小车按规划路线移动的功能,进一步帮助开发者了解及上手使用。

图片

1

图片
功能效果

在地图上绘制起终点和小车,对起终点进行路线规划给出多个路线方案,小车能够按照选择的路线进行移动、暂停、恢复移动,效果图如下:

图片
图片

2

图片
实现思路

地图组件库提供了基础地图、点标记、点聚合、折线、多边形、热力图等常用地图组件。使用基础地图组件可实现地图底图展示效果,地图上起终点、小车则可以使用点标记组件实现,路线绘制可以使用折线组件

此外,腾讯位置服务提供了驾车路线规划服务,开发者可以调用此服务来获取起点到终点的路线数据。点标记组件还提供了点标记实例,实例支持调用moveAlong方法移动点标记、pauseMove方法暂停点标记、resumeMove方法重新开始移动点标记,点击按钮可以调用对应的实例方法完成小车的出发、暂停、恢复移动操作。

接下来,我们将按照上述思路,分别使用Vue地图组件库、React地图组件库实现路线规划和模拟小车按规划路线移动的功能。

图片

3

图片
主要代码

1

安装组件库

在Vue项目中执行命令安装Vue版本地图组件库


npm install --save tlbs-map-vue

在React项目中执行命令安装React版本地图组件库


npm install --save tlbs-map-react

2

注册组件库

在Vue项目中全局注册组件库

main.js文件代码

import { createApp } from 'vue';

import App from './App.vue';

import TlbsMap from 'tlbs-map-vue';

createApp(App)

 .use(TlbsMap)

 .mount('#app');

3

地图初始化

使用tlbs-map地图组件初始化地图,传入center属性设置地图中心点,地图中心点坐标是起点经纬度坐标,zoom缩放级别设置为15,apiKey参数传入腾讯位置服务开发key(申请key参考

Vue代码

<template>

  <div class='map-container'>

    <tlbs-map

      apiKey=''

      :center='startPoint'

      :zoom='15'

      style='height: 100vh;'

    ></tlbs-map>

   </div>

</template>

<script setup>

//起点坐标

const startPoint = {

    lat: 39.98481500648338,

    lng: 116.30571126937866

 };

<script>

React代码

import { TMap as TlbsMap } from 'tlbs-map-react';

//起点坐标

const startPoint = {

 lat: 39.98481500648338,

 lng: 116.30571126937866

};

export default () => {

 return (

   <>

     <TlbsMap

       apiKey=''

       options={{ 

         center: startPoint,

         zoom: 15,

       }}

       style={{ width: '100%', height: '100%' }}

     >

     </TlbsMap>

    </>

 );

};

4

绘制起终点、小车

在基础地图组件子级插入点标记组件,点标记组件有两个参数geometries点数据、styles点样式,传入这两个参数即可完成起终点和小车绘制。

VUE代码

<template>

 <div class='map-container'>

   <tlbs-map

     api-key=''

     :center='startPoint'

     :zoom='15'

     style='height: 100vh;'

   >

     <tlbs-multi-marker

       :geometries='markerGeometries'

       :styles='markerStyles'

     />

   </tlbs-map>

  </div>

</template>

<script setup>

//起点坐标

const startPoint = {

   lat: 39.98481500648338,

   lng: 116.30571126937866

};

//终点坐标

const endPoint = {

   lat: 39.978813710266024,

  lng: 116.31699800491333

};

// 点标记样式

const markerStyles = {

 //小车标记点样式

 car: {

   width: 40,

   height: 40,

   anchor: {

     x: 20,

     y: 20,

   },

   faceTo: 'map',

   rotate: 180,

   src: 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/car.png',  },

//起点标记点样式

 start: {

   width: 25,

   height: 37,

   anchor: { x: 13, y: 32 },

   src: 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/start.png',

 },

 //终点标记点样式

 end: {

   width: 25,

   height: 37,

   anchor: { x: 13, y: 32 },

   src: 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/end.png',

 },

};

// 点标记数据

const markerGeometries = [

 {

   id: 'car',

   styleId: 'car',

   position: startPoint,

 },

 {

   id: 'start',

   styleId: 'start',

   position: startPoint,

 },

 {

   id: 'end',

   styleId: 'end',

   position: endPoint,

 },

];

<script>

React代码

import { TMap as TlbsMap , MultiMarker  } from 'tlbs-map-react';

//起点坐标

const startPoint = {

   lat: 39.98481500648338,

   lng: 116.30571126937866

};

//终点坐标

const endPoint = {

   lat: 39.978813710266024,

   lng: 116.31699800491333

};

// 点标记样式

const markerStyles = {

 //小车标记点样式

 car: {

   width: 40,

   height: 40,

   anchor: {

     x: 20,

     y: 20,

   },

   faceTo: 'map',

   rotate: 180,

   src: 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/car.png',

 },

 //起点标记点样式

 start: {

   width: 25,

   height: 37,

   anchor: { x: 13, y: 32 },

   src: 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/start.png',

 },

 //终点标记点样式

 end: {

   width: 25,

   height: 37,

   anchor: { x: 13, y: 32 },

   src: 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/end.png',

 },

};

// 点标记数据

const markerGeometries = [

 {

   id: 'car',

   styleId: 'car',

   position: startPoint,

 },

 {

   id: 'start',

   styleId: 'start',

   position: startPoint,

 },

 {

   id: 'end',

   styleId: 'end',

   position: endPoint,

 },

];

export default () => {

 return (

   <>

     <TlbsMap

       apiKey=''

       options={{

         center: startPoint,

         zoom: 15,

       }}

       style={{ width: '100%', height: '100%' }}

     >

       <MultiMarker

         styles={markerStyles}

         geometries={markerGeometries}

       />

     </TlbsMap>

   </>

 );

};

5

获取路线规划数据

定义routePlan方法调用驾车路线规划服务,驾车路线服务有浏览器跨域问题需要使用axios以jsonp方式发起接口请求,服务会返回起终点路线规划数据,我们可以得到驾车路线的坐标点串数据、路线距离、路线估算时间,注意接口地址中需要填入腾讯位置服务开发key并开通驾车路线服务。

Vue代码

<script setup>

import { ref } from 'vue';

import axios from 'axios';

import jsonpAdapter from 'axios-jsonp';

...

const routes = shallowRef([]);

const routePlan = () => {

  axios({

    // 接口地址中需要填入腾讯位置服务开发key并开通驾车路线服务

    url: `https://apis.map.qq.com/ws/direction/v1/driving/?from=${startPoint.lat},${startPoint.lng}&to=${endPoint.lat},${endPoint.lng}&output=jsonp&get_mp=1&key=`,

   adapter: jsonpAdapter,

  }).then((res) => {

    routes.value = res.data.result.routes.map(route => (

      {

        polyline: formatPolyline(route.polyline),

        duration: route.duration,

        distance: route.distance,

      }

    ));

  });

};

// 接口返回的点串数据是压缩过的,需要解压并返回经纬度对象数组

const formatPolyline = (datas) => {

  const polyline = [...datas];

  for (let i = 2; i < polyline.length ; i++) {

    polyline[i] = polyline[i - 2] + polyline[i] / 1000000;

  }

  const paths = [];

  for (let i = 0; i < polyline.length; i += 2) {

    paths.push({

      lat: polyline[i],

      lng: polyline[i + 1],

    });

  }

  return paths;

};

...

<script>

React代码

import axios from 'axios';

import jsonpAdapter from 'axios-jsonp';

...

export default () => {

 const [routes, setRoutes] = useState([]);

 //请求获取路线规划数据

 const routePlan = useCallback(async () => {

   const res = await axios({

     // 接口地址中需要填入腾讯位置服务开发key并开通驾车路线服务

     url: `https://apis.map.qq.com/ws/direction/v1/driving/?from=${startPoint.lat},${startPoint.lng}&to=${endPoint.lat},${endPoint.lng}&output=jsonp&get_mp=1&key=`,

     adapter: jsonpAdapter,

   });

   setRoutes(res.data.result.routes.map((route) => ({

     polyline: formatPolyline(route.polyline),

     duration: route.duration,

     distance: route.distance,

   })));

 }, [startPoint, endPoint]);

};

// 接口返回的点串数据是压缩过的,需要解压并返回经纬度对象数组

const formatPolyline = (datas: number[]) => {

 const polyline = [...datas];

 for (let i = 2; i < polyline.length ; i++) {

   polyline[i] = polyline[i - 2] + polyline[i] / 1000000;

 }

 const paths = [];

 for (let i = 0; i < polyline.length; i += 2) {

   paths.push({

     lat: polyline[i],

     lng: polyline[i + 1],

   });

 }

 return paths;

};

6

绘制驾车路线

在基础地图组件子节点增加折线组件,折线组件和点标记组件类似,需要传入geometries折线数据、styles折线样式,我们将上一步调用驾车路线规划服务获取的数据格式化为geometries数据传入到组件中即可绘制驾车路线。这里我们在页面新增一个按钮,点击时会调用routePlan方法触发路线规划

在页面中根据routes数据显示多个方案的tab,每个tab会展示路线的耗时、距离,代码中currentPlanIndex代表选中方案编号,点击对应方案会调用switchPlan改变currentPlanIndex值,同时重新计算geometries,对选中的路线的styleId设置为select,其他路线styleId是default,从而高亮选中路线。

Vue代码

<template>

  <div class='map-container'>

    <tlbs-map

      api-key=''

      :center='startPoint'

      :zoom='15'

      style='height: 100vh;'

    >

      <tlbs-multi-polyline

        :geometries='polylineGeometries'

        :styles='polylineStyles'

      />

      <tlbs-multi-marker

        ref='markerRef'

        :geometries='markerGeometries'

        :styles='markerStyles'

      />

    </tlbs-map>

    <div class='map-handle'>

      <button @click='routePlan'>

        路线规划

      </button>

    </div>

    <div

       v-if='routes.length'

       class='route-container'

    >

      <div

        v-for='(item, index) in routes'

        :key='index'

        class='route-plan-item'

        :class='{ active: currentPlanIndex === index }'

        @click='switchPlan(index)'

      >

        <p style='font-weight: 600;'>

          {{ item.duration }}分钟

        </p>

        <p>{{ item.distance / 1000 }}公里</p>

      </div>

    </div>

   </div>

</template>

<script setup>

...

// 线样式

const polylineStyles = {

// 选中线样式

  select: {

    color: '#11CA53', // 线填充色

    width: 4, // 折线宽度

    borderWidth: 2, // 边线宽度

    borderColor: '#FFF', // 边线颜色

    lineCap: 'round', // 线端头方式

    eraseColor: 'rgba(190,188,188,1)',

  },

// 未选中线样式

    default: {

    color: '#88C29E', // 线填充色

    width: 4, // 折线宽度

    borderWidth: 2, // 边线宽度

    borderColor: '#FFF', // 边线颜色

    lineCap: 'round', // 线端头方式

    eraseColor: 'rgba(190,188,188,1)',

  },

};

// 当前选择方案编号

const currentPlanIndex = ref(0);

// 切换方案

const switchPlan = (index: number) => {

  currentPlanIndex.value = index;

  pauseMove();

};

const routes = shallowRef([]);

const routePlan = () => {

  axios({

    url: `https://apis.map.qq.com/ws/direction/v1/driving/?from=${startPoint.lat},${startPoint.lng}&to=${endPoint.lat},${endPoint.lng}&output=jsonp&get_mp=1&key=`,

   adapter: jsonpAdapter,

  }).then((res) => {

    routes.value = res.data.result.routes.map(route => (

      {

        polyline: formatPolyline(route.polyline),

        duration: route.duration,

        distance: route.distance,

      }

    ));

  });

};// 根据接口返回的路线数据格式化为折线组件需要的数据格式

const polylineGeometries = computed(() => routes.value.map((route, index) => ({

  id: `polyline_${index}`,

  styleId: currentPlanIndex.value === index ? 'select' : 'default',

  paths: route.polyline,

  rank: currentPlanIndex.value === index ? 10 : 1,

})));

...

<script>

React代码

import { useCallback, useRef, useState, useMemo } from 'react';import { TMap as TlbsMap , MultiMarker, MultiPolyline  } from 'tlbs-map-react';import axios from 'axios';import jsonpAdapter from 'axios-jsonp';...const polylineStyles = {// 选中线样式  select: {    color: '#11CA53', // 线填充色    width: 4, // 折线宽度    borderWidth: 2, // 边线宽度    borderColor: '#FFF', // 边线颜色    lineCap: 'round', // 线端头方式    eraseColor: 'rgba(190,188,188,1)',  },// 未选中线样式    default: {    color: '#88C29E', // 线填充色    width: 4, // 折线宽度    borderWidth: 2, // 边线宽度    borderColor: '#FFF', // 边线颜色    lineCap: 'round', // 线端头方式    eraseColor: 'rgba(190,188,188,1)',  },};export default () => {  const [currentPlanIndex, setCurrentPlanIndex] = useState(0);  const [routes, setRoutes] = useState([]);  const routePlan = useCallback(async () => {    const res = await axios({      url: `https://apis.map.qq.com/ws/direction/v1/driving/?from=${startPoint.lat},${startPoint.lng}&to=${endPoint.lat},${endPoint.lng}&output=jsonp&get_mp=1&key=`,      adapter: jsonpAdapter,    });    setRoutes(res.data.result.routes.map((route) => ({      polyline: formatPolyline(route.polyline),      duration: route.duration,      distance: route.distance,    })));  }, [startPoint, endPoint]);  // 根据接口返回的路线数据格式化为折线组件需要的数据格式  const polylineGeometries = useMemo(() => {    return routes.map((route, index) => ({      id: `polyline_${index}`,      styleId: currentPlanIndex === index ? 'select' : 'default',      paths: route.polyline,      rank: currentPlanIndex === index ? 10 : 1,    }))  }, [routes, currentPlanIndex]);   // 切换方案   const switchPlan = useCallback((index: number) => {    setCurrentPlanIndex(index);    pauseMove();  }, []);   // 更加路线数据遍历生成方案tab,展示方案耗时及距离   const routeItems = routes.map((route, index) => {    const className = index === currentPlanIndex ? 'route-plan-item active' : 'route-plan-item';    return (      <div key={index} className={className} onClick={() => switchPlan(index)}>        <p>{ route.duration }分钟</p>        <p>{ route.distance / 1000 }公里</p>      </div>    )  });

 return (    <>      <TlbsMap        apiKey=''        options={{          center: startPoint,          zoom: 15,        }}        style={{ width: '100%', height: '100%' }}      >        <MultiPolyline          styles={polylineStyles}          geometries={polylineGeometries}        >        </MultiPolyline>        <MultiMarker          ref={markerRef}          styles={markerStyles}          geometries={markerGeometries}        />      </TlbsMap>      <div className='map-handle'>        <button onClick={routePlan}>          路线规划        </button>      </div>      {      routeItems.length      && <div className='route-container'>        {routeItems}      </div>}    </>  );};...

7

小车移动、暂停、恢复移动

实现小车移动、暂停、恢复移动需要分别调用点标记实例的moveAlong、pauseMove、resumeMove方法,通过ref可以获取点标记组件中的点标记实例。

Vue代码

<template>

  <div class='map-container'>

    <tlbs-map

      :center='startPoint'

      :zoom='15'

      style='height: 100vh;'

    >

      <tlbs-multi-polyline

        :geometries='polylineGeometries'

        :styles='polylineStyles'

      />

      <tlbs-multi-marker

        ref='markerRef'

        :geometries='markerGeometries'

        :styles='markerStyles'

      />

    </tlbs-map>

        <div class='map-handle'>

      <button @click='routePlan'>

        路线规划

      </button>

      <button @click='moveAlong'>

        出发

      </button>

      <button @click='pauseMove'>

        暂停

      </button>

      <button @click='resumeMove'>

        恢复

      </button>

    </div>

    ...

  </div>

</template>

<script setup>

...

// 获取点标记组件实例

const markerRef = ref(null);

// 小车移动出发

const moveAlong = () => {

  // 小车移动的路线数据

  const tMapPaths = polylineGeometries.value[currentPlanIndex.value]

    .paths.map(path => new TMap.LatLng(path.lat, path.lng));

  // 调用点标记图层实例的marker对象moveAlong方法进行小车移动

    markerRef.value.marker.moveAlong(

    {

      car: {

        path: tMapPaths,

        speed: 250,

      },

    },

    {

      autoRotation: true,

    },

  );

};

// 小车暂停

const pauseMove = () => {

  // 调用点标记图层实例的marker对象pauseMove方法暂停小车移动

  markerRef.value.marker.pauseMove();

};

// 小车恢复移动

const resumeMove = () => {

  // 调用点标记图层实例的marker对象pauseMove方法恢复小车移动

  markerRef.value.marker.resumeMove();

};

...

<script>

React代码

...export default () => {  ...  const markerRef = useRef(null);  // 小车移动出发  const moveAlong = useCallback(() => {    // 小车移动的路线数据    const tMapPaths = polylineGeometries[currentPlanIndex].paths.map(path => new TMap.LatLng(path.lat, path.lng));    // 调用点标记图层实例的marker对象moveAlong方法进行小车移动    markerRef.current.moveAlong(      {        car: {          path: tMapPaths,          speed: 250,        },      },      {        autoRotation: true,      },    );  }, [polylineGeometries, currentPlanIndex]);  // 小车暂停  const pauseMove = useCallback(() => {    // 调用点标记图层实例的marker对象pauseMove方法暂停小车移动    markerRef.current.pauseMove();  }, []);  // 小车恢复移动  const resumeMove = useCallback(() => {    // 调用点标记图层实例的marker对象pauseMove方法恢复小车移动    markerRef.current.resumeMove();  }, []);

 return (    <>      <TlbsMap        apiKey=''        options={{          center: startPoint,          zoom: 15,        }}        style={{ width: '100%', height: '100%' }}      >        <MultiPolyline          styles={polylineStyles}          geometries={polylineGeometries}        >        </MultiPolyline>        <MultiMarker          ref={markerRef}          styles={markerStyles}          geometries={markerGeometries}        />      </TlbsMap>      <div className='map-handle'>        <button onClick={moveAlong}>          出发        </button>        <button onClick={pauseMove}>          暂停        </button>        <button onClick={resumeMove}>          恢复        </button>      </div>      ...    </>  );};

到这里我们就使用地图组件库完成了路线规划和模拟小车按规划路线移动功能的开发。

使用好地图组件库能够帮助降低地图业务开发成本,欢迎您接入使用tlbs-map地图组件库并参与到组件库的开源共建。同时,别忘了在GitHub上关注并star我们的项目,以便及时获取最新动态哦!下一期为创建鸿蒙系统第一个地图应用。

以下是组件库地址:

地图组件库使用指南:

JavaScript API | 腾讯位置服务

Vue组件库:

https://github.com/Tencent/tlbs-map-vue

React组件库:

https://github.com/Tencent/tlbs-map-react

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多