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