高德地圖SDK實(shí)時(shí)繪制跑步路徑-Swift實(shí)現(xiàn)

-_-

跑步運(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

1.jpg

工程創(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 文件添加到工程中。

2.jpg

<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

引入完成后如下圖所示:

3.jpg

<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如下所示

4.jpg

<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)的路徑,如下圖:

5.jpg

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

6.jpg

文件代碼實(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è)置,所以在AppDelegatedidFinishLaunchingWithOptions進(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)入地圖界面

7.jpg
8.jpg

此時(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)路徑的效果如下圖:

9.jpg

(模擬器模擬的效果地圖上不會(huì)顯示其他信息,真機(jī)上則會(huì)跟你實(shí)際位置相對(duì)應(yīng),地圖內(nèi)容是完整顯示周?chē)畔⒌模?/p>

具體實(shí)現(xiàn)見(jiàn)源碼

<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é)果顯示

10.png
11.png

有興趣的自己看源碼修改來(lái)用的,地址見(jiàn)下方:

KalmanFilter的Java實(shí)現(xiàn)

<完> :)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容