iOS 自带地图(MapKit、MKMapView)轨迹渐变

JBOBeatris 8年前
   <p style="text-align:center"><img src="https://simg.open-open.com/show/2fd67db48822f2bf2c7c1fedaf04c2e7.png"></p>    <p style="text-align:center">WechatIMG2.png</p>    <p>项目已接入高德地图,并且大部分功能已经实现好,但BOSS觉得iOS自带的地图效果更好。。。本着面向老板编程的思想,换之。还好,高德地图是在MapKit上封装的,大部分api只要将前缀MA->MK即可,但有一个问题麻烦了,就是处理轨迹的渐变,Mapkit没有相应的方法,高德又不是开源的,而且国内的网站上基本搜不到解决方案,所以在这里把自己的思路和在国外论坛上找到的解决方法分享出来,让其他做运动的同行节省点时间。</p>    <p>如何绘制mapView就不说了,在mapView上绘制轨迹要添加MKPolyline,调用[self.mapView addOverlay:self.polyLine];但这个MKPolyline的构造方法中只接受和坐标相关的值,而轨迹渐变自然要通过速度控制,但传不进去,所以只能重写一个实现<MKOverlay>协议的类。下面就是我找到的,拿去可以直接用:</p>    <p>GradientPolylineOverlay.h实现</p>    <pre>  <code class="language-objectivec">#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</code></pre>    <p>GradientPolylineOverlay.m</p>    <pre>  <code class="language-objectivec">#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</code></pre>    <p>下面是在mapview上添加PolyLine的方法:([self smoothTrack] 是我针对项目需求做速度平滑渐变的算法,可以忽略;我绘制轨迹的坐标数据结构是由精度、维度、速度构成的字典;最后添加的方法是调用mapview的分类中的方法,所以有级别,也是根我需求相关,可直接用[self.mapView addOverlay:self.polyline] 代替)</p>    <pre>  <code class="language-objectivec">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];</code></pre>    <p>轨迹添加好了,还需要在渲染器上面呈现,会调用Mapkit相应的代理:(GradientPolylineRenderer 则是与之对应的渲染器)</p>    <pre>  <code class="language-objectivec">#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;  }</code></pre>    <p>GradientPolylineRenderer.h实现:</p>    <pre>  <code class="language-objectivec">#import <MapKit/MapKit.h>    @interface GradientPolylineRenderer : MKOverlayPathRenderer    @end</code></pre>    <p>GradientPolylineRenderer.m实现:(上面的几个宏定义,是速度的边界值,以及对应的颜色边界值;另外我这里会对hues[i]是否为0做判断,项目需求要区分暂停点和速度过快点,已防作弊,此种情况会用虚线代替,如果只绘制渐变实线,不用管这)</p>    <pre>  <code class="language-objectivec">#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</code></pre>    <p>以上就是我基于Mapkit绘制渐变轨迹的方式,相比高德的轨迹,圆滑程度还是欠缺些,但这些都是项目日后优化的点。如有问题,欢迎大家共同讨论、共同进步。</p>    <p> </p>    <p>来自:http://www.jianshu.com/p/2d71cc8dd035</p>    <p> </p>