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

{ 51 } Comments

  1. Dirk | April 16, 2009 at 12:39 pm | Permalink

    Great! Thanks for this useful example. This saved my project :) Dirk

  2. Michael | April 22, 2009 at 7:07 am | Permalink

    Nice work, was thinking about doing this myself – glad to see someone got there before me :)

  3. S.LakshmiKanth Reddy | April 27, 2009 at 12:06 am | Permalink

    very good example , thank u thanks a lot , this has given me good understanding over maps api.

  4. RAMEL SINGH RANA | April 30, 2009 at 1:22 am | Permalink

    Thanks for such a good maps overview.

  5. Popeto | May 10, 2009 at 10:33 am | Permalink

    Excellent, thank you. Question: is there a way to use core location or google or some other api to provide two points on the map and get the resulting set of latitude/longitude pairs for the route?

  6. admin | May 11, 2009 at 3:17 am | Permalink

    Thanks Popeto. What I think you’re trying to do is pretty straightforward, if you intend to track a user’s current position, and draw the route representing their track.

    What you’d need to do is register your class as the delegate with the CLLocationManager and make sure your class implements the CLLocationManagerDelegate functions. Whenever the location manager calls your function locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation you can use the newLocation.coordinate.latitude and longitude field. Given the example code attached to this post, you could add these new coordinates to the array of points, and tell the map view to refresh by calling setNeedsDisplay.

    I hoep this helps. Let me know if you need a more concrete example.

  7. Lee Armstrong | May 19, 2009 at 1:24 pm | Permalink

    Great article, works a treat!

    I use a custom UIImage instead of the pins, when I use the code it changes them back to pushpins…. any pointers here?

  8. Lee Armstrong | May 19, 2009 at 1:36 pm | Permalink

    Also I get a funky issue when using the code above to find the bounds and then carry out the SetRegion. The map zooms but it has the Google grey grid view and doesn’t load the map up!

    I have logged a bug with Apple….

  9. admin | May 19, 2009 at 1:51 pm | Permalink

    Hi Lee,

    I go over using custom views instead of the pins in this newer article: http://www.arlingtondev.com/thoughts/?p=81

    Let me know if you’re still having issues after reading through that sample. Thanks!

  10. admin | May 19, 2009 at 1:52 pm | Permalink

    Are you sure your device or simulator has network connectivity when you are seeing the grey grid view? Do you see the route line rendered on the grid view? Can you access the same maps in the actual google maps application on your device?

  11. Rajkumar | May 20, 2009 at 8:46 am | Permalink

    This example is very helpful for me to understand the concepts of Map..thanks a lot..

  12. Lee Armstrong | May 20, 2009 at 10:45 am | Permalink

    Yes the maps load fine if I scroll away and then back so network connectivity is there!

  13. sujith krishnan | May 30, 2009 at 1:53 am | Permalink

    Hey..
    This is a wonderful work by you. But i expected Map Kit will come with something to draw poly lines. However you did it.

    One question. I found you converting coordinate to pint and drawing lines through that point. Good. But i want to do the other way. I now convertPoint to Coordinate method is there with mapkit. I wrote the touchBegan: event method in “CSMapRouteLayerView” class , but touch is not firing teh method. I want to show user the place when he touch any particular point on the blue path.

    Any idea?

  14. Sean Christmann | June 9, 2009 at 5:10 pm | Permalink

    Nice clean implementation. I think there has to be a better way to avoid coordinate recalculations though when scrolling around the map, coordinate lookups should only be necessary when changing zoom levels.

  15. jade | June 11, 2009 at 8:54 am | Permalink

    Thanks for the sample code. I have the same question as Popeto (I think you misunderstood his question):
    Given 2 locations, e.g. train station and hotel, are you aware of a method to retrieve the intermediate points between them, using 3.0 MapKit or Google Map API? It’s for route guidance application on iPhone.

  16. Ali | June 11, 2009 at 7:36 pm | Permalink

    Very nice! I really appreciate it mate :D Is it possible to draw an arc between two points on a map ? Let’s say for example a geodesic arc ?

  17. Joe | July 7, 2009 at 3:40 pm | Permalink

    I have the same problem as Lee. All I have to do is nudge the map just a tiny bit in any direction and the map panels load just fine. I tried this:

    [mapView setRegion:region animated:YES];
    [mapView regionThatFits:region];

    … and this (both with an appropriate location and, in the case of the region, a span with a delta of 0.2 x 0.2):

    [mapView setCenterCoordinate:location animated:YES];

    No dice. The animation works just fine, and my pin annotation drops in with no trouble, but you don’t see any actual “map” content until you go and nudge the map to any one side. :\

  18. Greg Crisp | July 16, 2009 at 1:00 pm | Permalink

    Thanks for this. It filled in the missing piece I needed to fix a similar solution I was working on.

    One variation is to use the fast iterator for looping over the points, which is functionally equivalent. Also, I’m pinning the annotations on the map view in the same loop:


    // AnnotationDoohicky implements MKAnnotation protocol
    for (AnnotationDoohicky *anAnnotation in annotationDelegateList)
    {
    [theMapView addAnnotation:anAnnotation];
    if( anAnnotation.coordinate.latitude maxLat ) maxLat = anAnnotation.coordinate.latitude;
    if( anAnnotation.coordinate.longitude maxLong ) maxLong = anAnnotation.coordinate.longitude;
    }

  19. Greg Crisp | July 16, 2009 at 1:00 pm | Permalink

    // AnnotationDoohicky implements MKAnnotation protocol
    for (AnnotationDoohicky *anAnnotation in annotationDelegateList)
    {
    [theMapView addAnnotation:anAnnotation];
    if( anAnnotation.coordinate.latitude maxLat ) maxLat = anAnnotation.coordinate.latitude;
    if( anAnnotation.coordinate.longitude maxLong ) maxLong = anAnnotation.coordinate.longitude;
    }

  20. Greg Crisp | July 16, 2009 at 1:01 pm | Permalink

    Code in above comments seems to have been truncated?

  21. jassous | July 23, 2009 at 1:17 am | Permalink

    thanks for this example.
    my application will determine the distance between 2 pin that the user will putted. i put a pin on the map but i can move it ,i try pin.userintersectionenabled=yes + -(void)toucheBegan: but it’s not working can you help me?please?

  22. andrew | July 23, 2009 at 5:34 pm | Permalink

    awesome post, but I have a quick question. I am trying to duplicate this method in my own program, but I can’t get the drawRect method called. Can you tell me what exactly is calling this method?

  23. Jason | July 31, 2009 at 3:51 pm | Permalink

    I did exactly the same thing for something that I’m working one, but I was curious, did you run into an issue where buttons on the annotation callouts failed to trigger their actions when the overlay is present as a subview of the map?

  24. krish | August 4, 2009 at 11:09 pm | Permalink

    Hats off. This is great way of getting around non availability of overlay and polylines in mapkit. Thanks

  25. Thony | August 18, 2009 at 4:52 pm | Permalink

    Hi!

    Thanks for this example!

    I also have a question:

    I have generated a PNG file that contain some lines, how can I superposed the PNG image on google map at the good coordinates/zoom?

  26. admin | August 20, 2009 at 8:40 am | Permalink

    For anyone who was interested in drawing the lines under the other annotations on a map, there is an update to this example here:

    http://spitzkoff.com/craig/?p=108

    This update will draw the lines in an annotation view instead of as an overlaid view on the map. Its somewhat more complicated due to the way a map view will move the annotation view offscreen and the size of the route actually being larger than the view itself, but I think I’ve gotten around that using a non-clipped subview of the annotation view that always stays at 0,0 relative to the map view.

  27. Peignoir | August 31, 2009 at 9:49 pm | Permalink

    Thanks man you really helped me!

  28. Justin | September 8, 2009 at 10:40 am | Permalink

    Joe and Lee:

    The nudge-to-load issue may be caused by calling setRegion in the background. Moving it into the main thread fixed the problem for me.

  29. joandp | September 20, 2009 at 5:11 pm | Permalink

    I’m the lack loadFromWaypoints GDirections’s method in Javascript for example is almost IMPOSSIBLE to get here since, what it does is get the route based on the street address, and most shortest path the to destiny point. What you are doing here is just draw points, without taking into account, for example street orientation. Anyone have ever try this problem with Maps on iPhoneSDK ?

  30. Paul | October 18, 2009 at 12:28 pm | Permalink

    I see you are using the method CGPoint point = [_mapView convertCoordinate:location.coordinate toPointToView:self]; which I am trying to use to plot the mapview coordinate of a pin onto the camera overlay view.

    I get no errors and the code runs but no view appears, any ideas?

  31. Barry O'D | November 20, 2009 at 7:46 am | Permalink

    Hey, thanks for this great example you got, just one question I have is there any way of implimenting various locations into the code whereby you can choose various locations to go to.
    Thanks again!
    ありがとう

  32. Jompe71 | December 4, 2009 at 2:31 am | Permalink

    I made a Google directions wrapper around their REST API to plot the route between two locations in a MKMapView.
    Thus the coordinates returned from google’s API are only connected to the Steps ( turns) in the response route.
    These coords cannot be used for plotting since the route will not be as smooth as one will expect and doesn’t “follow” the actual roads.
    Are more accurate coords supplied somehow from their API or only available to Google themselfs.
    Anyone know ?

  33. johnny | January 9, 2010 at 5:58 pm | Permalink

    Thanks for your great work.
    Do you have any experience on appstore approval? :)

    Thanks a lot.

  34. FMulder | February 3, 2010 at 7:19 am | Permalink

    Hi, thanks a lot for this code.
    I have a question… I’m writing an app that needs a costant update of the overlaying track.
    I thought about two methods that doesn’t need an edit of your code.
    1) Right now, I get this working by removing the annotation, then calculating it again with the new path points and then adding it again to the mapview. But it gets laggy when there are many points and I’m moving the map.
    2) If I add an annotation for every track segment many routes are not showed correctly, and I also think that it would be a waste of memory.

    What do you think about it? do you have other (better) solutions like this or do you think it is better to edit your code for my purpose?

    thank you so much

  35. Myke | March 9, 2010 at 10:35 am | Permalink

    Thanks for this. I was trying to get this to work for a week. I can’t believe that something that is used so often in apps is not well documented in on apple’s site.

  36. Gilad | March 9, 2010 at 10:54 am | Permalink

    Very nice. Thanks for your effort.

  37. Michael | April 13, 2010 at 4:05 pm | Permalink

    Hi Craig

    How did you manage to have the route itself in the background? I face the problem that some “annotation-pupops” (where the title is in) is in the layer below and has the route being drawn through the text…

    Thanks for hints&help!
    Michael

  38. hitesh vaghela | January 12, 2011 at 8:52 am | Permalink

    Thank you for your great effort.

  39. cracked iphone | January 22, 2011 at 4:19 pm | Permalink

    I want to ask, do you see the route line rendered on the grid view? Can you access the same maps in the actual google maps application on your device?

  40. sohail | February 3, 2011 at 12:41 pm | Permalink

    Hi,

    This was really a great example, helped alot in my project.
    But now I have an issue, I dont see the lines in iOs4.2 .
    Can you Please advice ?

    Regards,
    Sohail

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

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

  42. 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.

  43. 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.

  44. 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?

  45. 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

  46. 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.

  47. 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?

  48. 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.

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

    Thanks for this usefull code

  50. 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..

  51. 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 :

{ 13 } Trackbacks

  1. [...] My last experiment with the MKMapKit was an example of how to use the MKMapView to display the line of a route on the map. [...]

  2. [...] programming: Overlay lines on map By ricafeal This is an example that shows a route (several points connected) on top of a map. I downloaded the XCode [...]

  3. [...] My last experiment maps on the iPhone using the MKMapView from the iPhone’s MapKit was an example of how to use the MKMapView to display the line of a route on the map. [...]

  4. [...] a little bit of more googleing I found this site of Craig who actually proposed the some approach but had posted an update where he suggested to draw the [...]

  5. [...] their own homebrew polyline classes to plug into MapKit. In particular, Craig Spitzkoff wrote up a document describing a method of displaying a polyline on the map using an annotation, albeit not updated in [...]

  6. [...] chance of facing a nice post solved my second problem. Thanks goes to Craig Spitzkoff as publishing this post which draws a bunch of location data onto MKMapView. I worked a bit to port the code as my needs [...]

  7. [...] Drawing polyines or routes on a MKMapView (Using Map Kit on the iPhone) Posted by admin iphone资讯 Subscribe to RSS feed  [...]

  8. [...] chance of facing a nice post solved my second problem. Thanks goes to Craig Spitzkoff as publishing this post which draws a bunch of location data onto MKMapView. I worked a bit to port the code as my needs [...]

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

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

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

  12. [...] 1>Draw route using mapkit [...]

  13. [...] 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 *