1//
2//  AIRMapPolylineRenderer.h
3//  mapDemo
4//
5//  Created by IjzerenHein on 13-11-21.
6//  Copyright (c) 2017 IjzerenHein. All rights reserved.
7//
8
9#import "AIRMapPolylineRenderer.h"
10
11@interface AIRMapPolylineRendererSegment : NSObject
12- (id)initWithPoint:(CGPoint)point color:(UIColor*)color;
13- (void) addPoint:(CGPoint)point color:(UIColor*)color;
14@property CGMutablePathRef path;
15@property UIColor *startColor;
16@property UIColor *endColor;
17@property CGPoint startPoint;
18@property CGPoint endPoint;
19@end
20@implementation AIRMapPolylineRendererSegment
21- (id)initWithPoint:(CGPoint)point color:(UIColor*)color
22{
23    self = [super init];
24    if (self){
25        self.path = CGPathCreateMutable();
26        self.startColor = color;
27        self.startPoint = point;
28        self.endPoint = point;
29        CGPathMoveToPoint(self.path, nil, point.x, point.y);
30    }
31    return self;
32}
33- (void) addPoint:(CGPoint)point color:(UIColor*)color
34{
35    self.endPoint = point;
36    self.endColor = color;
37    CGPathAddLineToPoint(self.path, nil, point.x, point.y);
38}
39@end
40
41@implementation AIRMapPolylineRenderer {
42    MKPolyline* _polyline;
43    NSArray<UIColor *> *_strokeColors;
44    MKMapSnapshot* _snapshot;
45    CLLocationCoordinate2D* _coordinates;
46}
47
48@synthesize strokeColors;
49
50- (id)initWithOverlay:(id<MKOverlay>)overlay polyline:(MKPolyline*)polyline
51{
52    self = [super initWithOverlay:overlay];
53    if (self){
54        _polyline = polyline;
55        [self createPath];
56    }
57    return self;
58}
59
60- (id)initWithSnapshot:(MKMapSnapshot*)snapshot overlay:(id<MKOverlay>)overlay polyline:(MKPolyline*)polyline
61{
62    self = [super initWithOverlay:overlay];
63    if (self){
64        _snapshot = snapshot;
65        _polyline = polyline;
66        _coordinates = malloc(sizeof(CLLocationCoordinate2D) * [_polyline pointCount]);
67        [_polyline getCoordinates:_coordinates range:NSMakeRange(0, [_polyline pointCount])];
68    }
69    return self;
70}
71
72- (void) dealloc
73{
74    if (_coordinates) free(_coordinates);
75}
76
77- (CGPoint) pointForIndex:(NSUInteger)index
78{
79    if (_snapshot != nil) {
80        return [_snapshot pointForCoordinate:_coordinates[index]];
81    }
82    else {
83        return [self pointForMapPoint:_polyline.points[index]];
84    }
85}
86
87- (UIColor*) colorForIndex:(NSUInteger)index
88{
89    if ((_strokeColors == nil) || !_strokeColors.count) return self.strokeColor;
90    index = MIN(index, _strokeColors.count - 1);
91    UIColor* color = _strokeColors[index];
92    CGFloat pc_r,pc_g,pc_b,pc_a;
93    [color getRed:&pc_r green:&pc_g blue:&pc_b alpha:&pc_a];
94    return (pc_a == 0) ? nil : color;
95}
96
97- (void) createPath
98{
99    CGMutablePathRef path = CGPathCreateMutable();
100    BOOL first = YES;
101    for (NSUInteger i = 0, n = _polyline.pointCount; i < n; i++){
102        CGPoint point = [self pointForIndex:i];
103        if (first) {
104            CGPathMoveToPoint(path, nil, point.x, point.y);
105            first = NO;
106        } else {
107            CGPathAddLineToPoint(path, nil, point.x, point.y);
108        }
109    }
110    self.path = path;
111}
112
113- (NSArray*) createSegments
114{
115    NSMutableArray* segments = [NSMutableArray new];
116    if (_polyline.pointCount <= 1) return segments;
117    AIRMapPolylineRendererSegment* segment = nil;
118    for (NSUInteger i = 0, n = _polyline.pointCount; i < n; i++){
119        CGPoint point = [self pointForIndex:i];
120        UIColor* color = [self colorForIndex:i];
121        if (segment == nil) {
122
123            // Start new segment
124            segment = [[AIRMapPolylineRendererSegment alloc] initWithPoint:point color:color];
125            [segments addObject:segment];
126        }
127        else if (((color == nil) && (segment.endColor == nil)) ||
128                 ((color != nil) && [segment.startColor isEqual:color])) {
129
130            // Append point to segment
131            [segment addPoint:point color: color];
132        }
133        else {
134
135            // Close the last segment if needed
136            if (segment.endColor == nil) {
137                [segment addPoint:point color:color];
138            }
139            else {
140
141                // Add transition gradient
142                segment = [[AIRMapPolylineRendererSegment alloc] initWithPoint:segment.endPoint color:segment.endColor];
143                [segment addPoint:point color:color];
144                [segments addObject:segment];
145            }
146
147            // Start new segment
148            if (i < (n - 1)) {
149                segment = [[AIRMapPolylineRendererSegment alloc] initWithPoint:point color:color];
150                [segments addObject:segment];
151            }
152        }
153    }
154
155    // Remove last segment in case it only contains a single path point
156    if ((segment != nil) && (segment.endColor == nil)) {
157        [segments removeLastObject];
158    }
159
160    return segments;
161}
162
163- (void) setStrokeColors:(NSArray<UIColor *> *)strokeColors
164{
165    if (_strokeColors != strokeColors) {
166        _strokeColors = strokeColors;
167    }
168}
169
170- (void) setStrokeColor:(UIColor *)strokeColor
171{
172    if (super.strokeColor != strokeColor) {
173        super.strokeColor = strokeColor;
174    }
175}
176
177- (void) drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context
178{
179    CGRect pointsRect = CGPathGetBoundingBox(self.path);
180    CGRect mapRectCG = [self rectForMapRect:mapRect];
181    if (!CGRectIntersectsRect(pointsRect, mapRectCG)) return;
182
183    [self drawWithZoomScale:zoomScale inContext:context];
184}
185
186- (void) drawWithZoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context
187{
188    CGFloat lineWidth = (self.lineWidth / zoomScale) * 2.0;
189    CGContextSetLineWidth(context, lineWidth);
190    CGContextSetLineCap(context, self.lineCap);
191    CGContextSetLineJoin(context, self.lineJoin);
192    CGContextSetFillColorWithColor(context, self.fillColor.CGColor);
193    CGContextSetMiterLimit(context, self.miterLimit);
194    CGFloat dashes[self.lineDashPattern.count];
195    for (NSUInteger i = 0; i < self.lineDashPattern.count; i++) {
196        dashes[i] = self.lineDashPattern[i].floatValue;
197    }
198    CGContextSetLineDash(context, self.lineDashPhase, dashes, self.lineDashPattern.count);
199
200    NSArray* segments = [self createSegments];
201    for (NSUInteger i = 0, n = segments.count; i < n; i++) {
202        AIRMapPolylineRendererSegment* segment = segments[i];
203
204        CGContextBeginPath(context);
205        CGContextAddPath(context, segment.path);
206
207        // When segment has two colors, draw it as a gradient
208        if (![segment.startColor isEqual:segment.endColor]) {
209            CGFloat pc_r,pc_g,pc_b,pc_a,
210            cc_r,cc_g,cc_b,cc_a;
211            [segment.startColor getRed:&pc_r green:&pc_g blue:&pc_b alpha:&pc_a];
212            [segment.endColor getRed:&cc_r green:&cc_g blue:&cc_b alpha:&cc_a];
213            CGFloat gradientColors[8] = {pc_r,pc_g,pc_b,pc_a,
214                cc_r,cc_g,cc_b,cc_a};
215            CGFloat gradientLocation[2] = {0,1};
216            CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
217            CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradientColors, gradientLocation, 2);
218            CGColorSpaceRelease(colorSpace);
219
220            CGContextReplacePathWithStrokedPath(context);
221            CGContextClip(context);
222            CGContextDrawLinearGradient(
223                                        context,
224                                        gradient,
225                                        segment.startPoint,
226                                        segment.endPoint,
227                                        kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation
228                                        );
229            CGGradientRelease(gradient);
230            CGContextResetClip(context);
231        }
232        else {
233            CGContextSetStrokeColorWithColor(context, segment.startColor.CGColor);
234            CGContextStrokePath(context);
235        }
236    }
237}
238
239@end
240
241