
跑步運(yùn)動(dòng)軟件最基本的功能之一,就是對(duì)運(yùn)動(dòng)中的用戶進(jìn)行實(shí)時(shí)定位,并繪制出運(yùn)動(dòng)路徑。本文主要內(nèi)容,就是用高德的SDK實(shí)現(xiàn)簡(jiǎn)單的路徑繪制。(蘋(píng)果內(nèi)置的地圖用的也是高德,當(dāng)然,你也可以用百度的。)
首先,xcode創(chuàng)建一個(gè)新的工程,選擇Single View Application,語(yǔ)言選擇Swift

工程創(chuàng)建完畢,下一步是導(dǎo)入高德地圖的framework。可以選擇用CocoaPods,手動(dòng)安裝方式如下:
下載高德地圖SDK,申請(qǐng)API Key。相關(guān)網(wǎng)頁(yè)
(撰寫(xiě)本文時(shí)高德SDK的最新版本為3.3.0,需要加-Objc,否則相關(guān)的接口調(diào)用會(huì)直接崩潰,Demo使用的版本為3.2.0,不需要加標(biāo)記,版本之間一些接口和變量會(huì)存在差異。)
<h3>導(dǎo)入SDK</h3>
左側(cè)目錄中選中工程名,在 TARGETS->Build Phases-> Link Binary With Libaries 中點(diǎn)擊“+”按鈕,在彈出的窗口中點(diǎn)擊“Add Other”按鈕,選擇下載好的 MAMapKit.framework 文件添加到工程中。

<h3>引入AMap.bundle資源文件</h3>
AMap.bundle資源文件中存儲(chǔ)了定位、默認(rèn)大頭針標(biāo)注視圖等圖片,可利用這些資源圖片進(jìn)行開(kāi)發(fā)。
左側(cè)目錄中選中工程名,在右鍵菜單中選擇Add Files to “工程名”…,從 MAMapKit.framework->Resources 文件夾中選擇 AMap.bundle文件,并勾選“Copy items if needed”復(fù)選框,單擊“Add”按鈕,將資源文件添加到工程中。
(MAMapKit.framework最好放在工程文件下,否則有可能導(dǎo)致編譯不過(guò)。)
<h3>引入依賴庫(kù)</h3>
左側(cè)目錄中選中工程名,在TARGETS->Build Settings-> Link Binary With Libaries中點(diǎn)擊“+”按鈕,在彈出的窗口中查找并選擇所需的庫(kù)(見(jiàn)下),單擊“Add”按鈕,將庫(kù)文件添加到工程中。
libz.tbd
libstdc++.6.0.9.tbd
Security.framework
SystemConfiguration.framework
CoreTelephony.framework
OpenGLES.framework
CoreLocation.framework
CoreGraphics.framework
Foundation.framework
UIKit.framework
引入完成后如下圖所示:

<h3>Info.plist添加屬性</h3>
添加NSLocationAlwaysUsageDescription。(不添加這貨的話會(huì)導(dǎo)致無(wú)法進(jìn)行定位,被這個(gè)問(wèn)題坑了下。。)
添加Required background modes,并給它添加App registers for location updates屬性,使得應(yīng)用在進(jìn)入后臺(tái)時(shí)也能繼續(xù)接受定位信息,MAMapView有一個(gè)屬性的設(shè)置和這個(gè)相關(guān),后續(xù)提到。
App Transport Security Settings添加上Allow Arbitrary Loads,并設(shè)為YES,iOS9之后網(wǎng)絡(luò)請(qǐng)求默認(rèn)改為https,不修改會(huì)報(bào)警告
修改后info.plist如下所示

<h2>接下來(lái),開(kāi)始代碼的實(shí)現(xiàn)</h2>
<h3>新建橋接頭文件</h3>
SDK為第三方OC項(xiàng)目,在Swift中調(diào)用需要?jiǎng)?chuàng)建橋接頭文件。新建頭文件,命名為MapPath-Bridging-Header.h,然后設(shè)置好對(duì)應(yīng)的路徑,如下圖:

Ps.先設(shè)置麻煩此時(shí)可以新建一個(gè)OC類,這時(shí)xcode會(huì)自動(dòng)彈出一個(gè)是否創(chuàng)建橋接頭文件提示框,選擇Create Bridging Header即可自動(dòng)生成并完成工程的對(duì)應(yīng)設(shè)置。

文件代碼實(shí)現(xiàn)如下
// Create-Bridging-Header.h
#import <MAMapKit/MAMapKit.h>
<h3>設(shè)置ApiKey</h3>
使用地圖需要先設(shè)置ApiKey,否則會(huì)報(bào)ApiKey為空的警告。在項(xiàng)目啟動(dòng)的時(shí)候設(shè)置,所以在AppDelegate的didFinishLaunchingWithOptions進(jìn)行處理
// AppDelegate.swift
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
// 設(shè)置apiKey
MAMapServices.sharedServices().apiKey = "e8be3a280e6fc3c2651ebef77e7a0fa5"
return true
}
新建一個(gè)類RunningViewController,繼承自UIViewController,遵守MAMapViewDelegate,該類用來(lái)專門(mén)展示跑步過(guò)程中的地圖界面,同時(shí)創(chuàng)建RunningViewController.xib文件
class RunningViewController: UIViewController, MAMapViewDelegate
聲明屬性
@IBOutlet weak var mapView: MAMapView!
var coordinateArray: [CLLocationCoordinate2D] = []
將一個(gè)UIView拖到RunningViewController.xib的視圖上,設(shè)置四邊具體父視圖的間距為0,并鏈接上mapView。(懶得寫(xiě)代碼添加視圖了。)
coordinateArray數(shù)組用來(lái)存儲(chǔ)每次獲取的定位更新數(shù)據(jù),繪制路徑時(shí)需要用到。
在viewDidLoad的時(shí)候進(jìn)行地圖初始化的設(shè)置
override func viewDidLoad() {
super.viewDidLoad()
initMapView()
}
func initMapView()
{
mapView.delegate = self
mapView.zoomLevel = 15.5
mapView.distanceFilter = 3.0
mapView.desiredAccuracy = kCLLocationAccuracyBestForNavigation
}
將視圖控制器設(shè)置為mapView的代理,當(dāng)位置變化更新時(shí)通過(guò)代理進(jìn)行回調(diào),繪制路徑時(shí)也需要通過(guò)代理進(jìn)行屬性的設(shè)置
zoomLevel為當(dāng)前地圖縮放的比例
distanceFilter為定位的最小更新距離,當(dāng)移動(dòng)距離超過(guò)設(shè)定的值時(shí)便會(huì)有位置的更新回調(diào)
desiredAccuracy為定位精度,默認(rèn)為最高精度,一般直接用這個(gè)就好
當(dāng)視圖顯示后,開(kāi)始進(jìn)行定位
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
startLocation()
}
func startLocation()
{
mapView.showsUserLocation = true
mapView.userTrackingMode = MAUserTrackingMode.Follow
mapView.pausesLocationUpdatesAutomatically = false
mapView.allowsBackgroundLocationUpdates = true
}
showsUserLocation設(shè)為true后便開(kāi)始進(jìn)行定位,設(shè)為false則定位停止。定位本身會(huì)比較耗點(diǎn),應(yīng)用在不需要定位時(shí)記得將定位功能關(guān)閉,否則你的手機(jī)電量會(huì)消耗很快滴。
userTrackingMode為定位的方式,查看接口可知有三種形式,這里采用跟隨用戶位置移動(dòng)的定位方式
allowsBackgroundLocationUpdates設(shè)置為true表示允許進(jìn)行后臺(tái)定位,保證之前有設(shè)置過(guò)App registers for location updates屬性,否則會(huì)崩潰。
<h3>代理的實(shí)現(xiàn)</h3>
每次有位置更新變會(huì)調(diào)用mapView(mapView: MAMapView, didUpdateUserLocation userLocation: MAUserLocation, updatingLocation: Bool)函數(shù)
// MARK: MAMapViewDelegate
func mapView(mapView: MAMapView, didUpdateUserLocation userLocation: MAUserLocation, updatingLocation: Bool)
{
// 地圖每次有位置更新時(shí)的回調(diào)
if updatingLocation {
// 獲取新的定位數(shù)據(jù)
let coordinate = userLocation.coordinate
// 添加到保存定位點(diǎn)的數(shù)組
self.coordinateArray.append(coordinate)
updatePath()
}
}
通過(guò)updatePath函數(shù)進(jìn)行路徑的繪制
func updatePath () {
// 每次獲取到新的定位點(diǎn)重新繪制路徑
// 移除掉除之前的overlay
let overlays = self.mapView.overlays
self.mapView.removeOverlays(overlays)
let polyline = MAPolyline(coordinates: &self.coordinateArray, count: UInt(self.coordinateArray.count))
self.mapView.addOverlay(polyline)
// 將最新的點(diǎn)定位到界面正中間顯示
let lastCoord = self.coordinateArray[self.coordinateArray.count - 1]
self.mapView.setCenterCoordinate(lastCoord, animated: true)
}
之前最開(kāi)始的實(shí)現(xiàn)方式是用最新的點(diǎn)和上一次點(diǎn)進(jìn)行連接,但這種方式會(huì)有一個(gè)問(wèn)題,在將地圖放大到最大縮放比后會(huì)發(fā)現(xiàn),通過(guò)兩點(diǎn)兩點(diǎn)連接的線段并不能構(gòu)成一條完整的線,而是一段一段不連貫的線段。所以現(xiàn)在改為直接拿所有的點(diǎn)重新繪制整條路徑。繪制之前記得將已經(jīng)存在的路徑移除掉,否則每次繪制的路徑會(huì)堆積在一起,導(dǎo)致路徑線條變粗長(zhǎng)生毛刺。
removeOverlays用來(lái)移除掉之前的路徑。
addOverlay添加重新繪制的路徑。
setCenterCoordinate將指定的坐標(biāo)點(diǎn)顯示在地圖中間,保證在用戶跑了很長(zhǎng)的距離之后也不會(huì)超出地圖的顯示范圍。
通過(guò)addOverlay繪制路徑時(shí),會(huì)有一個(gè)函數(shù)回調(diào):
func mapView(mapView: MAMapView!, viewForOverlay overlay: MAOverlay!) -> MAOverlayView! {
if overlay.isKindOfClass(MAPolyline) {
let polylineView = MAPolylineView(overlay: overlay)
polylineView.lineWidth = 6
polylineView.strokeColor = UIColor(red: 4 / 255.0, green: 181 / 255.0, blue: 108 / 255.0, alpha: 1.0)
return polylineView
}
return nil
}
判斷是否為MAPolyline類型,然后設(shè)置路徑寬度和顏色。(除了線條以外,地圖上貼圖片等一些操作也在這個(gè)回調(diào)中進(jìn)行處理)
大體實(shí)現(xiàn)如上。接下來(lái)可以把Demo跑起來(lái)了。
程序大體樣子如下,打開(kāi)時(shí),界面中間有個(gè)按鈕,點(diǎn)擊之后便進(jìn)入地圖界面


此時(shí),你可能迫不及待地想編好真機(jī)然后到外面跑跑測(cè)試下效果,其實(shí)模擬器提供了一個(gè)功能,能夠直接模擬位置的變化。選中模擬器的Debug,點(diǎn)Location,里面有幾個(gè)選項(xiàng),有幾種改變位置的方式,也可以通過(guò)自己設(shè)置經(jīng)緯度進(jìn)行定位,其中Freeway Drive運(yùn)動(dòng)的速度比較快~:)
繪制運(yùn)動(dòng)路徑的效果如下圖:

(模擬器模擬的效果地圖上不會(huì)顯示其他信息,真機(jī)上則會(huì)跟你實(shí)際位置相對(duì)應(yīng),地圖內(nèi)容是完整顯示周?chē)畔⒌模?/p>
<h3>最后再有個(gè)后記</h3>
iOS系統(tǒng)的定位采用的是混合定位的方式,通過(guò)GPS、Wifi、手機(jī)基站信號(hào)共同定位的方式來(lái)提高定位精度,雖說(shuō)如此,但偶爾出現(xiàn)某個(gè)點(diǎn)的定位誤差依然是難以避免的,當(dāng)出現(xiàn)較大偏差時(shí),會(huì)導(dǎo)致路徑上有某個(gè)明顯凸出的點(diǎn),或整條路徑毛刺現(xiàn)象嚴(yán)重,即使長(zhǎng)時(shí)間在一個(gè)位置不動(dòng),也會(huì)出現(xiàn)定位點(diǎn)在附近導(dǎo)出亂飄的情況。這些問(wèn)題只能通過(guò)算法分析來(lái)修正。
常用的處理算法為卡爾曼濾波,相關(guān)的內(nèi)容對(duì)數(shù)學(xué)功底要求極高,有興趣可自行Google研究。
之前無(wú)意在github上找到了份相關(guān)的源碼實(shí)現(xiàn),用的是java,改成iOS后直接拿來(lái)用發(fā)現(xiàn)對(duì)結(jié)果的修正確實(shí)起到一定的幫助,能將繪制的路徑進(jìn)行平滑處理,濾掉了明顯的毛刺。
下面為源碼對(duì)測(cè)試數(shù)據(jù)處理前后的結(jié)果顯示


有興趣的自己看源碼修改來(lái)用的,地址見(jiàn)下方:
KalmanFilter的Java實(shí)現(xiàn)
<完> :)