Archive for April, 2009

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

Sunday, 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

Interview Question

Tuesday, April 7th, 2009

I just read an interesting article about a well known marketing executive in NYC. One specific part of the article talks about how horrible he was to work for; the example given is that he would make employees do push-ups in front of clients (the reason for which is not provided).

This got me thinking about a story I heard once about an interesting question encountered at a job interview. The question was “What would be your response if I asked you to do some push-ups?” The job being interviewed for was something software related, development or testing; I forget. The nature of the job is unimportant except for the fact that there are not any physical requirements for the job, other than being able to show up at the office and operate a computer.

The answer that landed the job? “I’d ask you why you want me to do push-ups.”

From what I understand, the employer in question would not hire anyone that upon hearing the question decided to get down on the floor and do push-ups. Nor would they hire anyone who asked “How many would you like?” or any variation thereof. Many candidates when asked this question immediately got defensive, telling the interviewer it was inappropriate for them to ask for push-ups at any time, especially during a job interview. These candidates were also not considered for the job.

What most candidates for this particular company forgot to do was listen to the question. No one was asked to do push-ups. A question was asked about a hypothetical scenario in which an interviewee would be asked to perform a physical activity. The ideal candidate would know there is something wrong with an interviewer asking someone to do push-ups, but would be inquisitive and open-minded enough to inquire as to “why” push-ups would be requested before making their decision. Those candidates that immediately became defensive of their right not to have to do push-ups are not considering that there may be, even though they are unaware of the possibilities, a valid reason for such an odd request. It represents a closed-mindedness that the company was trying to avoid having in their employees. Candidates who immediately decided to do push-ups or ask for details about the push-ups they were about to do, while obviously eager to work for the company and willing to sacrifice, did not listen to the question, and lack the important instinct to question authority.

So, remember its OK to ask “why” during a job interview. But if an interviewer actually asks you to do push-ups, ask yourself if you’re OK with that level of disrespect.