跑步运动软件最基本的功能之一,就是对运动中的用户进行实时定位,并绘制出运动路径。本文主要内容,就是用高德的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.jpgPs.先设置麻烦此时可以新建一个OC类,这时xcode会自动弹出一个是否创建桥接头文件提示框,选择Create Bridging Header即可自动生成并完成工程的对应设置。
6.jpg文件代码实现如下
// Create-Bridging-Header.h
#import <MAMapKit/MAMapKit.h>
<h3>设置ApiKey</h3>
使用地图需要先设置ApiKey,否则会报ApiKey为空的警告。在项目启动的时候设置,所以在AppDelegate的didFinishLaunchingWithOptions进行处理
// 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有兴趣的自己看源码修改来用的,地址见下方:
<完> :)
网友评论