• 微信小程序 + 腾讯位置服务SDK 实现路线规划


    前言

    本文旨在以mpvue框架为基础,探讨地图类小程序的开发思路。 原作者利用mpvue + 腾讯地图的能力做了一个地铁路线规划的小程序,主要提供全球主要城市的地铁线网图及旅游介绍,其中国内城市支持查看地图和路线规划。

    目前腾讯位置服务也推出了路线规划插件地铁图插件,实现更加简单便捷,感兴趣的可点击查看。

    运行截图

    mpvue 介绍 及项目搭建

    mpvue = miniprogram + vue framework,说白了就是用vue框架开发小程序。mpvue最近升级为2.x版本,支持微信、支付宝、百度和头条小程序。和传统方式相比,mpvue开发具有以下优点:

    彻底的组件化开发能力:提高代码复用性

    • 完整的 Vue.js 开发体验

    • 方便的 Vuex 数据管理方案:方便构建复杂应用

    • 快捷的 webpack 构建机制:自定义构建策略、开发阶段 hotReload

    • 支持使用 npm 外部依赖

    • 使用 Vue.js 命令行工具 vue-cli 快速初始化项目

    • H5 代码转换编译成小程序目标代码的能力

    就个人使用体验来看,还是挺丝滑顺畅的,传统web应用开发无缝切换至小程序开发,基本零门槛。要注意的就是小程序的限制及和vue的差异:

    • 小程序使用相对像素 rpx 进行样式布局

    • 部分css选择符不支持,目前只支持 #id | .class | tag | tag,tag | ::after ::before,所以要特别注意

    • 组合式生命周期,mpvue将小程序和vue的生命周期混在一块,详情见 mpvue.com/mpvue/#_3 ,目前这个地方还有很多坑,比如在小程序page unload时,vue实例却没被销毁,导致下次进入页面时,页面状态不变,必须在unLoad时手动重置状态等

    • mpvue 会封装小程序数据对象,通常以$mp开头,如event.$mp.detail.target

    • 小程序的组件和vue组件有差异,不要幻想vue组件的特性都能用,如slot,异步组件等等

    • vue store 和 wx localstorage 最好不要弄混,要根据不同需要选择不同的存储方式

    • 不要用vue路由,要采用小程序原生的导航机制

    然后,我们搭建开发环境,mpvue脚手架是开箱即用的:

    # 全局安装 vue-cli
    # 一般是要 sudo 权限的
    $ npm install --global vue-cli@2.9
    
    # 创建一个基于 mpvue-quickstart 模板的新项目
    # 新手一路回车选择默认就可以了
    $ vue init mpvue/mpvue-quickstart my-project
    
    # 安装依赖,走你
    $ cd my-project
    $ npm install
    $ npm run dev
    

    接着,完善文件结构,增加 config、store、mixins等模块,如图:

    app.json是小程序专用文件,也需完善下:

    {
      "pages": [
        "pages/citylist/main",
        "pages/citydetail/main"
      ],
      "permission": {
        "scope.userLocation": {
          "desc": "你的位置信息将用于小程序位置接口的效果展示"
        }
      },
      "window": {
        "backgroundTextStyle": "light",
        "navigationBarBackgroundColor": "#eee",
        "navigationBarTitleText": "全球地铁,全程为你",
        "navigationBarTextStyle": "black"
      }
    }
    

    然后就可以愉快的写Vue代码了,咔咔一个页面,咔咔又是一个页面,组件,store,数据驱动,你喜欢的样子,它都有。

    腾讯地图+ 小程序

    着重说一下地图的接入,腾讯地图提供了两个对接入口给小程序,1是个性化地图展示,2是专用SDK,二者共同完善了小程序的地图生态。

    1、个性地图展示需要开发者自行注册并申请开发者密钥(key),并在管理后台绑定小程序,然后设置个性地图的样式,才能使用:

    <map
      id="citymap"
      name="citymap"
      :longitude="lng"
      :latitude="lat"
      :polyline="polyline"
    	:markers="markers"
      scale="12"
      :subkey="YOUR_OWN_QQMAP_KEY"
      show-location
      show-compass
      enable-rotate
      style=" 100%; height: 100%;"
    >
      <cover-view class="map-cover-view">
        <button class="explore-btn" type="primary" @tap="exploreCity">查看旅游攻略</button>
      </cover-view>
    </map>
    

    其中,map是小程序的原生组件,原生组件脱离在 WebView 渲染流程外,它的层级是最高的,所以页面中的其他组件无论设置 z-index 为多少,都无法盖在原生组件上。说白了就是原生组件是微信客户端提供的,它不属于内置浏览器,为此,小程序专门提供了 cover-view 和 cover-image 组件,可以覆盖在部分原生组件上面。这两个组件也是原生组件,但是使用限制与其他原生组件有所不同。

    笔者就因为这个坑耽误了不少时间,有时候开发工具可以用,但到了真机上组件就完全乱了,所以还是要以真机调试为准。对于原生组件,不要用太复杂的css,它的很多css属性支持的都不好。

    map可以定义多个参数,经纬度不用说,scale指放缩比例,也就是地图比例尺,polyline在地图上绘制折线,markers用于标记地图上的点,show-location用于显示用户所在位置,show-compass显示指北针。

    2、专用SDK,目前提供这些能力:

    • search(options:Object) 地点搜索,搜索周边poi,比如:“酒店” “餐饮” “娱乐” “学校” 等等

    • getSuggestion(options:Object) 用于获取输入关键字的补完与提示,帮助用户快速输入

    • reverseGeocoder(options:Object) 提供由坐标到坐标所在位置的文字描述的转换。输入坐标返回地理位置信息和附近poi列表

    • geocoder(options:Object) 提供由地址描述到所述位置坐标的转换,与逆地址解析的过程正好相反

    • direction(options:Object) 提供驾车,步行,骑行,公交的路线规划能力

    • getCityList() 获取全国城市列表数据

    • getDistrictByCityId(options:Object) 通过城市ID返回城市下的区县

    • calculateDistance(options:Object) 计算一个点到多点的步行、驾车距离

    我们以公共交通路线规划为例来看下(以下代码经过简化处理):

    第一步,初始化地图SDK对象

    import config from '@/config'
    import QQMapWX from '../../assets/lib/qqmap-wx-jssdk.js' // 这里用未压缩版的代码
    const QQMapSDK = new QQMapWX({
      key: config.qqMapKey || ''
    })
    

    第二步,获取起止坐标点,并进行路线查询

    // 坐标从上一页query传进来,坐标为浮点数,可通过geocoder接口获取
    this.fromLocation = {
      latitude: +query.from.split(',')[0] || -1,
      longitude: +query.from.split(',')[1] || -1
    }
    
    this.toLocation = {
      latitude: +query.to.split(',')[0] || -1,
      longitude: +query.to.split(',')[1] || -1
    }
    
    // 查询地图路线
    queryMapRoutine() {
      QQMapSDK.direction({
        mode: 'transit', // 'transit'(公交路线规划)
        // from参数不填默认当前地址
        from: this.fromLocation,
        to: this.toLocation,
        success: (res) => {
          console.log('路线规划结果', res);
          let routes = res.result.routes;
          this.routes = routes.map(r => {
    				// 对每一种路线方案,分别进行解析
            return this.parseRoute(r)
          })
          console.log('parsed routes', this.routes)
        }
      })
    }
    

    第三步,路线解析,生成路线描述等

    // 解析路线,包括距离,时间,描述,路线,起止点等
    parseRoute(route) {
    	let result = {}
    	// 出发时间
      result.setOutTime = formatTime(new Date())
      result.distance = route.distance < 1000 ?
                        `${route.distance}米` :
                        `${(route.distance / 1000).toFixed(2)}公里`
      result.duration = route.duration < 60 ?
                        `${route.duration}分钟` :
                        `${parseInt(route.duration / 60)}小时${route.duration % 60}分钟`
    	result.desc = []
    	// 每一个路线分很多步,如先步行,后乘公交,再搭地铁等
      route.steps.forEach(step => {
        // if (step.mode == 'WALKING' && step.distance > 0) {
        //   result.desc.push(`向${step.direction}步行${step.distance}米`)
        // }
        if (step.mode == 'TRANSIT' && step.lines[0]) {
          let line = step.lines[0]
          if (line.vehicle == 'BUS') line.title = `公交车-${line.title}`
          if (line.vehicle == 'RAIL') line.title = `铁路`
          result.desc.push(`${line.title}: ${line.geton.title} —> ${line.getoff.title},途经 ${line.station_count} 站。`)
        }
      })
      result.polyline = []
      result.points = []
      //获取各个步骤的polyline,也就是路线图
      for(let i = 0; i < route.steps.length; i++) {
        let step = route.steps[i]
        let polyline = this.getStepPolyline(step)
        if (polyline) {
          result.points = result.points.concat(polyline.points)
          result.polyline.push(polyline)
        }
    	}
    	// 标记路线整体显示坐标
      this.getStepPolyline.colorIndex = 0
      let midPointIndex = Math.floor(result.points.length / 2)
      result.latitude = result.points[midPointIndex].latitude
      result.longitude = result.points[midPointIndex].longitude
      // 标记路线起止点
      let startPoint = result.points[0]
      let endPoint = result.points[result.points.length - 1]
      result.markers = [
        {
          iconPath: this.startIcon,
          id: 0,
          latitude: startPoint.latitude,
          longitude: startPoint.longitude,
           28,
          height: 28,
          zIndex: -1,
          anchor: {x: 0.5, y: 1}
        },
        {
          iconPath: this.endIcon,
          id: 1,
          latitude: endPoint.latitude,
          longitude: endPoint.longitude,
           28,
          height: 28,
          zIndex: -1,
          anchor: {x: 0.5, y: 1}
        }
      ]
      return result
    },
    

    第四步,getStepPolyline函数 获取路线每一步的路线polyline

    getStepPolyline(step) {
    	let coors = [];
    	// 随机颜色
      let colorArr = ['#1aad19', '#10aeff', '#d84e43']
      let _dottedLine = true
      if (step.mode == 'WALKING' && step.polyline) {
        coors.push(step.polyline);
        _dottedLine = false
      } else if (step.mode == 'TRANSIT' && step.lines[0].polyline) {
        coors.push(step.lines[0].polyline);
      } else {
        return null
      }
      //坐标解压(返回的点串坐标,通过前向差分进行压缩)
      let kr = 1000000;
      for (let i = 0 ; i < coors.length; i++){
        for (let j = 2; j < coors[i].length; j++) {
          coors[i][j] = Number(coors[i][j - 2]) + Number(coors[i][j]) / kr;
        }
      }
      //定义新数组,将coors中的数组合并为一个数组
      let coorsArr = [];
      let _points = [];
      for (let i = 0 ; i < coors.length; i ++){
        coorsArr = coorsArr.concat(coors[i]);
      }
      //将解压后的坐标放入点串数组_points中
      for (let i = 0; i < coorsArr.length; i += 2) {
        _points.push({ latitude: coorsArr[i], longitude: coorsArr[i + 1] })
      }
      if (!this.getStepPolyline.colorIndex) {
        this.getStepPolyline.colorIndex = 0
      }
      let colorIndex = this.getStepPolyline.colorIndex % colorArr.length
    	this.getStepPolyline.colorIndex++
    	// 最终polyline结果
      let polyline = {
         7,
        points: _points,
        color: colorArr[colorIndex],
        dottedLine: _dottedLine,
        arrowLine: true, // 带箭头的线, 开发者工具暂不支持该属性
        borderColor: '#fff',
        borderWidth: 1
      }
      return polyline
    }
    

    最后,绑定到地图上并输出,我们可以得到一个大致这样的结果:

    广州火车站 -> 广州塔
    9.88km 30分钟
    地铁5号线 广州火车站 -> 珠江新城,途径7站
    地铁3号线 珠江新城 -> 广州塔,途径1站
    

    这样我们就通过direction接口进行了简单的路线规划功能,接着把生成的数据绑定到地图组件上,一个简易的小程序就做好了,是不是很简单?当然如果想做得更好,就要调用其他相似接口,慢慢完善细节。

    <map
      id="citymap"
      name="citymap"
      :latitude="currentRoute.latitude"
      :longitude="currentRoute.longitude"
      :polyline="currentRoute.polyline"
      :markers="currentRoute.markers"
      scale="12"
      :subkey="qqMapKey"
      show-location
      show-compass
      enable-rotate
      style=" 100%; height: 100%;"
    ></map>
    

    效果

    作者:棱镜_jh

    链接:https://juejin.cn/post/6844903809420886029

    来源:掘金

    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 相关阅读:
    [Yii Framework] 在views里面如何调用本controller的方法,获取一定的值
    [Yii Framework] 如何调用extension扩展
    [Yii Framework] 当AR类于数据库中的表的名字不同时
    [Yii Framework] 创建helper的注意事项
    [Yii Framework] Parameterized Named Scopes(命名规范参数化)
    [Yii Framework] 验证方法
    [Yii Framework] 删除AR后注意事项
    [Yii Framework] 如何使用theme
    [Yii Framework] 创建自己的extension
    彻底解决刷新重复提交问题,你还在用Response.Redirect吗?
  • 原文地址:https://www.cnblogs.com/Dreamholder/p/14485903.html
Copyright © 2020-2023  润新知