高德地图SDK实时绘制跑步路径-Swift实现

-_-

跑步运动软件最基本的功能之一,就是对运动中的用户进行实时定位,并绘制出运动路径。本文主要内容,就是用高德的SDK实现简单的路径绘制。(苹果内置的地图用的也是高德,当然,你也可以用百度的。)

首先,xcode创建一个新的工程,选择Single View Application,语言选择Swift

1.jpg

工程创建完毕,下一步是导入高德地图的framework。可以选择用CocoaPods,手动安装方式如下:

下载高德地图SDK,申请API Key。相关网页
(撰写本文时高德SDK的最新版本为3.3.0,需要加-Objc,否则相关的接口调用会直接崩溃,Demo使用的版本为3.2.0,不需要加标记,版本之间一些接口和变量会存在差异。)

<h3>导入SDK</h3>

左侧目录中选中工程名,在 TARGETS->Build Phases-> Link Binary With Libaries 中点击“+”按钮,在弹出的窗口中点击“Add Other”按钮,选择下载好的 MAMapKit.framework 文件添加到工程中。

2.jpg

<h3>引入AMap.bundle资源文件</h3>

AMap.bundle资源文件中存储了定位、默认大头针标注视图等图片,可利用这些资源图片进行开发。

左侧目录中选中工程名,在右键菜单中选择Add Files to “工程名”…,从 MAMapKit.framework->Resources 文件夹中选择 AMap.bundle文件,并勾选“Copy items if needed”复选框,单击“Add”按钮,将资源文件添加到工程中。

(MAMapKit.framework最好放在工程文件下,否则有可能导致编译不过。)

<h3>引入依赖库</h3>

左侧目录中选中工程名,在TARGETS->Build Settings-> Link Binary With Libaries中点击“+”按钮,在弹出的窗口中查找并选择所需的库(见下),单击“Add”按钮,将库文件添加到工程中。

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。(不添加这货的话会导致无法进行定位,被这个问题坑了下。。)

添加Required background modes,并给它添加App registers for location updates属性,使得应用在进入后台时也能继续接受定位信息,MAMapView有一个属性的设置和这个相关,后续提到。

App Transport Security Settings添加上Allow Arbitrary Loads,并设为YES,iOS9之后网络请求默认改为https,不修改会报警告

修改后info.plist如下所示

4.jpg

<h2>接下来,开始代码的实现</h2>

<h3>新建桥接头文件</h3>

SDK为第三方OC项目,在Swift中调用需要创建桥接头文件。新建头文件,命名为MapPath-Bridging-Header.h,然后设置好对应的路径,如下图:

5.jpg

Ps.先设置麻烦此时可以新建一个OC类,这时xcode会自动弹出一个是否创建桥接头文件提示框,选择Create Bridging Header即可自动生成并完成工程的对应设置。

6.jpg

文件代码实现如下

// Create-Bridging-Header.h
#import <MAMapKit/MAMapKit.h>

<h3>设置ApiKey</h3>

使用地图需要先设置ApiKey,否则会报ApiKey为空的警告。在项目启动的时候设置,所以在AppDelegatedidFinishLaunchingWithOptions进行处理

//  AppDelegate.swift
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    // Override point for customization after application launch.
    
    // 设置apiKey
    MAMapServices.sharedServices().apiKey = "e8be3a280e6fc3c2651ebef77e7a0fa5"
    return true
}

新建一个类RunningViewController,继承自UIViewController,遵守MAMapViewDelegate,该类用来专门展示跑步过程中的地图界面,同时创建RunningViewController.xib文件

class RunningViewController: UIViewController, MAMapViewDelegate

声明属性

@IBOutlet weak var mapView: MAMapView!
var coordinateArray: [CLLocationCoordinate2D] = []

将一个UIView拖到RunningViewController.xib的视图上,设置四边具体父视图的间距为0,并链接上mapView。(懒得写代码添加视图了。)

coordinateArray数组用来存储每次获取的定位更新数据,绘制路径时需要用到。

viewDidLoad的时候进行地图初始化的设置

override func viewDidLoad() {
    super.viewDidLoad()

    initMapView()
}

func initMapView()
{
    mapView.delegate = self
    mapView.zoomLevel = 15.5
    mapView.distanceFilter = 3.0
    mapView.desiredAccuracy = kCLLocationAccuracyBestForNavigation
}

将视图控制器设置为mapView的代理,当位置变化更新时通过代理进行回调,绘制路径时也需要通过代理进行属性的设置

zoomLevel为当前地图缩放的比例

distanceFilter为定位的最小更新距离,当移动距离超过设定的值时便会有位置的更新回调

desiredAccuracy为定位精度,默认为最高精度,一般直接用这个就好

当视图显示后,开始进行定位

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设为true后便开始进行定位,设为false则定位停止。定位本身会比较耗点,应用在不需要定位时记得将定位功能关闭,否则你的手机电量会消耗很快滴。

userTrackingMode为定位的方式,查看接口可知有三种形式,这里采用跟随用户位置移动的定位方式

allowsBackgroundLocationUpdates设置为true表示允许进行后台定位,保证之前有设置过App registers for location updates属性,否则会崩溃。

<h3>代理的实现</h3>

每次有位置更新变会调用mapView(mapView: MAMapView, didUpdateUserLocation userLocation: MAUserLocation, updatingLocation: Bool)函数

// MARK: MAMapViewDelegate

func mapView(mapView: MAMapView, didUpdateUserLocation userLocation: MAUserLocation, updatingLocation: Bool)
{
    // 地图每次有位置更新时的回调
    
    if updatingLocation {
        // 获取新的定位数据
        let coordinate = userLocation.coordinate
        
        // 添加到保存定位点的数组
        self.coordinateArray.append(coordinate)
       
        updatePath()
    }
}

通过updatePath函数进行路径的绘制

 func updatePath () {
    
    // 每次获取到新的定位点重新绘制路径
    
    // 移除掉除之前的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)
    
    // 将最新的点定位到界面正中间显示
    let lastCoord = self.coordinateArray[self.coordinateArray.count - 1]
    self.mapView.setCenterCoordinate(lastCoord, animated: true)
}

之前最开始的实现方式是用最新的点和上一次点进行连接,但这种方式会有一个问题,在将地图放大到最大缩放比后会发现,通过两点两点连接的线段并不能构成一条完整的线,而是一段一段不连贯的线段。所以现在改为直接拿所有的点重新绘制整条路径。绘制之前记得将已经存在的路径移除掉,否则每次绘制的路径会堆积在一起,导致路径线条变粗长生毛刺。

removeOverlays用来移除掉之前的路径。

addOverlay添加重新绘制的路径。

setCenterCoordinate将指定的坐标点显示在地图中间,保证在用户跑了很长的距离之后也不会超出地图的显示范围。

通过addOverlay绘制路径时,会有一个函数回调:

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类型,然后设置路径宽度和颜色。(除了线条以外,地图上贴图片等一些操作也在这个回调中进行处理)

大体实现如上。接下来可以把Demo跑起来了。

程序大体样子如下,打开时,界面中间有个按钮,点击之后便进入地图界面

7.jpg
8.jpg

此时,你可能迫不及待地想编好真机然后到外面跑跑测试下效果,其实模拟器提供了一个功能,能够直接模拟位置的变化。选中模拟器的Debug,点Location,里面有几个选项,有几种改变位置的方式,也可以通过自己设置经纬度进行定位,其中Freeway Drive运动的速度比较快~:)

绘制运动路径的效果如下图:

9.jpg

(模拟器模拟的效果地图上不会显示其他信息,真机上则会跟你实际位置相对应,地图内容是完整显示周围信息的)

具体实现见源码

<h3>最后再有个后记</h3>

iOS系统的定位采用的是混合定位的方式,通过GPS、Wifi、手机基站信号共同定位的方式来提高定位精度,虽说如此,但偶尔出现某个点的定位误差依然是难以避免的,当出现较大偏差时,会导致路径上有某个明显凸出的点,或整条路径毛刺现象严重,即使长时间在一个位置不动,也会出现定位点在附近导出乱飘的情况。这些问题只能通过算法分析来修正。

常用的处理算法为卡尔曼滤波,相关的内容对数学功底要求极高,有兴趣可自行Google研究。

之前无意在github上找到了份相关的源码实现,用的是java,改成iOS后直接拿来用发现对结果的修正确实起到一定的帮助,能将绘制的路径进行平滑处理,滤掉了明显的毛刺。

下面为源码对测试数据处理前后的结果显示

10.png
11.png

有兴趣的自己看源码修改来用的,地址见下方:

KalmanFilter的Java实现

<完> :)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容