joeltrew / GradientPathRenderer

Renders MKPolyline with a fancy multicoloured gradient fill
MIT License
143 stars 13 forks source link

Color depends of altitude #1

Closed NasH5169 closed 8 years ago

NasH5169 commented 8 years ago

Hi,

First, thanks for this awesome class to customize a MKPolyline.

I'm wondering if this is possible to add a gradient which depends of the altitude. Each points (lat/lng) of my polyline have an altitude value. The min altitude represents green color, the max altitude red color.

I don't find any information to do it. An idea ?

A rendering sample made with Google Map JS API sample

Thanks again.

joeltrew commented 8 years ago

Hi there! Thanks, it's so cool to see people using something I've made! I'm actually doing the exact thing you ask in one of my apps so I can help you on this one. What you want to do is calculate a hue value for each point based on your elevation data, and create a gradient between the previous point's colour and the current point's colour in the drawMapRect function, for each part of the path.

Here's the class I made so you can see how I've done it. I might add it to this repo if it's something lots of people want.

 //
 //  PedalPathRenderer.swift
 //  Pedal
 //
 //  Created by Joel Trew on 24/04/2016.
 //

 import MapKit

 class PedalPathElevationRenderer: MKOverlayPathRenderer {

    var polyline : MKPolyline
    var elevations: [Int]
    var maxElevation: Int?
    var border: Bool = false
    var borderColor: UIColor?

    //MARK: Initializers
    init(polyline: MKPolyline, elevations: [Int]) {
        self.polyline = polyline
        self.elevations = elevations
        super.init(overlay: polyline)
        self.maxElevation = calculateMaxElevation()
    }

    //MARK: Override methods
    override func drawMapRect(mapRect: MKMapRect, zoomScale: MKZoomScale, inContext context: CGContext) {

        var baseWidth: CGFloat
        if zoomScale > 0.1 {
            baseWidth = self.lineWidth * 2
        } else {
            baseWidth = self.lineWidth / zoomScale
        }

        /*
         Set path width relative to map zoom scale
         */

        var firstColor:UIColor = UIColor(hue: self.getHueFor(elevation: elevations[0]), saturation: 1, brightness: 1, alpha: 1)
        var lastColor: UIColor = UIColor(hue: self.getHueFor(elevation: elevations[1]), saturation: 1, brightness: 1, alpha: 1)

        if self.border {
            CGContextSetLineWidth(context, baseWidth * 1.5)
            CGContextSetLineJoin(context, CGLineJoin.Round)
            CGContextSetLineCap(context, CGLineCap.Round)
            CGContextAddPath(context, self.path)
            CGContextSetStrokeColorWithColor(context, self.borderColor?.CGColor ?? UIColor.whiteColor().CGColor)
            CGContextStrokePath(context)
        }

        for i in 0...self.polyline.pointCount-1 {
            let point: CGPoint = pointForMapPoint(self.polyline.points()[i])
            let path: CGMutablePathRef  = CGPathCreateMutable()

            lastColor = UIColor(hue: self.getHueFor(elevation: elevations[i]), saturation: 1, brightness: 1, alpha: 1)
            if i==0 {
                CGPathMoveToPoint(path, nil, point.x, point.y)
            } else {
                let previousPoint = self.pointForMapPoint(polyline.points()[i-1])
                CGPathMoveToPoint(path, nil, previousPoint.x, previousPoint.y)
                CGPathAddLineToPoint(path, nil, point.x, point.y)

                CGContextSaveGState(context);
                let colorspace = CGColorSpaceCreateDeviceRGB()
                let locations: [CGFloat] = [0,1]
                let gradient = CGGradientCreateWithColors(colorspace, [firstColor.CGColor, lastColor.CGColor], locations)

                let pathToFill = CGPathCreateCopyByStrokingPath(path, nil, baseWidth, CGLineCap.Round, CGLineJoin.Round, self.miterLimit)
                CGContextAddPath(context, pathToFill)
                CGContextClip(context);

                let gradientStart = previousPoint;
                let gradientEnd = point;

                CGContextDrawLinearGradient(context, gradient, gradientStart, gradientEnd, CGGradientDrawingOptions.DrawsBeforeStartLocation);
                CGContextRestoreGState(context)
            }
            firstColor = UIColor(CGColor: lastColor.CGColor)
        }

        super.drawMapRect(mapRect, zoomScale: zoomScale, inContext: context)
    }

    /*
     Create path from polyline
     Thanks to Adrian Schoenig
     (http://adrian.schoenig.me/blog/2013/02/21/drawing-multi-coloured-lines-on-an-mkmapview/ )
     */
    override func createPath() {
        let path: CGMutablePathRef  = CGPathCreateMutable()
        var pathIsEmpty: Bool = true

        for i in 0...self.polyline.pointCount-1 {

            let point: CGPoint = pointForMapPoint(self.polyline.points()[i])
            if pathIsEmpty {
                CGPathMoveToPoint(path, nil, point.x, point.y)
                pathIsEmpty = false
            } else {
                CGPathAddLineToPoint(path, nil, point.x, point.y)
            }
        }
        self.path = path
    }

    private func getHueFor(elevation elevation: Int) -> CGFloat {
        let maxElevation = CGFloat(self.maxElevation ?? 50)
        let hue = CGFloat(elevation).map(from: 0...maxElevation+5, to: 0.0...0.4)
        return 0.4-hue

    }

    private func calculateMaxElevation() -> Int {
        return self.elevations.maxElement() ?? 0
    }

 }

 extension CGFloat {
    func map(from from: ClosedInterval<CGFloat>, to: ClosedInterval<CGFloat>) -> CGFloat {
        let result = ((self - from.start) / (from.end - from.start)) * (to.end - to.start) + to.start
        return result
    }
 }

Hopefully this will help you out with your issue. 😄

NasH5169 commented 8 years ago

Thanks for your answer. I did a similar coding. It's working but each drawMapRect call will create multiples CGPath, the map reactivity slow down, the memory increase a lot and on iPad 2 (512 MB), some memory warning come fast. I have some GPX traces with a lot of points (~1000), so for now, i'm searching for a lighter solution.

Thanks again and happy coding :)

joeltrew commented 8 years ago

Ok good luck :) , you're welcome to share your solution here if you find a better way

wangjinyu commented 7 years ago

@NasH5169 What's the solution, can you share it to me. Thanks