Drawing polyines or routes on a MKMapView in iOS4 – Part 3

July 4th, 2010

Get right to it: here’s the sample code

With the release of iOS 4, Apple has drastically decreased the complexity involved in rendering shapes, including routes, on a map. In fact, they’ve added an entire set of classes to MapKit specifically designed to help you add shapes to a map. These new classes and protocols are:
MKCircle
MKCircleView
MKMultiPoint
MKOverlayPathView
MKPolygon
MKPolygonView
MKPolyline
MKPolylineView
MKShape
MKOverlay

These classes make it incredibly easy to do what was once complex (And slightly buggy). Before when you would scroll or zoom in on a map with a route rendered in an MKAnnotationView, the route would have to temporarily be hidden because it was not scaled or scrolled along with with map. the MapKit framework now handles all this automatically.

So how do you add a route to a map using the updated MKMapView in iOS 4?

  1. Create a MKPolyline
  2. Add the polyline (as a MKOverlay) to the map
  3. Implement mapView:viewForOverlay: in your MKMapViewDelegate
  4. Initialize, set values on, and return a MKPolylineView from your updated map view delegate
  5. Here’s some sample code that shows how you might create your MKPolyline:

    -(void) loadRoute
    {
    NSString* filePath = [[NSBundle mainBundle] pathForResource:@”route” ofType:@”csv”];
    NSString* fileContents = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    NSArray* pointStrings = [fileContents componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];

    // while we create the route points, we will also be calculating the bounding box of our route
    // so we can easily zoom in on it.
    MKMapPoint northEastPoint;
    MKMapPoint southWestPoint;

    // create a c array of points.
    MKMapPoint* pointArr = malloc(sizeof(CLLocationCoordinate2D) * pointStrings.count);

    for(int idx = 0; idx < pointStrings.count; idx++)
    {
    // break the string down even further to latitude and longitude fields.
    NSString* currentPointString = [pointStrings objectAtIndex:idx];
    NSArray* latLonArr = [currentPointString componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@","]];

    CLLocationDegrees latitude = [[latLonArr objectAtIndex:0] doubleValue];
    CLLocationDegrees longitude = [[latLonArr objectAtIndex:1] doubleValue];

    // create our coordinate and add it to the correct spot in the array
    CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(latitude, longitude);

    MKMapPoint point = MKMapPointForCoordinate(coordinate);

    //
    // adjust the bounding box
    //

    // if it is the first point, just use them, since we have nothing to compare to yet.
    if (idx == 0) {
    northEastPoint = point;
    southWestPoint = point;
    }
    else
    {
    if (point.x > northEastPoint.x)
    northEastPoint.x = point.x;
    if(point.y > northEastPoint.y)
    northEastPoint.y = point.y;
    if (point.x < southWestPoint.x)
    southWestPoint.x = point.x;
    if (point.y < southWestPoint.y)
    southWestPoint.y = point.y;
    }

    pointArr[idx] = point;

    }

    // create the polyline based on the array of points.
    self.routeLine = [MKPolyline polylineWithPoints:pointArr count:pointStrings.count];

    _routeRect = MKMapRectMake(southWestPoint.x, southWestPoint.y, northEastPoint.x - southWestPoint.x, northEastPoint.y - southWestPoint.y);

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

    }

    Now that you’ve got your route’s polyline created, you need to let the map view display it by adding it as an overlay:

    [self.mapView addOverlay:self.routeLine];

    Adding the overlay alone will not render anything on the map. Your MKMapViewDelegate implementation must return an overlay for this route you’ve just added. Luckily, the new iOS 4 MapKit framework provides a default implementation of an MKOverlayView that is capable of rendering a line on a map. This class is called MKPolylineView, and this is how you might use it:

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

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

    overlayView = self.routeLineView;

    }

    return overlayView;

    }

    I hope this helps you use the new MapKit classes. Apple really has simplified rendering lines, routes and other shapes on the map, and these new classes and protocols really deprecate my previous articles on the topic, so long as you are targeting iOS 4 and above.

    Here a sample project that uses the above code:
    os4Maps

Which Platform

March 9th, 2010

Had an interesting time tonight at the XConomy Mobile Madness conference. I was invited to be a guest panelist in a forum titled “Mobile Smackdown”; the point of the forum was to debate the merits of various mobile platforms.

I was chosen to argue for the iPhone.

As far as mobile development platforms are concerned, I’m fairly agnostic. I’ve worked in Windows Mobile, Android and the iPhone, and can honestly say that each one of them has their merits. For me, its not the features of a development platform that decides whether or not I should be developing for it. Its the business case for that platform that makes a more compelling argument than the technology itself.

My team (iPhone) lost the debate. I really don’t like to argue. Its also really difficult to argue for a point that you don’t completely agree with. I can not claim that the iPhone is easier to develop for or a better platform for developers when I have enjoyed all that Android offers. The other teams made very good points about the closed nature of the iPhone as well as the strict approval process on the iTunes App Store. Apple has recently reversed its decision on many apps, and that has the effect of turning app developers away, as Android becomes more and more popular.

There were two audience participatory votes at tonight’s “smackdown”:

  • Which team presented their case the best
  • Regardless of the debate, which platform should developers target

I was not surprised by the answer to the first question; I’m pretty terrible at debate. The second answer surprised me though; the most hands went up for Android. I think there are a few reasons for this:

  • Its easy to get lost in the sea of 120,000+ apps, and independent developers are having and increasingly difficult time rising to the top of the iTunes App Store.
  • Android’s future looks bright, as more devices from multiple manufactures are deployed on multiple networks. The potential market for these devices are larger than a device from a single carrier on a single network (understanding of course the iPhone is on many networks throughout the world, but restricted to one carried in its most popular market, the U.S.).
  • Android is an easy platform for which to develop. Its Java.
  • Android apps can be distributed independently of the app store

While Android shows huge potential, there are more people downloading and actually paying for apps on the iPhone. This may change, but until it does, we need to consider the size of the market we want to enter with our apps vs. the difficultly of getting our apps discovered. It may be better to target an emerging market with fewer paying customers if they can actually find your app. But if it is an extremely unique and popular idea, your potential for revenue is still greater on the iPhone.

Come Write Software

October 16th, 2009

Come write software with me and the rest of Raizlabs. We’re looking to build our team of developers. Are you interested in writing mobile applications? Send me a note.

Objective-C SHA1 Function for the iPhone

September 25th, 2009

Here’s a nice little function that will generate an SHA-1 hash digest that will match what the php sha1() function will generate if you give it the same input:

#import

@implementation SHA1

+(NSString*) digest:(NSString*)input
{
const char *cstr = [input cStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytes:cstr length:input.length];

uint8_t digest[CC_SHA1_DIGEST_LENGTH];

CC_SHA1(data.bytes, data.length, digest);

NSMutableString* output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];

for(int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++)
[output appendFormat:@"%02x", digest[i]];

return output;

}


@end

Drawing polyines or routes on a MKMapView (as an MKAnnotationView) – Part 2

August 19th, 2009

It turns out there is a better way to display routes on a map than the last example I gave. The problem with my last example is that the route exists in a layer above the map view, resulting in the line of the route being drawn on top of any annotation views. So pins, photos and anything else expressed as an MKAnnotation would be drawn over by the routes in the layer above the map view.

The solution to this problem then is to draw the lines as usual, but in a custom MKAnnoationView. This view will be set to not clip its own subviews, and will then have a subview that lies outside the frame of the annotation view, and this subview will draw the lines. The route annotation view needs a subview to do the rendering which will always be positioned at the full frame size and origin of the map. This way the MKAnnotationView can be smaller than the route, but it  always draws in the internal subview, which is the size of the map view.

So… long story short: the ability to put a route on a MKMapView via a custom MKAnnotation. You can download the sample code here.

//
// CSRouteView.m
// testMapp
//
// Created by Craig on 8/18/09.
// Copyright Craig Spitzkoff 2009. All rights reserved.
//
#import "CSRouteView.h"
#import "CSRouteAnnotation.h"
// this is an internally used view to CSRouteView. The CSRouteView needs a subview that does not get clipped to always
// be positioned at the full frame size and origin of the map. This way the view can be smaller than the route, but it
// always draws in the internal subview, which is the size of the map view.
@interface CSRouteViewInternal : UIView
{
// route view which added this as a subview.
CSRouteView* _routeView;
}
@property (nonatomic, retain) CSRouteView* routeView;
@end
@implementation CSRouteViewInternal
@synthesize routeView = _routeView;
-(void) drawRect:(CGRect) rect
{
CSRouteAnnotation* routeAnnotation = (CSRouteAnnotation*)self.routeView.annotation;

// 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 != routeAnnotation.points && routeAnnotation.points.count > 0)
{
CGContextRef context = UIGraphicsGetCurrentContext();

if(nil == routeAnnotation.lineColor)
routeAnnotation.lineColor = [UIColor blueColor]; // setting the property instead of the member variable will automatically reatin it.

CGContextSetStrokeColorWithColor(context, routeAnnotation.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 < routeAnnotation.points.count; idx++)
{
CLLocation* location = [routeAnnotation.points objectAtIndex:idx];
CGPoint point = [self.routeView.mapView convertCoordinate:location.coordinate toPointToView:self];

NSLog(@"Point: %lf, %lf", point.x, point.y);

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

CGContextStrokePath(context);

// debug. Draw the line around our view.
/*
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, 0, self.frame.size.height);
CGContextAddLineToPoint(context, self.frame.size.width, self.frame.size.height);
CGContextAddLineToPoint(context, self.frame.size.width, 0);
CGContextAddLineToPoint(context, 0, 0);
CGContextStrokePath(context);
*/
}

}
-(id) init
{
self = [super init];
self.backgroundColor = [UIColor clearColor];
self.clipsToBounds = NO;

return self;
}
-(void) dealloc
{
self.routeView = nil;

[super dealloc];
}
@end
@implementation CSRouteView
@synthesize mapView = _mapView;
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor clearColor];
// do not clip the bounds. We need the CSRouteViewInternal to be able to render the route, regardless of where the
// actual annotation view is displayed.
self.clipsToBounds = NO;

// create the internal route view that does the rendering of the route.
_internalRouteView = [[CSRouteViewInternal alloc] init];
_internalRouteView.routeView = self;

[self addSubview:_internalRouteView];
}
return self;
}
-(void) setMapView:(MKMapView*) mapView
{
[_mapView release];
_mapView = [mapView retain];

[self regionChanged];
}
-(void) regionChanged
{
NSLog(@"Region Changed");

// move the internal route view.
CGPoint origin = CGPointMake(0, 0);
origin = [_mapView convertPoint:origin toView:self];

_internalRouteView.frame = CGRectMake(origin.x, origin.y, _mapView.frame.size.width, _mapView.frame.size.height);
[_internalRouteView setNeedsDisplay];

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

[super dealloc];
}
@end

Lumbar Epidural Steroid Injection

June 19th, 2009

Today I had a series injections of anesthetics and steroids in my spine. These are supposed to temporarily relieve my pain over the next two or three weeks. After that, we get to see if I am still in pain, and how bad it is, and that will be the lead determining factor of whether I go in for a discectomy to take care of the herniated disc. The discectomy would be a minimally invasive micro endoscopic procedure, which depending on the results may or may not be an outpatient procedure.

The shots I had today were painless, and the radiologist who administered them was amazing. The hard part though was lying on my stomach long enough to receive the injections. This three minutes of lying on my stomach was probably one of the most painful experiences of my life. My back just can not support that position right now. But relief came quickly afterward, so the three minutes of extreme pain was worth it.

What I’m hoping for, of course, is that after three weeks and the wearing off of the steroid injection, that the ejected disc material pushing up against my sciatic nerve will have been reabsorbed or receded away from my nerves. That’s really the source of all this pain; not the rupturing of the disc, but rather the pressing on and displacement of my lumbar nerves. If I’m not in pain after that, I can start some more serious physical therapy and hopefully get some strength back. But the radiologists reaction to my MRI was not encouraging, and he gave the impression that any relief he could offer would only be temporary. Only time will tell.

New England Baptist Hospital was impressive though. No smoking anywhere on their campus, super clean and modern, all the support staff were in suits… it just felt like a very well run operation.

In light of all this JobVent.com prospecting goes well… I’ve had several good conversations over the last few days. The power of online social networking in situations like this is becoming extremely clear. LinkedIn and Twitter have been indispensable.

Ouch

June 15th, 2009

My Herniated Disc
I’m trying to sell JobVent.com. Here’s why.

About a week ago, I had an MRI, which revealed a 19.4mm herniation in the disc between my L4 and L5 vertebrae. What happens when you have a disc herniation is that the outer casing of the disc ruptures, and the gel within the disc is forced out. In some cases, the herniation is bad enough that it puts a great deal of pressure on the nerves that run through the back of the spine. This is currently what is happening to me.

Walking is now difficult, sitting in a chair even more so. And we software developers, well, we like to sit in chairs. I’m comfortable when standing, but only after standing for about 10 minutes.

It seems more and more likely that I will be having spinal surgery to address this problem. We’ll find out after I talk to several surgeons.

After the family tragedy we went through this winter, and the pain I am going through right now, its becoming more and more difficult for me to find energy and time to maintain JobVent.com. I find myself with several new main focuses in my life, and these are as follows:

  1. Recover. Be it through surgery or physical therapy or a combination of the two, I simply cannot function while in this much pain. My main goal right now is the restoration of my own physical health.
  2. Family. We had a very tough winter, but we go on. My wife is my world. We will have children.
  3. Work. I write great software, and my main focus is mobile development. I enjoy it, I work with an amazing team, and we’re going to continue building amazing products for multiple platforms.

Unfortunately, as you see, JobVent.com does not make the list. So if you, or someone you know is interested in acquiring what turns out to be a fairly popular website, please, send me an email. I’ll answer any questions you have.

Using MKAnnotation, MKPinAnnotationView and creating a custom MKAnnotationView in an MKMapView

May 16th, 2009

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.

Today’s experiment will demonstrate how to drop pin annotation views, as well as custom annotation views on the iphone’s map by providing the map with objects that implement the MKAnnotation protocol.

The class I created that implements MKAnnotation can be used to differentiate between those annotations that should be represented by MKPinAnnotationViews of diferernt colors, as well as our custom annotation view, CSImageAnnotationView. The CSMapAnnotation class needs to keep track of additional information such as images and URLs that can be used for a custom display.

This is the header for the custom annotation. You can see that there is an enumeration that is used to designate what type of annotation view should be used for display of each annotation on the iPhone’s map. Also available are the userData and url properties; userData can be used to store anything, but in the case of this example, we’re using it to store path information for an image that should be displayed on the map. Title is also an available property, but subtitle will be generated on the fly.

//
// CSMapAnnotation.h
// mapLines
//
// Created by Craig on 5/15/09.
// Copyright 2009 Craig Spitzkoff. All rights reserved.
//

#import
#import

// types of annotations for which we will provide annotation views.
typedef enum {
CSMapAnnotationTypeStart = 0,
CSMapAnnotationTypeEnd = 1,
CSMapAnnotationTypeImage = 2
} CSMapAnnotationType;

@interface CSMapAnnotation : NSObject
{
CLLocationCoordinate2D _coordinate;
CSMapAnnotationType _annotationType;
NSString* _title;
NSString* _subtitle;
NSString* _userData;
NSURL* _url;
}

-(id) initWithCoordinate:(CLLocationCoordinate2D)coordinate
annotationType:(CSMapAnnotationType) annotationType
title:(NSString*)title;

@property CSMapAnnotationType annotationType;
@property (nonatomic, retain) NSString* userData;
@property (nonatomic, retain) NSURL* url;

@end

In most cases, when our MKMapViewDelegate is asked for an MKAnnotation, we return a MKPinAnnotation. If the CSMapAnnotation is set to be CSMapAnnotationTypeImage , the delegate instead produces an instance of our custom annotation view, CSImageAnnotationView, and sets its properties based on the annotation’s user data.

This is the body of our custom annotation view. It is pretty simple; the bulk of the functionality is in the initialization function, which sets up the view to have a UIImageView.

//
// CSImageAnnotationView.m
// mapLines
//
// Created by Craig on 5/15/09.
// Copyright 2009 Craig Spitzkoff. All rights reserved.
//

#import "CSImageAnnotationView.h"
#import "CSMapAnnotation.h"

#define kHeight 100
#define kWidth 100
#define kBorder 2

@implementation CSImageAnnotationView
@synthesize imageView = _imageView;

- (id)initWithAnnotation:(id )annotation reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
self.frame = CGRectMake(0, 0, kWidth, kHeight);
self.backgroundColor = [UIColor whiteColor];

CSMapAnnotation* csAnnotation = (CSMapAnnotation*)annotation;

UIImage* image = [UIImage imageNamed:csAnnotation.userData];
_imageView = [[UIImageView alloc] initWithImage:image];

_imageView.frame = CGRectMake(kBorder, kBorder, kWidth - 2 * kBorder, kWidth - 2 * kBorder);
[self addSubview:_imageView];

return self;

}

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

@end

So, how does the map know to ask the delegate for annotation views? It knows to do this based on annotations that are added to the map in our view controller’s viewDidLoad method.


// create the image annotation
annotation = [[[CSMapAnnotation alloc] initWithCoordinate:[[points objectAtIndex:points.count / 2] coordinate]
annotationType:CSMapAnnotationTypeImage
title:@"Cleveland Circle"] autorelease];
[annotation setUserData:@"cc.jpg"];
[annotation setUrl:[NSURL URLWithString:@"http://en.m.wikipedia.org/wiki/Cleveland_Circle"]];

[_mapView addAnnotation:annotation];

UPDATE – 6/1/2009
You’ll notice we set the URL on the annotation above, and the code has now been updated to display that webpage when the user clicks the annotation disclosure of our custom image annotation view. The code sample you can download has been updated to reflect this change.

The full code of this example is available for download here.

Update – 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.

RemoteIO Audio Unit for FFT based on compressed stream output

May 6th, 2009

Today, I pose a question for other iPhone developers who have a lot of experience working with Audio.

The examples I have seen out there performing FFTs on audio have all been based around the same concept from the aurioTouch example code. This code works well for me, however I would like to perform the FFT analysis on the outgoing audio from a compressed file stream, instead of input from the microphone. Now I have the file stream working as output via audio queues, as well as the RemoteIO Audio Unit receiving data from the mic at the same time, but I’d much rather the audio buffers being sent to my audio unit’s AURenderCallback be representative of the data being played back, instead of the data being received from the microphone.

So, I guess my question is this: Can I set up a RemoteIO Audio Unit to intercept outgoing uncompressed audio buffers based on compressed audio being played back through audio queues?

Thanks in advance… first applicable code snippet gets a prize.

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

April 12th, 2009

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