微信小程式 + 騰訊地圖SDK 實現路線規劃

稜鏡_jh發表於2019-03-29

原文連結

最近小程式的發展越來越火了,作為各個產品線的extra服務入口,以輕便、快速、強大的社交鏈吸引著大量的使用者和開發者。業內開發框架層出不窮,wepy,mpvue,taro等等,都在朝著更快,更強大的方向發展,有統一 H5、微信、支付寶、百度和頭條小程式的大趨勢。

本文旨在以mpvue框架為基礎,探討地圖類小程式的開發思路,權當分享和總結。 筆者利用mpvue + 騰訊地圖的能力做了一個地鐵路線規劃的小程式,主要提供全球主要城市的地鐵線網圖及旅遊介紹,其中國內城市支援檢視地圖和路線規劃。雖然功能並不複雜,但也算一個實用工具了。話不多說,先體驗一下:

小程式碼

github原始碼地址: github.com/WarpPrism/S…

執行截圖

微信小程式 + 騰訊地圖SDK 實現路線規劃
微信小程式 + 騰訊地圖SDK 實現路線規劃
微信小程式 + 騰訊地圖SDK 實現路線規劃
微信小程式 + 騰訊地圖SDK 實現路線規劃

下面就開始正式介紹如何開發了:

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等模組,如圖:

code structure

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="width: 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,
      width: 28,
      height: 28,
      zIndex: -1,
      anchor: {x: 0.5, y: 1}
    },
    {
      iconPath: this.endIcon,
      id: 1,
      latitude: endPoint.latitude,
      longitude: endPoint.longitude,
      width: 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 = {
    width: 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="width: 100%; height: 100%;"
></map>
複製程式碼

更多實現請參照原始碼和小程式本身,如果對你有幫助,可以star支援。

其他

這裡還有一款鋼琴教學類的 web應用,釋出在gitee page上

crystalworld.gitee.io/qpiano/#/

感興趣的朋友可以看下,之後有時間筆者會補上開發教程和原始碼,另作分享。

2019-04-01更新: 介紹文章傳送門: juejin.im/post/5ca1d2…

相關文章