在MKMapView中添加渐变色路径

作者: 口子窖 | 来源:发表于2018-06-08 15:23 被阅读58次

刚来苏宁接触到的业务并不多,既然要写博客那就暂时上一篇之前所遇到的关于在地图控件中如何添加渐变色路径实现方案吧。

整体实现原理是依据用户跑步配速将坐标点等分为若干色块,将色块相连就形成了渐变色轨迹

如何在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

相关文章

网友评论

  • BennyLoo:绘制的时候,polyLine设置了颜色,但是有时候整个线条都变成了黑色。 请问这个情况怎么处理??
    口子窖:那是因为速度属性没有值了,所以显示了黑色。解决方法可以设置速度值,或者设置无速度时的line颜色
  • 9bd131d72a30:你好请问下MKMapView原生方法里有个显示比例尺showsScale属性 我想问除了这个属性可以控制比例尺外还有别的方法控制比例尺的位置和使其一直显示的方法吗
    口子窖:MKCoordinateRegion可以用这个API去设置。region大概意识是表示地图中的一块区域,设置完region的center和span.lat和span.lon就可以设置地图的缩放比例和缩放位置

本文标题:在MKMapView中添加渐变色路径

本文链接:https://www.haomeiwen.com/subject/zczbsftx.html