Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
377 views
in Technique[技术] by (71.8m points)

mkmapview - Gradient Polyline with MapKit ios

I'm trying to trace a route on a MKMapView using overlays (MKOverlay). However, depending on the current speed, I want to do something like the Nike app with a gradient while tracing the route, if the color is changing (for example, from green to orange if a user is driving from 65mph to 30mph).

Here's a screenshot of what I want:

gradient overlay image

So every 20 meters, I adding an overlay from the old to the new coordinates using:

// Create a c array of points. 
MKMapPoint *pointsArray = malloc(sizeof(CLLocationCoordinate2D) * 2);

// Create 2 points.
MKMapPoint startPoint = MKMapPointForCoordinate(CLLocationCoordinate2DMake(oldLatitude, oldLongitude));
MKMapPoint endPoint = MKMapPointForCoordinate(CLLocationCoordinate2DMake(newLatitude, newLongitude));

// Fill the array.
pointsArray[0] = startPoint;
pointsArray[1] = endPoint;

// Erase polyline and polyline view if not nil.
if (self.routeLine != nil)
    self.routeLine = nil;

if (self.routeLineView != nil)
    self.routeLineView = nil;

// Create the polyline based on the array of points.
self.routeLine = [MKPolyline polylineWithPoints:pointsArray count:2];

// Add overlay to map.
[self.mapView addOverlay:self.routeLine];

// clear the memory allocated earlier for the points.
free(pointsArray);

// Save old coordinates.
oldLatitude = newLatitude;
oldLongitude = newLongitude;

Basically I'm adding a lot of little overlays. Then I would like to create the gradient on this little line drawing, so I'm trying to do so in the overlay delegate:

- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay {
    MKOverlayView* overlayView = nil;

    if(overlay == self.routeLine) {
        // If we have not yet created an overlay view for this overlay, create it now. 
        if(self.routeLineView == nil) {
            self.routeLineView = [[[MKPolylineView alloc] initWithPolyline:self.routeLine] autorelease];

            if (speedMPH < 25.0) {
                self.routeLineView.fillColor = [UIColor redColor];
                self.routeLineView.strokeColor = [UIColor redColor];
            }
            else if (speedMPH >= 25.0 && speedMPH < 50.0) {
                self.routeLineView.fillColor = [UIColor orangeColor];
                self.routeLineView.strokeColor = [UIColor orangeColor];
            }
            else {
                self.routeLineView.fillColor = [UIColor greenColor];
                self.routeLineView.strokeColor = [UIColor greenColor];
            }

            // Size of the trace.
            self.routeLineView.lineWidth = routeLineWidth;

            // Add gradient if color changed.
            if (oldColor != self.routeLineView.fillColor) {
                CAGradientLayer *gradient = [CAGradientLayer layer];
                    gradient.frame = self.routeLineView.bounds;
                    gradient.colors = [NSArray arrayWithObjects:(id)[oldColor CGColor], (id)[self.routeLineView.fillColor CGColor], nil];
                    [self.routeLineView.layer insertSublayer:gradient atIndex:0];
            }

            // Record old color for gradient.
            if (speedMPH < 25.0)
                oldColor = [UIColor redColor];
            else if (speedMPH >= 25.0 && speedMPH < 50.0)
                oldColor = [UIColor orangeColor];
            else
                oldColor = [UIColor greenColor];
        }

        overlayView = self.routeLineView;
    }

    return overlayView;
}

I'm trying to add the gradient this way, but I guess it is not the way to do it because I can't make it to work.

I can also trace the route every time there is an update on the user's location (in the location object's delegate), or as above every 20 meters.

Can you please help me on that one, giving me tips! Thanks!

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

One of the idea I came up is to create a CGPath and stroke it with gradient every time when drawMapRect method been called, since the MKPolylineView is replaced by MKPlolylineRenderer in ios7.

I tried to implement this by subclassing a MKOverlayPathRenderer but I failed to pick out individual CGPath, then I find a mysterious method named-(void) strokePath:(CGPathRef)path inContext:(CGContextRef)context which sounds like what I need, but it will not be called if you don't call the super method when you override your drawMapRect.

thats what Im working out for now.

I'll keep trying so if I work out something I'll come back and update the answer.

=========UPDATE================================================

enter image description here

So that is what I'm worked out these days, I almost implemented the basic idea mentioned above but yes, I still cannot pick out an individual PATH according to specific mapRect, so I just draw all paths with gradient at the same time when the boundingBox of all paths intersects with current mapRect. poor trick, but work for now.

In the -(void) drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context method in render class, I do this:

CGMutablePathRef fullPath = CGPathCreateMutable();
BOOL pathIsEmpty = YES;

//merging all the points as entire path
for (int i=0;i< polyline.pointCount;i++){
    CGPoint point = [self pointForMapPoint:polyline.points[i]];
    if (pathIsEmpty){
        CGPathMoveToPoint(fullPath, nil, point.x, point.y);
        pathIsEmpty = NO;
    } else {
        CGPathAddLineToPoint(fullPath, nil, point.x, point.y);
    }
}

//get bounding box out of entire path.
CGRect pointsRect = CGPathGetBoundingBox(fullPath);
CGRect mapRectCG = [self rectForMapRect:mapRect];
//stop any drawing logic, cuz there is no path in current rect.
if (!CGRectIntersectsRect(pointsRect, mapRectCG))return;

Then I split the entire path point by point to draw its gradient individually. note that the hues array containing hue value mapping each velocity of location.

for (int i=0;i< polyline.pointCount;i++){
    CGMutablePathRef path = CGPathCreateMutable();
    CGPoint point = [self pointForMapPoint:polyline.points[i]];
    ccolor = [UIColor colorWithHue:hues[i] saturation:1.0f brightness:1.0f alpha:1.0f];
    if (i==0){
        CGPathMoveToPoint(path, nil, point.x, point.y);
    } else {
        CGPoint prevPoint = [self pointForMapPoint:polyline.points[i-1]];
        CGPathMoveToPoint(path, nil, prevPoint.x, prevPoint.y);
        CGPathAddLineToPoint(path, nil, point.x, point.y);
        CGFloat pc_r,pc_g,pc_b,pc_a,
        cc_r,cc_g,cc_b,cc_a;
        [pcolor getRed:&pc_r green:&pc_g blue:&pc_b alpha:&pc_a];
        [ccolor getRed:&cc_r green:&cc_g blue:&cc_b alpha:&cc_a];
        CGFloat gradientColors[8] = {pc_r,pc_g,pc_b,pc_a,
                                    cc_r,cc_g,cc_b,cc_a};

        CGFloat gradientLocation[2] = {0,1};
        CGContextSaveGState(context);
        CGFloat lineWidth = CGContextConvertSizeToUserSpace(context, (CGSize){self.lineWidth,self.lineWidth}).width;
        CGPathRef pathToFill = CGPathCreateCopyByStrokingPath(path, NULL, lineWidth, self.lineCap, self.lineJoin, self.miterLimit);
        CGContextAddPath(context, pathToFill);
        CGContextClip(context);//<--clip your context after you SAVE it, important!
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradientColors, gradientLocation, 2);
        CGColorSpaceRelease(colorSpace);
        CGPoint gradientStart = prevPoint;
        CGPoint gradientEnd = point;
        CGContextDrawLinearGradient(context, gradient, gradientStart, gradientEnd, kCGGradientDrawsAfterEndLocation);
        CGGradientRelease(gradient);
        CGContextRestoreGState(context);//<--Don't forget to restore your context.
    }
    pcolor = [UIColor colorWithCGColor:ccolor.CGColor];
}

That is all the core drawing method and of course you need points, velocity in your overlay class and feed them with CLLocationManager.

the last point is how to get hue value out of velocity, well, I found that if hue ranging from 0.03~0.3 is exactly represent from red to green, so I do some proportionally mapping to hue and velocity.

last of the last, here you are this is full source of this demo:https://github.com/wdanxna/GradientPolyline

don't panic if can't see the line you draw, I just position the map region on my position :)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...