Breadcrumb/CrumbPathRenderer.m

/*
 Copyright (C) 2014 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 */
 
#import "CrumbPathRenderer.h"
#import "CrumbPath.h"
 
 
@implementation CrumbPathRenderer
 
- (void)drawMapRect:(MKMapRect)mapRect
          zoomScale:(MKZoomScale)zoomScale
          inContext:(CGContextRef)context;
{
    CrumbPath *crumbs = (CrumbPath *)(self.overlay);
    
    CGFloat lineWidth = MKRoadWidthAtZoomScale(zoomScale);
    
    // outset the map rect by the line width so that points just outside
    // of the currently drawn rect are included in the generated path.
    MKMapRect clipRect = MKMapRectInset(mapRect, -lineWidth, -lineWidth);
    
    __block CGPathRef path = nil;
    [crumbs readPointsWithBlockAndWait:^(MKMapPoint *points, NSUInteger pointsCount){
        path = [self newPathForPoints:points
                           pointCount:pointsCount
                             clipRect:clipRect
                            zoomScale:zoomScale];
    }];
    
    if (path != nil)
    {
        CGContextAddPath(context, path);
        CGContextSetRGBStrokeColor(context, 0.0f, 0.0f, 1.0f, 0.5f);
        CGContextSetLineJoin(context, kCGLineJoinRound);
        CGContextSetLineCap(context, kCGLineCapRound);
        CGContextSetLineWidth(context, lineWidth);
        CGContextStrokePath(context);
        CGPathRelease(path);
    }
}
 
 
#pragma mark - Private Implementation
 
static BOOL LineBetweenPointsIntersectsRect(MKMapPoint p0, MKMapPoint p1, MKMapRect r)
{
    double minX = MIN(p0.x, p1.x);
    double minY = MIN(p0.y, p1.y);
    double maxX = MAX(p0.x, p1.x);
    double maxY = MAX(p0.y, p1.y);
    
    MKMapRect r2 = MKMapRectMake(minX, minY, maxX - minX, maxY - minY);
    return MKMapRectIntersectsRect(r, r2);
}
 
static inline double POW2(a) { return a * a; }
 
- (CGPathRef)newPathForPoints:(MKMapPoint *)points
                      pointCount:(NSUInteger)pointCount
                        clipRect:(MKMapRect)mapRect
                       zoomScale:(MKZoomScale)zoomScale
{
    CGMutablePathRef path = nil;
    
    // The fastest way to draw a path in an MKOverlayView is to simplify the
    // geometry for the screen by eliding points that are too close together
    // and to omit any line segments that do not intersect the clipping rect.  
    // While it is possible to just add all the points and let CoreGraphics 
    // handle clipping and flatness, it is much faster to do it yourself:
    //
    if (pointCount > 1)
    {
        path = CGPathCreateMutable();
        
        BOOL needsMove = YES;
        
        // Calculate the minimum distance between any two points by figuring out
        // how many map points correspond to MIN_POINT_DELTA of screen points
        // at the current zoomScale.
        const double MIN_POINT_DELTA = 5.0;
        double minPointDelta = MIN_POINT_DELTA / zoomScale;
        double c2 = POW2(minPointDelta);
        
        MKMapPoint lastPoint = points[0];
        for (NSUInteger i = 1; i < pointCount - 1; i++)
        {
            MKMapPoint point = points[i];
            double a2b2 = POW2(point.x - lastPoint.x) + POW2(point.y - lastPoint.y);
            if (a2b2 >= c2) {
                if (LineBetweenPointsIntersectsRect(point, lastPoint, mapRect))
                {
                    if (needsMove)
                    {
                        CGPoint lastCGPoint = [self pointForMapPoint:lastPoint];
                        CGPathMoveToPoint(path, NULL, lastCGPoint.x, lastCGPoint.y);
                    }
                    CGPoint cgPoint = [self pointForMapPoint:point];
                    CGPathAddLineToPoint(path, NULL, cgPoint.x, cgPoint.y);
                    needsMove = NO;
                }
                else
                {
                    // discontinuity, lift the pen
                    needsMove = YES;
                }
                lastPoint = point;
            }
        }
        
        // If the last line segment intersects the mapRect at all, add it unconditionally 
        MKMapPoint point = points[pointCount - 1];
        if (LineBetweenPointsIntersectsRect(point, lastPoint, mapRect))
        {
            if (needsMove)
            {
                CGPoint lastCGPoint = [self pointForMapPoint:lastPoint];
                CGPathMoveToPoint(path, NULL, lastCGPoint.x, lastCGPoint.y);
            }
            CGPoint cgPoint = [self pointForMapPoint:point];
            CGPathAddLineToPoint(path, NULL, cgPoint.x, cgPoint.y);
        }
    }
    
    return path;
}
 
@end