• 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
    著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
  • 相关阅读:
    MySQL显示数据库版本的SQL语句
    如何清空ostringstream对象中的内容
    C/C++中的Split函数
    关于socket长连接的心跳包
    利用MyEclipse配置S2SH三大框架篇-Spring配置
    利用MyEclipse配置S2SH三大框架篇-struts2配置
    Oracle OCP 11G 051答案解析目录
    AFX_EXT_CLASS
    C++中的explicit关键字
    SSH2三大框架整合警告
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/5949236.html
Copyright © 2020-2023  润新知