iOS 自帶地圖(MapKit、MKMapView)軌跡漸變
WechatIMG2.png
項目已接入高德地圖,并且大部分功能已經實現好,但BOSS覺得iOS自帶的地圖效果更好。。。本著面向老板編程的思想,換之。還好,高德地圖是在MapKit上封裝的,大部分api只要將前綴MA->MK即可,但有一個問題麻煩了,就是處理軌跡的漸變,Mapkit沒有相應的方法,高德又不是開源的,而且國內的網站上基本搜不到解決方案,所以在這里把自己的思路和在國外論壇上找到的解決方法分享出來,讓其他做運動的同行節省點時間。
如何繪制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繪制漸變軌跡的方式,相比高德的軌跡,圓滑程度還是欠缺些,但這些都是項目日后優化的點。如有問題,歡迎大家共同討論、共同進步。
來自:http://www.jianshu.com/p/2d71cc8dd035