how to customize MKPolyLineView to draw different style lines
Asked Answered
B

5

9

I want to customize the lines drawn on MKMapView to show a route so that the lines have a border color and a fill color. Similar to this where it has a black border and is filled with another color:

blue line with black border

I'm currently just returning MKPolyLineView objects from mapView:viewForOverlay: which works fine for plain lines. The docs says the MKPolyLineView is not to be subclassed, so should I subclass MKOverlayView and implement my own drawMapRect? Or should I subclass MKOverlayPathView? Or create a replacement for MKPolylineView?

EDIT - what I'm asking is: where is the place to put your own Quartz drawing code in order to draw your own annotations/overlays? Currently I've created a subclass of MKOverlayView and implement my own drawMapRect:zoomScale:inContext: It's pretty easy to draw the overlay that way but is that the best solution?

Begay answered 14/10, 2011 at 12:28 Comment(0)
M
13

You can do this by implementing your own MKOverlayPathView subclass, which draws the path twice in the map rect. Once thicker with black and once thinner on top with another colour.

I have created a simple drop-in replacement of MKPolylineView which lets you do that: ASPolylineView.

If you want to do it yourself, the two main methods that you need to implement could look like this:

- (void)drawMapRect:(MKMapRect)mapRect
          zoomScale:(MKZoomScale)zoomScale
          inContext:(CGContextRef)context
{
    UIColor *darker = [UIColor blackColor];
    CGFloat baseWidth = self.lineWidth / zoomScale;

    // draw the dark colour thicker
    CGContextAddPath(context, self.path);
    CGContextSetStrokeColorWithColor(context, darker.CGColor);
    CGContextSetLineWidth(context, baseWidth * 1.5);
    CGContextSetLineCap(context, self.lineCap);
    CGContextStrokePath(context);

    // now draw the stroke color with the regular width
    CGContextAddPath(context, self.path);
    CGContextSetStrokeColorWithColor(context, self.strokeColor.CGColor);
    CGContextSetLineWidth(context, baseWidth);
    CGContextSetLineCap(context, self.lineCap);
    CGContextStrokePath(context);

    [super drawMapRect:mapRect zoomScale:zoomScale inContext:context];
}

- (void)createPath
{
    // turn the polyline into a path

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

    for (int i = 0; i < self.polyline.pointCount; i++) {
        CGPoint point = [self pointForMapPoint:self.polyline.points[i]];

        if (pathIsEmpty) {
            CGPathMoveToPoint(path, nil, point.x, point.y);
            pathIsEmpty = NO;
        } else {
            CGPathAddLineToPoint(path, nil, point.x, point.y);
        }
    }

    self.path = path;
}
Margay answered 21/2, 2013 at 10:5 Comment(0)
S
4

You can just add two MKPolyLineView objects with the same coordinates, but different thicknesses.

Add one with a lineWidth of 10 (or whatever) with strokeColor set to black.

Then add another with a lineWidth of 6 with strokeColor set to your other desired color.

You can use the same MKPolyLine for both MKPolyLineView objects.

Socinian answered 21/10, 2011 at 1:11 Comment(3)
Hmmm, good idea, just have to make sure the wider polyline is under the thinner polyline.Begay
nice idea, maybe you can add the thinner polyline as a subview of the wider one ? as MKPolyLineView extends from UIView, this is possible in theory....Stans
You could do that, but then you'd need to offset the coordinates for the subview, which means not sharing the MKPolyLine. Better to make them both children of a parent UIView object if you want them to transform together.Socinian
N
2

MKPolylineView can only be used for stroking a designated path. You can use some of the properties in MKOverlayPathView to change their appearance but only some of them would apply, e.g. fillColor, strokeColor.

If you want to draw something more complex, you can use MKOverlayPathView. It is more generic and thus suited for more than just stroking paths. For drawing simple lines, the result would be identical to MKPolylineView (at least, according to the docs).

If you want to do more complex drawing, subclass MKOverlayPathView. What you're trying to do is non-trivial.

Nunciature answered 25/10, 2011 at 12:4 Comment(0)
H
2

I use a subclass NamedOverlay that holds an overlay an a name:

NamedOverlay.h

#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>

@interface NamedOverlay : NSObject <MKOverlay>

@property (strong, readonly, nonatomic) NSString *name;
@property (strong, readonly, nonatomic) id<MKOverlay> overlay;

-(id)initWithOverlay:(id<MKOverlay>)overlay andName:(NSString *)name;

@end

NamedOverlay.m

#import "NamedOverlay.h"

@implementation NamedOverlay

- (id)initWithOverlay:(id<MKOverlay>)overlay andName:(NSString *)name
{
    _name = name;
    _overlay = overlay;
    return self;
}

- (MKMapRect)boundingMapRect
{
    return [_overlay boundingMapRect];
}

- (CLLocationCoordinate2D)coordinate
{
    return [_overlay coordinate];
}

-(BOOL)intersectsMapRect:(MKMapRect)mapRect
{
    return [_overlay intersectsMapRect:mapRect];
}

@end

and in the map controller I instantiate two overlays with different name, then in the MKMapViewDelegate I can identify which overlay I want to draw and do something like:

- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id < MKOverlay >)overlay
{
    NamedOverlay *namedOverlay = (NamedOverlay *) overlay;
    MKPolyline *polyline = namedOverlay.overlay;
    if ([namedOverlay.name isEqualToString:@"top"]) {
        MKPolylineView *view1 = [[MKPolylineView alloc] initWithOverlay:polyline];
        view1.strokeColor = [UIColor whiteColor];
        view1.lineWidth = 25.0;
        return view1;
    } else {
        MKPolylineView *view1 = [[MKPolylineView alloc] initWithOverlay:polyline];
        view1.strokeColor = [UIColor blueColor];
        view1.lineWidth = 15.0;
        return view1;
    }
}
Headrest answered 15/3, 2014 at 8:37 Comment(0)
S
1

I know that this may not match the pure approach you want, but why not using MKPolygon instead of a MKPolyLine ?
Create a MKPolygon instance that represents a kind of corridor around your route, and then , when you create the MKPolygonView that corresponds to the MKPolygon/corridor you've created, set the properties of the MKPolygonView to get a different fill color and strokeColor

  myPolygonView.lineWidth=3;
  myPolygonView.fillColor=[UIColor blueColor];
  myPolygonView.strokeColor=[UIColor darkGrayColor];

I didn't try it myself but this should work. Only drawback is that when you zoom in / out, the 'width' of the 'route' will change.... :/

Stans answered 19/10, 2011 at 20:3 Comment(4)
nice idea but calculating the bounds of the polygon from an arbitrary set of route coordinates is pretty complexBegay
a basic approach could be to add 0.000001 to the latitude for the upper part of the corridor, then -0.000001 for the lower part, and then build a corridor with a polygon composed of upper and lower parts ... just a idea...Stans
That would only work if the route ran in an east/west direction. These can run in any direction so the polygon would need to be based on the heading of each line segment. The side would need to be at right angles to the route heading and computing the heading from the lat/lon pair using the Spherical Law of Cosines is an expensive operation.Begay
In GIS, this can be achieved using ST_BUFFER postgis.org/docs/ST_Buffer.html . If you have spatialite on your iPhone, you can also calculate it with 1 invocation : gaia-gis.it/spatialite-2.4.0-4/spatialite-sql-2.4-4.html . But I agree, this is an expensive operation.Stans

© 2022 - 2024 — McMap. All rights reserved.