Skip to content

Drawing polyines or routes on a MKMapView (Using Map Kit on the iPhone)

Apple recently released the 3.0 Beta of their iPhone SDK. One of the most exciting new items in this SDK for me was the addition of the MapKit framework. This new mapping component would allow developers to add maps to their applications that have similar performance and functionality to the Google Maps application that ships with the iPhone.

Unfortunately, there are a few useful pieces missing from the new map SDK; the most glaring of which (to me) is the built in ability to draw routes on a map. This is easily solved though by placing a custom UIView over the map that acts as the map delegate, and knows how to take a series of CLLocation coordinates and plot them on the map, regardless of the location the user pans to or how far they have zoomed in.

I have included the code of this custom class below, as well as some tips on how to use it. There are two things to keep in mind though when looking at this code:

  1. It is in no way optimized, meaning the bulk of the drawRect functionality executes, regardless if the polyline being rendered is completely off screen.
  2. The Map Kit SDK is still in Beta, and is subject to change. This sample was written targeting the second beta of the 3.0 iPhone SDK. I do not anticipate huge changes in the SDK, but future releases may break the code listed below, or (hopefully) make the code below completely uneccessary.

Going through the code, you can see when this new CSMapRouteLayerView is initialized with an array of CLLocations and a MapView, the view adds itself as the subview to the MKMapView and registers as its delegate. It then, based on the points that were passed in, uses the map to determine the region that would result in the full path of the route’s line being displayed. The map is then zoomed to this region that contains the while route.

The drawRect functionality is pretty straightforward; for every geographic point in the array, it uses the map to determine the pixel coordinates of that point, and then draws a line to that point from the previous point (in the case of the first point, it just moves the pen to that location and nothing is drawn till the next point).

One downside to this approach, and you can see this in the MKMapViewDelegate handlers in the route layer view, is that when the user decides to scroll or zoom the map, we must temporarily disable the display of the route. This is because during the transition, the lines will appear to be rendered at the wrong location. As soon as the region is done changing, we can bring our polyline back onto the map.

The sample data used for this project is a CSV file with some Latitude/Longitude pairs. The applications main view controller does the work of opening up this file, parsing out the points, and sending it to the initialization of our route layer view. I have not copied the code for this below, but you can see it if you download the sample project.

I have an updated post here that builds on this project and also discusses using Using MKAnnotation, MKPinAnnotationView and creating a custom MKAnnotationView in an MKMapView.

8-19-2009: Another update. This one displays the route as an annotation view, so it does not render on top of other annotations. You can view the update at this link.

Here’s the important code… you can download the sample project here: mapLines Sample Project

//
// CSMapRouteLayerView.h
// mapLines
#import
#import

@interface CSMapRouteLayerView : UIView
{
MKMapView* _mapView;
NSArray* _points;
UIColor* _lineColor;
}

-(id) initWithRoute:(NSArray*)routePoints mapView:(MKMapView*)mapView;

@property (nonatomic, retain) NSArray* points;
@property (nonatomic, retain) MKMapView* mapView;
@property (nonatomic, retain) UIColor* lineColor;

@end

//
// CSMapRouteLayerView.m
// mapLines
#import "CSMapRouteLayerView.h"

@implementation CSMapRouteLayerView
@synthesize mapView = _mapView;
@synthesize points = _points;
@synthesize lineColor = _lineColor;

-(id) initWithRoute:(NSArray*)routePoints mapView:(MKMapView*)mapView
{
self = [super initWithFrame:CGRectMake(0, 0, mapView.frame.size.width, mapView.frame.size.height)];
[self setBackgroundColor:[UIColor clearColor]];

[self setMapView:mapView];
[self setPoints:routePoints];

// determine the extents of the trip points that were passed in, and zoom in to that area.
CLLocationDegrees maxLat = -90;
CLLocationDegrees maxLon = -180;
CLLocationDegrees minLat = 90;
CLLocationDegrees minLon = 180;

for(int idx = 0; idx < self.points.count; idx++)
{
CLLocation* currentLocation = [self.points objectAtIndex:idx];
if(currentLocation.coordinate.latitude > maxLat)
maxLat = currentLocation.coordinate.latitude;
if(currentLocation.coordinate.latitude < minLat)
minLat = currentLocation.coordinate.latitude;
if(currentLocation.coordinate.longitude > maxLon)
maxLon = currentLocation.coordinate.longitude;
if(currentLocation.coordinate.longitude < minLon)
minLon = currentLocation.coordinate.longitude;
}

MKCoordinateRegion region;
region.center.latitude = (maxLat + minLat) / 2;
region.center.longitude = (maxLon + minLon) / 2;
region.span.latitudeDelta = maxLat - minLat;
region.span.longitudeDelta = maxLon - minLon;

[self.mapView setRegion:region];
[self.mapView setDelegate:self];
[self.mapView addSubview:self];

return self;
}

- (void)drawRect:(CGRect)rect
{
// only draw our lines if we're not int he moddie of a transition and we
// acutally have some points to draw.
if(!self.hidden && nil != self.points && self.points.count > 0)
{
CGContextRef context = UIGraphicsGetCurrentContext();

if(nil == self.lineColor)
self.lineColor = [UIColor blueColor];

CGContextSetStrokeColorWithColor(context, self.lineColor.CGColor);
CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0);

// Draw them with a 2.0 stroke width so they are a bit more visible.
CGContextSetLineWidth(context, 2.0);

for(int idx = 0; idx < self.points.count; idx++)
{
CLLocation* location = [self.points objectAtIndex:idx];
CGPoint point = [_mapView convertCoordinate:location.coordinate toPointToView:self];

if(idx == 0)
{
// move to the first point
CGContextMoveToPoint(context, point.x, point.y);
}
else
{
CGContextAddLineToPoint(context, point.x, point.y);
}
}

CGContextStrokePath(context);
}
}

#pragma mark mapView delegate functions
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
{
// turn off the view of the route as the map is chaning regions. This prevents
// the line from being displayed at an incorrect positoin on the map during the
// transition.
self.hidden = YES;
}
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
// re-enable and re-poosition the route display.
self.hidden = NO;
[self setNeedsDisplay];
}

-(void) dealloc
{
[_points release];
[_mapView release];
[super dealloc];
}

@end

{ 12 } Comments

  1. Aya | February 11, 2011 at 11:49 am | Permalink

    Thanks for your great work, this is very useful example (y)

  2. sudesh kumar | February 16, 2011 at 12:35 am | Permalink

    I have a problem ,after adding route on map view in ios 4.2 ,disable map movement with finger touch.
    Plz help me …thanks in advance.

  3. raji.nair | March 15, 2011 at 3:54 am | Permalink

    i am having a question that when i start my map my current position should be recorded and from their the direction in which i am travelling should get displayed on the map through routes lines. How is this possible.

  4. Rajasekhar Veernapu | September 13, 2011 at 8:06 am | Permalink

    Awesome. I like it , And is there any way to get the coordinates between my current location and the location i search?

  5. Johny | September 19, 2011 at 10:05 am | Permalink

    Great work.
    I have used this code with little modifications to draw outline of a country and also shade that country.
    it works fine but SOMETIMES the app shows some strange behavior. That is when i move the region or zoom the map the shaded region will not become to its correct place.
    Why it happens any idea

  6. F. Bartolomucci | October 6, 2011 at 11:03 am | Permalink

    Unfortunately both a compilation of your program and an embedding of your classes in my project produced a map that could not be nudged. In some way the regionWillChange event does not get through to moving the map.

  7. xcode noob | October 27, 2011 at 7:02 am | Permalink

    Hi,

    Firstly , thanx for the post, it is indeed very useful.

    I can’t seem to pan on the map, how do i enable that again?
    I can’t seem to do that when it is running in the simulator, am i missing something?

  8. amit kumar shukla | December 2, 2011 at 7:41 am | Permalink

    hii, very good example ever yet, but it is static drawing can you give me example for dynamic line drawing.

  9. Amit kumar | January 23, 2012 at 5:06 am | Permalink

    Thanks for this usefull code

  10. Prasad Pamidi | April 19, 2012 at 10:08 am | Permalink

    Hiii…

    You did great Job!!!!

    I used your implementation into your project. But my app is getting freezed abnormally. It is taking more time to update the maps. Can you let me know is there anyway to solve.

    Thanks in Advance..

  11. 8hast4nbtkt | July 3, 2012 at 9:44 pm | Permalink

    So i’m pleased for everyone due to this fact reliable information. You really did make the evening :

  12. anilmcmt | September 27, 2014 at 1:56 am | Permalink

    Thanks for the sample code.

{ 3 } Trackbacks

  1. […] http://spitzkoff.com/craig/?p=65 […]

  2. […] 1>Draw route using mapkit […]

  3. […] trying to add a routing feature to an app I’m working on. I found Craig Spitzkoff’s article on how to draw lines on an MKMapView which works pretty good. But since I don’t have the […]

Post a Comment

Your email is never published nor shared. Required fields are marked *