WechatIMG2.png

项目已接入高德地图,并且大部分功能已经实现好,但BOSS觉得iOS自带的地图效果更好。。。本着面向老板编程的思想,换之。还好,高德地图是在MapKit上封装的,大部分api只要将前缀MA->MK即可,但有一个问题麻烦了,就是处理轨迹的渐变,Mapkit没有相应的方法,高德又不是开源的,而且国内的网站上基本搜不到解决方案,所以在这里把自己的思路和在国外论坛上找到的解决方法分享出来,让其他做运动的同行节省点时间。

如何在iPhone上绘制mapView就不说了,在mapView上绘制轨迹要添加MKPolyline,调用[self.mapView addOverlay:self.polyLine];但这个MKPolyline的构造方法中只接受和坐标相关的值,而轨迹渐变自然要通过速度控制,但传不进去,所以只能重写一个实现<MKOverlay>协议的类。下面就是我找到的,拿去可以直接用:

GradientPolylineOverlay.h实现

#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h> @interface GradientPolylineOverlay : NSObject <MKOverlay>{
MKMapPoint *points;
NSUInteger pointCount;
NSUInteger pointSpace; MKMapRect boundingMapRect;
pthread_rwlock_t rwLock;
} //Initialize the overlay with the starting coordinate.
//The overlay's boundingMapRect will be set to a sufficiently large square
//centered on the starting coordinate.
-(id) initWithCenterCoordinate:(CLLocationCoordinate2D)coord; -(id) initWithPoints:(CLLocationCoordinate2D*)_points velocity:(float*)_velocity count:(NSUInteger)_count; //Add a location observation. A MKMapRect containing the newly added point
//and the previously added point is returned so that the view can be updated
//int that rectangle. If the added coordinate has not moved far enough from
//the previously added coordinate it will not be added to the list and
//MKMapRectNULL will be returned.
//
-(MKMapRect)addCoordinate:(CLLocationCoordinate2D)coord; -(void) lockForReading; //The following properties must only be accessed when holding the read lock
// via lockForReading. Once you're done accessing the points, release the
// read lock with unlockForReading.
//
@property (assign) MKMapPoint *points;
@property (readonly) NSUInteger pointCount;
@property (assign) float *velocity; -(void) unlockForReading; @end

GradientPolylineOverlay.m

#import "GradientPolylineOverlay.h"
#import <pthread.h> #define INITIAL_POINT_SPACE 1000
#define MINIMUM_DELTA_METERS 10.0 @implementation GradientPolylineOverlay{
} @synthesize points, pointCount, velocity; -(id) initWithCenterCoordinate:(CLLocationCoordinate2D)coord{
self = [super init];
if (self){
//initialize point storage and place this first coordinate in it
pointSpace = INITIAL_POINT_SPACE;
points = malloc(sizeof(MKMapPoint)*pointSpace);
points[0] = MKMapPointForCoordinate(coord);
pointCount = 1; //bite off up to 1/4 of the world to draw into
MKMapPoint origin = points[0];
origin.x -= MKMapSizeWorld.width/8.0;
origin.y -= MKMapSizeWorld.height/8.0;
MKMapSize size = MKMapSizeWorld;
size.width /=4.0;
size.height /=4.0;
boundingMapRect = (MKMapRect) {origin, size};
MKMapRect worldRect = MKMapRectMake(0, 0, MKMapSizeWorld.width, MKMapSizeWorld.height);
boundingMapRect = MKMapRectIntersection(boundingMapRect, worldRect); // initialize read-write lock for drawing and updates
pthread_rwlock_init(&rwLock,NULL);
}
return self;
} -(id) initWithPoints:(CLLocationCoordinate2D*)_points velocity:(float *)_velocity count:(NSUInteger)_count{
self = [super init];
if (self){
pointCount = _count;
self.points = malloc(sizeof(MKMapPoint)*pointCount);
for (int i=0; i<_count; i++){
self.points[i] = MKMapPointForCoordinate(_points[i]);
} self.velocity = malloc(sizeof(float)*pointCount);
for (int i=0; i<_count;i++){
self.velocity[i] = _velocity[i];
} //bite off up to 1/4 of the world to draw into
MKMapPoint origin = points[0];
origin.x -= MKMapSizeWorld.width/8.0;
origin.y -= MKMapSizeWorld.height/8.0;
MKMapSize size = MKMapSizeWorld;
size.width /=4.0;
size.height /=4.0;
boundingMapRect = (MKMapRect) {origin, size};
MKMapRect worldRect = MKMapRectMake(0, 0, MKMapSizeWorld.width, MKMapSizeWorld.height);
boundingMapRect = MKMapRectIntersection(boundingMapRect, worldRect); // initialize read-write lock for drawing and updates
pthread_rwlock_init(&rwLock,NULL);
}
return self;
} -(void)dealloc{
free(points);
free(velocity);
pthread_rwlock_destroy(&rwLock);
} //center
-(CLLocationCoordinate2D)coordinate{
return MKCoordinateForMapPoint(points[0]);
} -(MKMapRect)boundingMapRect{
return boundingMapRect;
} -(void) lockForReading{
pthread_rwlock_rdlock(&rwLock);
} -(void) unlockForReading{
pthread_rwlock_unlock(&rwLock);
} -(MKMapRect)addCoordinate:(CLLocationCoordinate2D)coord{
//Acquire the write lock because we are going to changing the list of points
pthread_rwlock_wrlock(&rwLock); //Convert a CLLocationCoordinate2D to an MKMapPoint
MKMapPoint newPoint = MKMapPointForCoordinate(coord);
MKMapPoint prevPoint = points[pointCount-1]; //Get the distance between this new point and previous point
CLLocationDistance metersApart = MKMetersBetweenMapPoints(newPoint, prevPoint);
MKMapRect updateRect = MKMapRectNull; if (metersApart > MINIMUM_DELTA_METERS){
//Grow the points array if necessary
if (pointSpace == pointCount){
pointSpace *= 2;
points = realloc(points, sizeof(MKMapPoint) * pointSpace);
} //Add the new point to points array
points[pointCount] = newPoint;
pointCount++; //Compute MKMapRect bounding prevPoint and newPoint
double minX = MIN(newPoint.x,prevPoint.x);
double minY = MIN(newPoint.y,prevPoint.y);
double maxX = MAX(newPoint.x, prevPoint.x);
double maxY = MAX(newPoint.y, prevPoint.y); updateRect = MKMapRectMake(minX, minY, maxX - minX, maxY - minY);
} pthread_rwlock_unlock(&rwLock); return updateRect;
} @end

下面是在mapview上添加PolyLine的方法:([self smoothTrack] 是我针对项目需求做速度平滑渐变的算法,可以忽略;我绘制轨迹的坐标数据结构是由精度、维度、速度构成的字典;最后添加的方法是调用mapview的分类中的方法,所以有级别,也是根我需求相关,可直接用[self.mapView addOverlay:self.polyline] 代替)

NSMutableArray *smoothTrackArray = [self smoothTrack];
double count = smoothTrackArray.count;
CLLocationCoordinate2D *points;
float *velocity;
points = malloc(sizeof(CLLocationCoordinate2D)*count);
velocity = malloc(sizeof(float)*count); for(int i=0;i<count;i++){
NSDictionary *dic = [smoothTrackArray objectAtIndex:i];
CLLocationDegrees latitude = [dic[@"latitude"] doubleValue];
CLLocationDegrees longitude = [dic[@"longitude"] doubleValue];
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(latitude, longitude);
velocity[i] = [dic[@"speed"] doubleValue];
points[i] = coordinate;
} self.polyline = [[GradientPolylineOverlay alloc] initWithPoints:points velocity:velocity count:count];
[self.mapView addOverlay:self.polyline level:1];

轨迹添加好了,还需要在渲染器上面呈现,会调用Mapkit相应的代理:(GradientPolylineRenderer 则是与之对应的渲染器)

#pragma mark -
#pragma mark - MKMap Delegate
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id <MKOverlay>)overlay{
if([overlay isKindOfClass:[GradientPolylineOverlay class]]){
//轨迹
GradientPolylineRenderer *polylineRenderer = [[GradientPolylineRenderer alloc] initWithOverlay:overlay];
polylineRenderer.lineWidth = 8.f;
return polylineRenderer;
}
return nil;
}

GradientPolylineRenderer.h实现:

#import <MapKit/MapKit.h>

@interface GradientPolylineRenderer : MKOverlayPathRenderer

@end

GradientPolylineRenderer.m实现:(上面的几个宏定义,是速度的边界值,以及对应的颜色边界值;另外我这里会对hues[i]是否为0做判断,项目需求要区分暂停点和速度过快点,已防作弊,此种情况会用虚线代替,如果只绘制渐变实线,不用管这)

#import "GradientPolylineRenderer.h"
#import <pthread.h>
#import "GradientPolylineOverlay.h"
#import "Constant.h" #define V_MAX 4.5
#define V_MIN 1.0
#define H_MAX 0.33
#define H_MIN 0.03 @implementation GradientPolylineRenderer{
float* hues;
pthread_rwlock_t rwLock;
GradientPolylineOverlay* polyline;
} - (id) initWithOverlay:(id<MKOverlay>)overlay{
self = [super initWithOverlay:overlay];
if (self){
pthread_rwlock_init(&rwLock,NULL);
polyline = ((GradientPolylineOverlay*)self.overlay);
float *velocity = polyline.velocity;
int count = (int)polyline.pointCount;
[self velocity:velocity ToHue:&hues count:count];
[self createPath];
}
return self;
}
/**
* Convert velocity to Hue using specific formular.
*
* H(v) = Hmax, (v > Vmax)
* = Hmin + ((v-Vmin)*(Hmax-Hmin))/(Vmax-Vmin), (Vmin <= v <= Vmax)
* = Hmin, (v < Vmin)
*
* @param velocity Velocity list.
* @param count count of velocity list.
*
* @return An array of hues mapping each velocity.
*/
- (void) velocity:(float*)velocity ToHue:(float**)_hue count:(int)count{
*_hue = malloc(sizeof(float)*count);
for (int i=0;i<count;i++){
float curVelo = velocity[i];
// //原有渐变公式
// curVelo = ((curVelo < V_MIN) ? V_MIN : (curVelo > V_MAX) ? V_MAX : curVelo);
// (*_hue)[i] = H_MIN + ((curVelo-V_MIN)*(H_MAX-H_MIN))/(V_MAX-V_MIN); if(curVelo>0.){
curVelo = ((curVelo < V_MIN) ? V_MIN : (curVelo > V_MAX) ? V_MAX : curVelo);
(*_hue)[i] = H_MIN + ((curVelo-V_MIN)*(H_MAX-H_MIN))/(V_MAX-V_MIN);
}else{
//暂停颜色
(*_hue)[i] = 0.;
}
}
} -(void) createPath{
CGMutablePathRef path = CGPathCreateMutable();
BOOL pathIsEmpty = YES;
for (int i=0;i< polyline.pointCount;i++){
CGPoint point = [self pointForMapPoint:polyline.points[i]];
if (pathIsEmpty){
CGPathMoveToPoint(path, nil, point.x, point.y);
pathIsEmpty = NO;
} else {
CGPathAddLineToPoint(path, nil, point.x, point.y);
}
} pthread_rwlock_wrlock(&rwLock);
self.path = path; //<—— don't forget this line.
pthread_rwlock_unlock(&rwLock);
} //-(BOOL)canDrawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale{
// CGRect pointsRect = CGPathGetBoundingBox(self.path);
// CGRect mapRectCG = [self rectForMapRect:mapRect];
// return CGRectIntersectsRect(pointsRect, mapRectCG);
//} - (void) drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context{ //put this blok into the canDraw method cause problem
CGRect pointsRect = CGPathGetBoundingBox(self.path);
CGRect mapRectCG = [self rectForMapRect:mapRect];
if (!CGRectIntersectsRect(pointsRect, mapRectCG))return;
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineJoin(context, kCGLineJoinRound);
UIColor* pcolor,*ccolor;
for (int i=0;i< polyline.pointCount;i++){
CGPoint point = [self pointForMapPoint:polyline.points[i]];
CGMutablePathRef path = CGPathCreateMutable(); if(hues[i]==0.){
//虚线
if(i==0){
CGPathMoveToPoint(path, nil, point.x, point.y);
}else{
//颜色
CGContextSetRGBStrokeColor(context, 153.0 / 255.0, 153.0 / 255.0, 153.0 / 255.0, 1.0);
//线宽
CGFloat lineWidth = CGContextConvertSizeToUserSpace(context, (CGSize){self.lineWidth,self.lineWidth}).width;
CGContextSetLineWidth(context, lineWidth); CGFloat lengths[] = {lineWidth*2,lineWidth*2};//设置虚线
CGContextSetLineDash(context, lineWidth, lengths, 2);//设置虚线 CGPoint prevPoint = [self pointForMapPoint:polyline.points[i-1]];
CGPathMoveToPoint(path, nil, prevPoint.x, prevPoint.y);
CGPathAddLineToPoint(path, nil, point.x, point.y);
CGContextAddPath(context, path);
CGContextStrokePath(context);
}
}else{
//跑步渐变
ccolor = [UIColor colorWithHue:hues[i] saturation:1.0f brightness:1.0f alpha:1.0f];
if (i==0){
CGPathMoveToPoint(path, nil, point.x, point.y);
} else {
CGPoint prevPoint = [self pointForMapPoint:polyline.points[i-1]];
CGPathMoveToPoint(path, nil, prevPoint.x, prevPoint.y);
CGPathAddLineToPoint(path, nil, point.x, point.y); CGFloat pc_r,pc_g,pc_b,pc_a,
cc_r,cc_g,cc_b,cc_a; [pcolor getRed:&pc_r green:&pc_g blue:&pc_b alpha:&pc_a];
[ccolor getRed:&cc_r green:&cc_g blue:&cc_b alpha:&cc_a]; CGFloat gradientColors[8] = {pc_r,pc_g,pc_b,pc_a,
cc_r,cc_g,cc_b,cc_a}; CGFloat gradientLocation[2] = {0,1};
CGContextSaveGState(context);
CGFloat lineWidth = CGContextConvertSizeToUserSpace(context, (CGSize){self.lineWidth,self.lineWidth}).width;
CGPathRef pathToFill = CGPathCreateCopyByStrokingPath(path, NULL, lineWidth, self.lineCap, self.lineJoin, self.miterLimit);
CGContextAddPath(context, pathToFill);
CGContextClip(context);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradientColors, gradientLocation, 2);
CGColorSpaceRelease(colorSpace);
CGPoint gradientStart = prevPoint;
CGPoint gradientEnd = point;
CGContextDrawLinearGradient(context, gradient, gradientStart, gradientEnd, kCGGradientDrawsAfterEndLocation);
CGGradientRelease(gradient);
CGContextRestoreGState(context);
pcolor = [UIColor colorWithCGColor:ccolor.CGColor];
}
}
}
}
@end

以上就是我基于Mapkit绘制渐变轨迹的方式,相比高德的轨迹,圆滑程度还是欠缺些,但这些都是项目日后优化的点。
最后,附上我项目的下载地址,如果有帮到你,求好评,谢谢!;如果有其他问题,也欢迎一起讨论
https://itunes.apple.com/cn/app/ning-meng-pao-bu/id1108388147?mt=8

文/柠檬跑步(简书作者)
原文链接:http://www.jianshu.com/p/2d71cc8dd035
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

iOS 原生地图(MapKit、MKMapView)轨迹渐变的更多相关文章

  1. IOS原生地图与高德地图

    原生地图 1.什么是LBS LBS: 基于位置的服务   Location Based Service 实际应用:大众点评,陌陌,微信,美团等需要用到地图或定位的App 2.定位方式 1.GPS定位  ...

  2. iOS原生地图开发指南续——大头针与自定义标注

    iOS原生地图开发指南续——大头针与自定义标注 出自:http://www.sxt.cn/info-6042-u-7372.html 在上一篇博客中http://my.oschina.net/u/23 ...

  3. iOS原生地图开发详解

    在上一篇博客中:http://my.oschina.net/u/2340880/blog/414760.对iOS中的定位服务进行了详细的介绍与参数说明,在开发中,地位服务往往与地图框架结合使用,这篇博 ...

  4. iOS原生地图开发进阶——使用导航和附近兴趣点检索

    iOS原生地图开发进阶——使用导航和附近兴趣点检索 iOS中的mapKit框架对国际化的支持非常出色.在前些篇博客中,对这个地图框架的基础用法和标注与覆盖物的添加进行了详细的介绍,这篇博客将介绍两个更 ...

  5. iOS 原生地图 开发

    iOS开发有时候用到地图,不少人第一想到的是用第三方.当然有时候为了和安卓同步,可能会一起使用某一第三方.但有时候,我们是可以用原生地图开发的.上面两个示意图是原生地图的自定义开发.运行demo,将展 ...

  6. iOS原生地图与高德地图的使用

    原生地图 1.什么是LBS LBS: 基于位置的服务 Location Based Service 实际应用:大众点评,陌陌,微信,美团等需要用到地图或定位的App 2.定位方式 1.GPS定位 2. ...

  7. iOS 原生地图地理编码与反地理编码

    当我们要在App实现功能:输入地名,编码为经纬度,实现导航功能. 那么,我需要用到原生地图中的地理编码功能,而在Core Location中主要包含了定位.地理编码(包括反编码)功能. 在文件中导入 ...

  8. 如何初始化一个iOS原生地图

    /** 初始化一个mapView  需导入 #import <MapKit/MapKit.h> - returns: 返回一个mapView对象 */ mapView = [[MKMapV ...

  9. iOS之原生地图与高德地图

    原生地图 1.什么是LBS LBS: 基于位置的服务 Location Based Service 实际应用:大众点评,陌陌,微信,美团等需要用到地图或定位的App 2.定位方式 1.GPS定位 2. ...

随机推荐

  1. while (cin>>str)退出死循环

    今天在练习的时候突然发现了这个问题,百度之感觉还挺常见的,故记之! //题目描述 // //写出一个程序,接受一个十六进制的数值字符串,输出该数值的十进制字符串. // //输入描述 : //输入一个 ...

  2. SNMP 和 NetBios协议理解

      一.简单网络管理协议(SNMP,Simple Network Management Protocol)构成了互联网工程工作小组(IETF,Internet Engineering Task For ...

  3. HBase高性能复杂条件查询引擎---二级多列索引

    http://www.infoq.com/cn/articles/hbase-second-index-engine 原理 “二级多列索引”是针对目标记录的某个或某些列建立的“键-值”数据,以列的值为 ...

  4. js 发红包

    <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content ...

  5. 全面理解Linux输入输出重定向

    全面理解Linux输入输出重定向 本教程通过视频方式讲解shell操作,理解感念,教程通俗易懂,比起看一大堆文档要舒服的多.本次教程主要讲解  Linux Shell 中支持输入输出重定向,用符号&l ...

  6. 关于javascript里面仿python切片操作数组的使用方法

    其实在使用了好一段时间的 python之后,我觉得最让我念念不忘的并不是python每次在写函数或者循环的时候可以少用{}括号这样的东西(ps:其实也是了..感觉很清爽,而且又开始写js的时候老是想用 ...

  7. 数据库中使用 Synonym和openquery

    如果,你想在一台数据库服务器上,查询另一个台数据服务器的数据该如何做呢?如果,你想在同一台数据服务器上,在不同的数据库之间查询数据,又该怎么办呢?那就让我为你介绍Synonym和openquery吧. ...

  8. 第三百三十天 how can I 坚持

    今天是姥姥二周年,不是忘了,是根本就没不知道,没放在心上,正月十九,记下了,人这一辈子. 搞不懂,搞不懂那签. 锥地求泉,先难后易,顺其自然,偶遇知己,携手同行,是签文的关键,我逐个解释给你听.锥地求 ...

  9. 二、 C#调用存储过程

    个人比较喜欢使用第二种传递参数的方法 1. 调用的方法 public DataTable ExceStoredProcedure (string strCom, SqlParameter[] comm ...

  10. mssql 用户只能查看授权的数据库

    问题背景:公司的一台数据库服务器上放在多个数据库,每个数据库都使用不同的登录名称,但在将项目文件发布到Ftp时,有些Ftp的信息是在客户那边的 一旦客户那边使用配置文件中的数据库信息连接到数据库他就能 ...