summaryrefslogtreecommitdiff
path: root/platform/ios/src/MGLUserLocationHeadingBeamLayer.m
blob: efe7e4db9376f66f5513880b3bcfd9a9e5e69728 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#import "MGLUserLocationHeadingBeamLayer.h"

#import "MGLFaux3DUserLocationAnnotationView.h"
#import "MGLGeometry.h"

@implementation MGLUserLocationHeadingBeamLayer
{
    CAShapeLayer *_maskLayer;
}

- (instancetype)initWithUserLocationAnnotationView:(MGLUserLocationAnnotationView *)userLocationView
{
    CGFloat size = MGLUserLocationAnnotationHaloSize;

    self = [super init];
    self.bounds = CGRectMake(0, 0, size, size);
    self.position = CGPointMake(CGRectGetMidX(userLocationView.bounds), CGRectGetMidY(userLocationView.bounds));
    self.contents = (__bridge id)[self gradientImageWithTintColor:userLocationView.tintColor.CGColor];
    self.contentsGravity = kCAGravityBottom;
    self.contentsScale = UIScreen.mainScreen.scale;
    self.opacity = 0.4;
    self.shouldRasterize = YES;
    self.rasterizationScale = UIScreen.mainScreen.scale;
    self.drawsAsynchronously = YES;

    _maskLayer = [CAShapeLayer layer];
    _maskLayer.frame = self.bounds;
    _maskLayer.path = [self clippingMaskForAccuracy:0];
    self.mask = _maskLayer;

    return self;
}

- (void)updateHeadingAccuracy:(CLLocationDirection)accuracy
{
    // recalculate the clipping mask based on updated accuracy
    _maskLayer.path = [self clippingMaskForAccuracy:accuracy];
}

- (void)updateTintColor:(CGColorRef)color
{
    // redraw the raw tinted gradient
    self.contents = (__bridge id)[self gradientImageWithTintColor:color];
}

- (CGImageRef)gradientImageWithTintColor:(CGColorRef)tintColor
{
    UIImage *image;

    CGFloat haloRadius = MGLUserLocationAnnotationHaloSize / 2.0;

    UIGraphicsBeginImageContextWithOptions(CGSizeMake(MGLUserLocationAnnotationHaloSize, haloRadius), NO, 0);

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = UIGraphicsGetCurrentContext();

    // gradient from the tint color to no-alpha tint color
    CGFloat gradientLocations[] = {0.0, 1.0};
    CGGradientRef gradient = CGGradientCreateWithColors(
        colorSpace,
        (__bridge CFArrayRef)@[(__bridge id)tintColor,
                               (id)CFBridgingRelease(CGColorCreateCopyWithAlpha(tintColor, 0))],
        gradientLocations);

    // draw the gradient from the center point to the edge (full halo radius)
    CGPoint centerPoint = CGPointMake(haloRadius, haloRadius);
    CGContextDrawRadialGradient(context, gradient,
                                centerPoint, 0.0,
                                centerPoint, haloRadius,
                                kNilOptions);

    image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    CGGradientRelease(gradient);
    CGColorSpaceRelease(colorSpace);

    return image.CGImage;
}

- (CGPathRef)clippingMaskForAccuracy:(CGFloat)accuracy
{
    // size the mask using accuracy, but keep within a good display range
    CGFloat clippingDegrees = 90 - accuracy;
    clippingDegrees = fmin(clippingDegrees, 70); // most accurate
    clippingDegrees = fmax(clippingDegrees, 10); // least accurate

    CGRect ovalRect = CGRectMake(0, 0, MGLUserLocationAnnotationHaloSize, MGLUserLocationAnnotationHaloSize);
    UIBezierPath *ovalPath = UIBezierPath.bezierPath;

    // clip the oval to ± incoming accuracy degrees (converted to radians), from the top
    [ovalPath addArcWithCenter:CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect))
                        radius:CGRectGetWidth(ovalRect) / 2.0
                    startAngle:MGLRadiansFromDegrees(-180 + clippingDegrees)
                      endAngle:MGLRadiansFromDegrees(-clippingDegrees)
                     clockwise:YES];

    [ovalPath addLineToPoint:CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect))];
    [ovalPath closePath];

    return ovalPath.CGPath;
}

@end