The iPhone has plenty of neat features to use, one of the more recent features is using the built in Google Maps support. The specific SDK library we are looking at is MapKit.
The application we are looking at building today is going to display the last 300 earthquakes from around the world - data pulled from USGS. The events are shown on the map, with larger earthquakes being displayed larger in size and a darker color. This shows off putting custom annotations on the map with a custom drawn view for each. Below is a video of the application in action. When it loads it will request the data and update the map.
To get moving on this application, I setup a basic View Based
Application (named EarthquakeMap in my case), opened the view nib in
Interface Builder and dropped a Map View on it. Back in XCode we need to
add a reference to MapKit.framework, which should show up by default in
the list to the right - for reference
/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator3.0.sdk/ System/Library/Frameworks.
This completes all the quick setup.
Diving into the code, we need to add to our view controller header an
IBOutlet for the map view (typed MKMapView) - don't forget to add an
import for <MapKit/MapKit.h>. MKMapView provides a delegate
protocol, therefore at this point we declare that our view controller
will implement some of the delegate, MKMapViewDelegate. To finish the
header file we add an NSMutableArray to hold our collection of seismic
events that we build from the data we get.
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
@interface EarthquakeMapViewController : UIViewController <MKMapViewDelegate> {
IBOutlet MKMapView *mapView;
NSMutableArray *eventPoints;
}
@end
Make sure to jump over into Interface Builder and hook up the IBOutlet
to the map view and the delegate of the map view to the view controller,
owner.
One item we need to build is a value object to hold the data about our
seismic events. This is a pretty normal object, the only thing specific
for this is that we are going to implement the MKAnnotation protocol.
This protocol defines properties for an object that wants to be used as
an annotation on a map. There is one property we want to implement which
is coordinate of type CLLocationCoordinate2D. The header for our
object looks like the following.
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
@interface SeismicEvent : NSObject <MKAnnotation>{
float latitude;
float longitude;
float magnitude;
float depth;
}
@property (nonatomic) float latitude;
@property (nonatomic) float longitude;
@property (nonatomic) float magnitude;
@property (nonatomic) float depth;
//MKAnnotation
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
@end
The implementation file isn't much more, the only thing we do is make
sure to return a CLLocationCoordinate2D made up of our latitude and
longitude for the getter of coordinate. I also dropped in the
description method for making debugging easier.
#import "SeismicEvent.h"
@implementation SeismicEvent
@synthesize latitude;
@synthesize longitude;
@synthesize magnitude;
@synthesize depth;
@synthesize coordinate;
- (CLLocationCoordinate2D)coordinate
{
CLLocationCoordinate2D coord = {self.latitude, self.longitude};
return coord;
}
- (NSString*) description
{
return [NSString stringWithFormat:@"%1.3f, %1.3f, %1.3f, %1.1f",
self.latitude, self.longitude, self.magnitude, self.depth];
}
@end
The next task is going out and getting the data from the USGS site. I
should mention that I have no idea if you're allowed to use the data I
am pulling for external applications or not. I simply found the file
link and used it. The data comes in way of a comma separated file from
the url
http://neic.usgs.gov/neis/gis/qed.asc.
We jump into the implementation file for the view controller and work
inside viewDidLoad for loading the file. Below is the entire method, I
will go over the code right after.
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *dataUrl = [NSURL
URLWithString:@"http://neic.usgs.gov/neis/gis/qed.asc"];
NSString *fileString = [NSString stringWithContentsOfURL:dataUrl
encoding:NSUTF8StringEncoding
error:nil];
int count = 0;
NSScanner *scanner = [NSScanner scannerWithString:fileString];
eventPoints = [[NSMutableArray array] retain];
SeismicEvent *event;
NSString *line;
NSArray *values;
while ([scanner isAtEnd] == NO) {
[scanner scanUpToString:@"\n" intoString:&line];
//skip the first line
if(count > 0) {
values = [line componentsSeparatedByString:@","];
event = [[[SeismicEvent alloc] init] autorelease];
event.latitude = [[values objectAtIndex:2] floatValue];
event.longitude = [[values objectAtIndex:3] floatValue];
event.magnitude = [[values objectAtIndex:4] floatValue];
event.depth = [[values objectAtIndex:5] floatValue];
[eventPoints addObject:event];
}
count++;
if(count == 300) {
//limit number of events to 300
break;
}
}
[mapView addAnnotations: eventPoints];
}
At the top we first build an NSURL for the page and then pull in the
information into a string. We then need parse the information, this is
going to be done with a combination of NSScanner and separating the
string using NSString. Next up: initializing scanner, events
collection, and declaring some variables. Then, we need to loop through
the file, using a while loop checking if the scanner has hit the end
of the file. Using scanner to grab the string for an entire line we
check to make sure it's not the first line (headers). If it isn't the
first line we chop up the string at the commas. With those values we
create a new SeismicEvent and set the appropriate properties on the
object, pulling out the correct value for each. The object is then added
to the array of events. Still inside the loop we check if we have added
300 and if so break out - we don't want to overcrowd the map. The last
thing done in the function is we add the events as annotations on the
map.
If everything is correct you should get something like the image below, where there are a ton of pins all over the map showing where earthquakes have occurred.

That is pretty cool, especially with the amount of code we have written so far. But what would be cooler? Well, showing the magnitude of the earthquake by changing the pin to a circle that gets larger and more red with increasing magnitude. Ok, so to do this we take advantage of one of the delegate methods on the map view delegate. The method we are looking at is:
- (MKAnnotationView *)mapView:(MKMapView *)lmapView
viewForAnnotation:(id <MKAnnotation>)annotation;
The method lets us use a custom view for an annotation. The view that is
returned from the method has be a MKAnnotationView or a view that
extends it. We are going to build a custom view that extends
MKAnnotationView - I named mine EarthquakeEventView. The only thing
in the header for this is an instance variable named event that is a
SeismicEvent which is going to be set when the annotation is set. The
complete header code is below.
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#import "SeismicEvent.h"
@interface EarthquakeEventView : MKAnnotationView {
SeismicEvent *event;
}
@end
Jumping over to the implementation file the first thing to do is
override the init function, initWithAnnotation. In the method I call
the super and simply set the background color to clear or transparent -
this is important because it allows us to draw semi-transparent graphics
and allows the view to have alpha. The next method in our file is going
to be to override setAnnotation. This is where we grab the
SeismicEvent as it comes in and set it to our instance variable. We
also set the size of our view as this point to be a height and width of
our magnitude squared times 0.75, this makes it a non linear sizing
algorithm (tidbit: Richter Scale is logarithmic). We also need to
override drawRect which is where we actually draw our circle. This is
done by grabbing the graphics context for the object, setting the color,
and drawing the circle. We start with a yellow color and modify it to be
more red depending on the magnitude. Finally, it's nice to override
dealloc function to clean up our memory. The entire implementation
file follows.
#import "EarthquakeEventView.h"
@implementation EarthquakeEventView
- (id)initWithAnnotation:(id <MKAnnotation>)annotation
reuseIdentifier:(NSString *)reuseIdentifier {
if(self = [super initWithAnnotation:annotation
reuseIdentifier:reuseIdentifier]) {
self.backgroundColor = [UIColor clearColor];
}
return self;
}
- (void)setAnnotation:(id <MKAnnotation>)annotation {
super.annotation = annotation;
if([annotation isMemberOfClass:[SeismicEvent class]]) {
event = (SeismicEvent *)annotation;
float magSquared = event.magnitude * event.magnitude;
self.frame = CGRectMake(0, 0, magSquared * .75, magSquared * .75);
} else {
self.frame = CGRectMake(0,0,0,0);
}
}
- (void)drawRect:(CGRect)rect {
float magSquared = event.magnitude * event.magnitude;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(context, 1.0, 1.0 - magSquared * 0.015, 0.211, .6);
CGContextFillEllipseInRect(context, rect);
}
- (void)dealloc {
[event release];
[super dealloc];
}
@end
To get this view being used we jump back to our controller implementation file and hook in the delegate function mentioned earlier. So, we can go ahead and add the following to our file.
- (MKAnnotationView *)mapView:(MKMapView *)lmapView
viewForAnnotation:(id <MKAnnotation>)annotation {
EarthquakeEventView *eventView = (EarthquakeEventView *)[lmapView
dequeueReusableAnnotationViewWithIdentifier:
@"eventview"];
if(eventView == nil) {
eventView = [[[EarthquakeEventView alloc] initWithAnnotation:annotation
reuseIdentifier:@"eventview"]
autorelease];
}
eventView.annotation = annotation;
return eventView;
}
What is going on above is we use
dequeueReusableAnnotationViewWithIdentifier to grab an already created
view to make reuse of our annotation views. If one isn't returned we
create a new one. Then we just set the annotation on it and return the
view. Simple as that. Assuming everything is perfect, don't forget to
import the correct headers, you should have a fully working application
that shows the most recent 300 earthquakes around the world.
Source Files:
I liked your post and how you created a custom annotation views...I'm currently working on MKMapView myself. feel free to join my developer network on Ning: http://codeninja.ning.com/.
Have you tested to see if dequeueReusableAnnotationViewWithIdentifier is really finding annotations on the queue to be reused?
No, not particularly. Is there a reason you would think that they would not be reused?
hi,
DL your code. Upgraded all to the latest SDK, getting this bug in SesmicEvent.m: (thanks for diving into mapkit)
!synthesize property 'coordinate' must either be named as a compatiable ivar or must use explicitly name an ivar
add CLLocationCoordinate2D coordinate; in SeismicEvent.h with other variable declarations
Thanks for an excellent example of practical use!
I am v.new to obj-c ... i notice event release in EarthquakeEventView dealloc ... where is the associated retain ?
It gets autoreleased in your view controller.
Thanks for this great example!
Hi there!
I really like your code, I'm new to iphone sdk. I would like to find out two things:
?1. I did the coding until I have all those red pins. I'd like to use a .PNG file instead of the pins. How can I do that?
?2. At last but not least is there a chance to show some lines of information if I click on one pin or pic? The information should load from the database, like magnitude, strength, facalties in three different lines.
I know I ask too much but I'm surffing the web for some weeks now and I found this article the most helpful!
I would be really glad if you could answer me!
cheers and really really thanks for the code again!
Atis
Hi! great tutorial, thank you.
I just wonder how could I add callouts balloons when I touch some of custom annotations in the map. Could someone help me? I already worked with callouts, but I'm having difficulty to see how it would work on this code.
regards
Hey there , I got an an error :(
The document EarthquakeMapViewController - xib could not be opened. An instance of IBMKMapView could not be decoded.
Ensure all plug-ins used to create this document have been loaded.
Thanks for a great tutorial. It worked *nearly* perfectly. If I put an NSLog(@"hello world") in to the (void)setAnnotation:(id )annotation method, it seems to output that three times. Any ideas why? I have only one marker and I can see that the markers array is at 1 (using the count function). Thanks v much
@atisfix - make sure your mapview outlet delegate is set to file's owner. Check you have an IBOutlet for mapview and make sure referencing the mapview in interface builder.
Amazing Tutorial!! Everything I was looking for and more!
Thankyou AMAZING!!!
nice one..!