iOS 原生地图(MapKit、MKMapView)轨迹渐变


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
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值