Core Location & Map Kit:
        Bringing Your Own Maps
                                      Chris Adamson
                                       @invalidname
Voices That Matter: iPhone Developer Conference 2010
What You’ll Learn Today


Getting current location information
from Core Location
Getting a map UI from Map Kit
Getting route and other geo-data from
third-party providers
Where Am I?
Location technologies in iPhone

 Wi-ïŹ
   Can look up location via Skyhook
 Cellular network
   Can triangulate location from cell
   towers
 GPS
Beyond location


Course: which direction are you going?
Speed: how fast are you moving?
Heading: which direction are you
facing?
Altitude: how high up are you?
Core Location

Abstracts away the speciïŹc location
technologies
  No direct access to any speciïŹc
  location technology (GPS, etc.)
Lets you focus on your use of location
data
Basic Core Location use

Add Core Location framework to your
project
#import <CoreLocation/
CoreLocation.h>
Core Location framework


Three classes
  CLHeading, CLLocation,
  CLLocationManager
One Protocol
  CLLocationManagerDelegate
Building a simple CL demo
The CL Basics

Create a CLLocationManager
Set its delegate
Set desired accuracy and ïŹlter
Call startUpdatingLocation and/or
startUpdatingHeading
Handle delegate callbacks
Creating a CLLocationManager



locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy =
     kCLLocationAccuracyNearestTenMeters;
locationManager.distanceFilter = 10;
CLLocationManagerDelegate

locationManager:didUpdateToLocation:
fromLocation:
locationManager:didUpdateHeading:
locationManager:didFailWithError:
locationManagerShouldDisplayHeading
Calibration:
Location updates

CLLocation object contains:
  coordinate — struct with latitude and
  longitude
  course, speed, altitude properties
  accuracy properties
  timestamp!
Delegate implementation

- (void)locationManager:(CLLocationManager *)manager
    didUpdateToLocation:(CLLocation *)newLocation
    fromLocation:(CLLocation *)oldLocation {
! latLabel.text = [NSString stringWithFormat:@"%0.3f",
         newLocation.coordinate.latitude];
! lngLabel.text = [NSString stringWithFormat:@"%0.3f",
         newLocation.coordinate.longitude];
! altitudeLabel.text = [NSString stringWithFormat:@"%0.3f",
         newLocation.altitude];
! courseLabel.text = (newLocation.course >= 0.0) ?
! !     [NSString stringWithFormat:@"%0.3f", newLocation.course] :
! !     @"N/A";
! speedLabel.text = (newLocation.speed >= 0.0) ?
! !     [NSString stringWithFormat:@"%0.3f", newLocation.speed]:
! !     @"N/A";
! timestampLabel.text = [timestampFormatter stringFromDate:newLocation.timestamp];
}
Using coordinates

Latitude and Longitude are
CLLocationDegrees (doubles)
  -90° ≀ Latitude ≀ 90°
  -180° ≀ Longitude ≀ 180°
1° latitude ≅ 69 miles
1° longitude varies from 69 mi. to 0
You might get an error!

kCLErrorLocationUnknown - can be
ignored. CL is still trying.
kCLErrorDenied - user declined location
services to your app. Handle with grace.
Handling delegate errors


- (void)locationManager:(CLLocationManager *)manager
        didFailWithError:(NSError *)error {
! if ([error code] == kCLErrorDenied) {
! !   userDeclinedLocationPrivilegesHUD.hidden = NO;
! }
}
Demo: Simple Core Location
Other CL points of interest


-[CLLocation getDistanceFrom:]
CLHeading properties:
  magneticHeading
  trueHeading
Looking at Maps
Map Kit


Framework to provide map images to
UIKit applications
#import <MapKit/MapKit.h>
Map Kit Basic Use


Create an MKMapView and assign a
delegate
Add MKAnnotations to the map
Set region or span of map
Delegate handles events from map
Building a Map Kit Demo
About the demo

Converted CSV list of Apple Store
coordinates and addresses (circa 2007)
to .plist
  http://www.poi-factory.com/node/1700
Given starting location, gets distance to
each store and sorts the POI array
On tap, add nearest POI as annotation
MKMapView


Add to your UI in IB or in code
Requires a network connection to fetch
map image tiles from Google
Binds you to Google Maps terms of
service
MKAnnotation


Protocol to represent a point of interest
Added to MKMapViews
Properties: coordinate, title, subtitle
  MKPlacemark offers a concrete
  implementation
Implementing MKAnnotation

@interface MyMapAnnotation : NSObject <MKAnnotation> {
! CLLocationCoordinate2D coordinate;
! NSString *title;
! MKPinAnnotationColor color;
}
@property (nonatomic, readonly) CLLocationCoordinate2D
            coordinate;
@property (nonatomic) MKPinAnnotationColor color;

-(id) initWithCoordinate:(CLLocationCoordinate2D)coordinateP
      title:(NSString*) titleP
      color: (MKPinAnnotationColor) colorP;

@end
MKAnnotationView

Visual representation of an
MKAnnotation on an MKMapView
  Concrete implementation:
  MKPinAnnotationView
MKMapView dequeues and reuses
MKAnnotationViews (similar to
UITableView / UITableViewCell)
Adding annotations to map


-(IBAction) plusButtonTapped: (id) sender {
! // todo: bounds check
! NSDictionary *poiDict = [poiArray objectAtIndex:nextPoiIndex++];
! CLLocationCoordinate2D poiCoordinate;
! poiCoordinate.latitude = [[poiDict valueForKey:@"latitude"] doubleValue];
! poiCoordinate.longitude = [[poiDict valueForKey:@"longitude"] doubleValue];
! MyMapAnnotation *poiAnnotation = [[MyMapAnnotation alloc]
! !     !    !   !   !   !    !   !     initWithCoordinate:poiCoordinate
! !     !    !   !   !   !    !   !     title:[poiDict valueForKey:@"name"]
! !     !    !   !   !   !    !   !     color:MKPinAnnotationColorRed! ];
! [mapView addAnnotation:poiAnnotation];
! [self adjustMapZoom];
}
Zooming MKMapView, pt. 1


-(void) adjustMapZoom {
! if ([mapView.annotations count] == 1) {
! !     // if only one point, zoom smartly around it
! !     [mapView setRegion:MKCoordinateRegionMakeWithDistance
! !     !    !   !   !   !    ([[mapView.annotations objectAtIndex:0] coordinate],
! !     !    !   !   !   !     2000, // 2 km lat span
! !     !    !   !   !   !     2000) // 2 km lng span
! !     !    !     animated: YES];
  }
Zooming MKMapView, pt. 2
!   else {
!   !      // find a region encompassing all annotations
!   !      CLLocationDegrees maxLatitude = -180;
!   !      CLLocationDegrees minLatitude = 180;
!   !      CLLocationDegrees maxLongitude = -180;
!   !      CLLocationDegrees minLongitude = 180;
!   !
!   !      for (id<MKAnnotation> annotation in [mapView annotations]) {
!   !      !    if ([annotation coordinate].latitude > maxLatitude) {
!   !      !    !     maxLatitude = [annotation coordinate].latitude;
!   !      !    }
!   !      !    if ([annotation coordinate].latitude < minLatitude) {
!   !      !    !     minLatitude = [annotation coordinate].latitude;
!   !      !    }
!   !      !    if ([annotation coordinate].longitude > maxLongitude) {
!   !      !    !     maxLongitude = [annotation coordinate].longitude;
!   !      !    }
!   !      !    if ([annotation coordinate].longitude < minLongitude) {
!   !      !    !     minLongitude = [annotation coordinate].longitude;
!   !      !    }
!   !      }
!   !
!   !      CLLocation *maxPoint = [[[CLLocation alloc] initWithLatitude:maxLatitude
!   !      !    !     !    !     longitude:maxLongitude] autorelease];
!   !      CLLocation *minPoint = [[[CLLocation alloc] initWithLatitude:minLatitude
!   !      !    !     !    !     longitude:minLongitude] autorelease];
!   !      CLLocationDistance distance = [maxPoint getDistanceFrom:minPoint];
!   !      MKCoordinateRegion region;
!   !      region.center.latitude = (maxPoint.coordinate.latitude + minPoint.coordinate.latitude) / 2.0;
!   !      region.center.longitude = (maxPoint.coordinate.longitude + minPoint.coordinate.longitude) / 2.0;
!   !      region.span.latitudeDelta = (distance / METERS_PER_DEGREE_LATITUDE) * 1.10;
!   !      region.span.longitudeDelta = 0.0;
!   !      MKCoordinateRegion adjustedRegion = [mapView regionThatFits:region];
!   !      [mapView setRegion:adjustedRegion animated:YES];
!   }
MKMapViewDelegate

Called when region changes (scaling,
scrolling), and as map images load
Provides event when a “callout
accessory view” is tapped (e.g.,
disclosure button)
Requests views for newly-added
annotations
Providing views for annotations

- (MKAnnotationView *)mapView:(MKMapView *)mapViewP
            viewForAnnotation:(id <MKAnnotation>)annotation {
! MKPinAnnotationView *pinView = (MKPinAnnotationView*)
          [mapViewP dequeueReusableAnnotationViewWithIdentifier:@"pin"];
! if (pinView) {
! !     pinView.annotation = annotation;
! } else {
! !     pinView = [[[MKPinAnnotationView alloc]
                    initWithAnnotation:annotation reuseIdentifier:@"pin"]
                    autorelease];
! }
!
! pinView.animatesDrop = YES;
! pinView.canShowCallout = YES;
! if ([annotation isKindOfClass:[MyMapAnnotation class]]) {
! !     MyMapAnnotation *myAnnotation = (MyMapAnnotation*) annotation;
! !     pinView.pinColor = myAnnotation.color;
! }
! return pinView;
}
Demo: Apple Store Finder
Also: Reverse Geocoding

Geocoding: ïŹnding coordinates for an
address. Map Kit doesn’t do this.
Reverse geocoding: ïŹnding address for
given coordinates
  MKReverseGeocoder and
  MKReverseGeocoderDelegate
You must display a map to use this API
The Treachery of Images
This is not a city
This is not a body of water
This is not a street
Demo: Why Apple Store Finder is
                       Broken
Map Kit Doesn’t Provide Maps

 Map Kit provides map images
 Nothing in Map Kit has any awareness
 of geography, transportation, political
 borders, cultural or regional
 distinctions, etc.
 All it does is push pixels to your screen
Bringing Your Own Maps


Google Maps terms prohibit Apple from
providing map data in Map Kit
You have to provide it yourself
  Typically via a third-party
Third-Party Map Providers


Google
MapQuest
NAVTEQ
Bing
Others
What Map Providers Offer


POI search — businesses, geographic
features, roads, etc.
Directions and trafïŹc info
Geocoding and reverse geocoding
Map images
Map data access



On-board database
Network access
Section 3.3.1 Considerations
Section 3.3.1 Considerations
Section 3.3.1 Considerations
Section 3.3.1 Considerations
Section 3.3.1 Considerations
Calling Map Providers

Safest approach may be to use a network
API, using Cocoa’s URL Loading
Service or CFNetwork APIs
  e.g., MapQuest web services
NAVTEQ Smart APIs for Mobile -
iPhone-only API
Fixing Apple Store Demo
Fix Strategy


Sort POIs by linear distance, then use a
map provider to get driving distances for
the ïŹrst few and re-sort
Sort later POIs as more are consumed
from array
Using MapQuest API


Sign up at developer.mapquest.com
Get an API key
  Provide this key with each request
Compose webservice request URL
Parse XML result
Send web service request

#define MQ_DIRECTIONS_REQUEST_FORMAT
   @"http://www.mapquestapi.com/directions/v1/route?key=%@&from=%f,%f&to=%@,
   %@&outFormat=xml"

-(void) addDrivingDistanceToPoiDict: (NSMutableDictionary*) poiDict {
! NSString *poiLat = [poiDict valueForKey: @"latitude"];
! NSString *poiLng = [poiDict valueForKey: @"longitude"];
! NSString *mqURLS = [NSString stringWithFormat:MQ_DIRECTIONS_REQUEST_FORMAT,
! !     !    !   !   !   MQ_APP_KEY,
! !     !    !   !   !   homeCoordinate.latitude, homeCoordinate.longitude,
! !     !    !   !   !   poiLat, poiLng];
! NSURL *mqURL = [NSURL URLWithString:mqURLS];
! NSURLRequest *mqURLRequest = [NSURLRequest requestWithURL:mqURL];
! NSURLResponse *mqURLResponse = nil;
! NSError *mqURLError = nil;
! NSData *routeData = [NSURLConnection sendSynchronousRequest:mqURLRequest
! !     !    !   !   !   !    !   !   !   returningResponse:&mqURLResponse
! !     !    !   !   !   !    !   !   !   error:&mqURLError];
! MQDirectionsParser *parser = [[MQDirectionsParser alloc] initWithXML:routeData];
Get web service result
  <?xml version="1.0" encoding="UTF-8"?>
  <response>
  <info>
  <statusCode>0</statusCode>
  <messages/>
  <copyright>
  <imageUrl>http://tile21.mqcdn.com/res/mqlogo.gif</imageUrl>
  <imageAltText>© 2010 MapQuest, Inc.</imageAltText>
  <text>© 2010 MapQuest, Inc.</text>
  </copyright>
  </info>
  <route>
  <sessionId>4bd0285d-01bc-0000-02b7-3827-001e4f148321</sessionId>
  <options>
  <shapeFormat>raw</shapeFormat>
  <generalize>-1.0</generalize>
  <maxLinkId>0</maxLinkId>
  <narrativeType>text</narrativeType>
  <stateBoundaryDisplay>true</stateBoundaryDisplay>
  <countryBoundaryDisplay>true</countryBoundaryDisplay>
  <sideOfStreetDisplay>true</sideOfStreetDisplay>
  <destinationManeuverDisplay>true</destinationManeuverDisplay>
  <avoidTimedConditions>false</avoidTimedConditions>
  <timeType>0</timeType>
  <routeType>FASTEST</routeType>
  <locale>en_US</locale>
  <unit>M</unit>
  <tryAvoidLinkIds/>
  <mustAvoidLinkIds/>
  <manmaps>true</manmaps>
  </options>
  <boundingBox>
  <ul>
  <lat>47.663799</lat>
  <lng>-122.348854</lng>
  </ul>
  <lr>
  <lat>47.60448</lat>
Create an XML parser


-(id) initWithXML: (NSData*) xml {
! if (self = [super init]) {
! !     routeDict = [[NSMutableDictionary alloc] init];
! !     routeDistance = -1;
! !
! !     NSXMLParser *parser = [[NSXMLParser alloc] initWithData:xml];
! !     [parser setDelegate:self];
! !     [parser parse];
! }
! return self;
}
Notice interesting tags


-   (void) parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
!    !     !   !   !    !   !   !   namespaceURI:(NSString *)namespaceURI
!    !     !   !   !    !   !   !   qualifiedName:(NSString *)qualifiedName
!    !     !   !   !    !   !   !   attributes:(NSDictionary *)attributeDict {
!    // only care about "distance" and "narrative" tags
!    if ([elementName isEqualToString:@"distance"] |
!    !     [elementName isEqualToString:@"narrative"]) {
!    !     currentCharacters = [[NSMutableString alloc] init];
!    }
!    if ([elementName isEqualToString:@"legs"]) {
!    !     narrativeArray = [[NSMutableArray alloc] init];
!    }
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
! [currentCharacters appendString:string];
}
Retrieve values from tags


- (void)parser:(NSXMLParser *)parser   didEndElement:(NSString *)elementName
! !     !    !   !   !   !    !   !    namespaceURI:(NSString *)namespaceURI
! !     !    !   !   !   !    !   !    qualifiedName:(NSString *)qName {

!   if ([elementName isEqualToString:@"distance"]) {
!   !     // if this is first distance, not part of a leg, then it's the whole trip
!   !     if (routeDistance == -1) {
!   !     !   routeDistance = [currentCharacters doubleValue];
!   !     }
!   }
!   if ([elementName isEqualToString:@"narrative"]) {
!   !     [narrativeArray addObject:currentCharacters];
!   }
!
!   [currentCharacters release];
!   currentCharacters = nil;
}
Demo: Fixed Apple Store Finder
The Catch
TNSAAFL!


All third-party mapping providers have
terms of service
You should actually read them
  Because you’ll probably violate them
  without knowing it
Interesting MapQuest TOS
          01.       (d) Traffic information shall not be (i) used for Real-Time Navigation; (ii)
             used in conjunction with in-car or stand alone portable navigation devices; or (iii)
             used for the primary purpose of Your page or application. For the purposes of
             this Agreement, “Real-Time Navigation” shall mean using a sensor to determine
             location and providing contemporaneous turn-by-turn directions as the user
             moves through the route.


     RESTRICTIONS. Except as expressly authorized by MapQuest, You shall not:
      â–Ș    derive results from the Service based on sensor-derived location data or information or input
           in the form of coordinate data, provided that a coordinate location or location derived by a
           single sensor, including without limitation a sensor incorporated into, connected to or in
           communication with any mobile device or system, may be used solely as an origin or
           destination in deriving a map or direction;
      â–Ș    [
]
      â–Ș    use the Service with products, systems or applications capable of navigation, positioning,
           tracking or routing of a movable asset;




http://developer.mapquest.com/web/info/terms-of-
                    use-free
Interesting Google Maps TOS

  Your Maps API Implementation must be generally accessible to
  users without charge. You may require users to log in to your
  Maps API Implementation if you do not require users to pay a fee.
  Unless you have entered into a separate written agreement with
  Google or obtained Google's written permission, your Maps API
  Implementation must not:

  (a) require a fee-based subscription or other fee-based restricted
  access; or
  (b) operate only behind a ïŹrewall or only on an internal network
  (except during the development and testing phase).



http://code.google.com/apis/maps/terms.html
Interesting Bing Maps TOS


     If you would like to develop or host an Application that is designed to access and
     use the service for commercial, non-commercial or government use, provided that
     such use is not educational or non-profit as defined under Section 2(i), and your
     Application and content will be available publically without restriction (for example,
     login or password must not be required) you may do so without entering into a
     MWS/BM agreement or licensing the service through Microsoft Volume Licensing by
     complying with the following terms:

     In addition to all of the restrictions on educational and non-profit use, including the
     limitations on Traffic Data, set forth in Section 2(i) above, the following restriction
     also applies:
      ‱ You may not exceed more than 125,000 sessions or 500,000 transactions, both
        as defined in the SDKs, in any twelve month period




http://www.microsoft.com/maps/product/terms.html
More Bing Maps TOS


   Restrictions on your use: We do have some restrictions on your use of the service. You may
   not:
    ‱ copy, store, archive, or create a database of the content, except that geocodes may be
      stored locally only for use with your Applications;
    ‱ exceed 50,000 geocoding transactions or requests in any 24 hour period;
    ‱ download more than 250 points of interest at any one time;
    ‱ use the service for business asset tracking, fleet management, or dispatch;
    ‱ present or alert an end user to individual maneuvers of a route in any way that is
      synchronized with the end-user’s sensor-based position along the route, (e.g. “real-time”
      navigation);
    ‱ [
]
    ‱ integrate the Bing Maps Platform or any of its content with any other mapping platform;



http://www.microsoft.com/maps/product/terms.html
Mapping terms of service

Most free terms prohibit:
  Use in commercial apps
  Use with a GPS-determined location
  Use with competitors’ maps or data
You can get around some of these with
commercial licensing
Other hazards

Handle bad data gracefully
  Expect mis-formed or missing tags
  and values
  Expect some data to be out of date
  (e.g., closed businesses)
Handle network latency gracefully
Summary
iPhone Mapping


Core Location is great
Map Kit is great
Using them together is great
  But

More Mapping

Really understanding the relationship
between multiple points of interest
requires real map data
Look to third parties for this
  You will probably need a commercial
  license
Further Reading


http://code.google.com/apis/maps/
http://www.microsoft.com/maps/
developers/
http://developer.mapquest.com/
http://www.nn4d.com
Q&A
Thanks!
  http://www.subfurther.com/blog
                  @invalidname
invalidname [at] gmail [dot] com

Core Location and Map Kit: Bringing Your Own Maps [Voices That Matter: iPhone 2010]

  • 1.
    Core Location &Map Kit: Bringing Your Own Maps Chris Adamson @invalidname Voices That Matter: iPhone Developer Conference 2010
  • 2.
    What You’ll LearnToday Getting current location information from Core Location Getting a map UI from Map Kit Getting route and other geo-data from third-party providers
  • 3.
  • 4.
    Location technologies iniPhone Wi-ïŹ Can look up location via Skyhook Cellular network Can triangulate location from cell towers GPS
  • 5.
    Beyond location Course: whichdirection are you going? Speed: how fast are you moving? Heading: which direction are you facing? Altitude: how high up are you?
  • 6.
    Core Location Abstracts awaythe speciïŹc location technologies No direct access to any speciïŹc location technology (GPS, etc.) Lets you focus on your use of location data
  • 7.
    Basic Core Locationuse Add Core Location framework to your project #import <CoreLocation/ CoreLocation.h>
  • 8.
    Core Location framework Threeclasses CLHeading, CLLocation, CLLocationManager One Protocol CLLocationManagerDelegate
  • 9.
  • 10.
    The CL Basics Createa CLLocationManager Set its delegate Set desired accuracy and ïŹlter Call startUpdatingLocation and/or startUpdatingHeading Handle delegate callbacks
  • 11.
    Creating a CLLocationManager locationManager= [[CLLocationManager alloc] init]; locationManager.delegate = self; locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters; locationManager.distanceFilter = 10;
  • 12.
  • 13.
    Location updates CLLocation objectcontains: coordinate — struct with latitude and longitude course, speed, altitude properties accuracy properties timestamp!
  • 14.
    Delegate implementation - (void)locationManager:(CLLocationManager*)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation { ! latLabel.text = [NSString stringWithFormat:@"%0.3f", newLocation.coordinate.latitude]; ! lngLabel.text = [NSString stringWithFormat:@"%0.3f", newLocation.coordinate.longitude]; ! altitudeLabel.text = [NSString stringWithFormat:@"%0.3f", newLocation.altitude]; ! courseLabel.text = (newLocation.course >= 0.0) ? ! ! [NSString stringWithFormat:@"%0.3f", newLocation.course] : ! ! @"N/A"; ! speedLabel.text = (newLocation.speed >= 0.0) ? ! ! [NSString stringWithFormat:@"%0.3f", newLocation.speed]: ! ! @"N/A"; ! timestampLabel.text = [timestampFormatter stringFromDate:newLocation.timestamp]; }
  • 15.
    Using coordinates Latitude andLongitude are CLLocationDegrees (doubles) -90° ≀ Latitude ≀ 90° -180° ≀ Longitude ≀ 180° 1° latitude ≅ 69 miles 1° longitude varies from 69 mi. to 0
  • 16.
    You might getan error! kCLErrorLocationUnknown - can be ignored. CL is still trying. kCLErrorDenied - user declined location services to your app. Handle with grace.
  • 17.
    Handling delegate errors -(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { ! if ([error code] == kCLErrorDenied) { ! ! userDeclinedLocationPrivilegesHUD.hidden = NO; ! } }
  • 18.
  • 19.
    Other CL pointsof interest -[CLLocation getDistanceFrom:] CLHeading properties: magneticHeading trueHeading
  • 20.
  • 21.
    Map Kit Framework toprovide map images to UIKit applications #import <MapKit/MapKit.h>
  • 22.
    Map Kit BasicUse Create an MKMapView and assign a delegate Add MKAnnotations to the map Set region or span of map Delegate handles events from map
  • 23.
  • 24.
    About the demo ConvertedCSV list of Apple Store coordinates and addresses (circa 2007) to .plist http://www.poi-factory.com/node/1700 Given starting location, gets distance to each store and sorts the POI array On tap, add nearest POI as annotation
  • 25.
    MKMapView Add to yourUI in IB or in code Requires a network connection to fetch map image tiles from Google Binds you to Google Maps terms of service
  • 26.
    MKAnnotation Protocol to representa point of interest Added to MKMapViews Properties: coordinate, title, subtitle MKPlacemark offers a concrete implementation
  • 27.
    Implementing MKAnnotation @interface MyMapAnnotation: NSObject <MKAnnotation> { ! CLLocationCoordinate2D coordinate; ! NSString *title; ! MKPinAnnotationColor color; } @property (nonatomic, readonly) CLLocationCoordinate2D coordinate; @property (nonatomic) MKPinAnnotationColor color; -(id) initWithCoordinate:(CLLocationCoordinate2D)coordinateP title:(NSString*) titleP color: (MKPinAnnotationColor) colorP; @end
  • 28.
    MKAnnotationView Visual representation ofan MKAnnotation on an MKMapView Concrete implementation: MKPinAnnotationView MKMapView dequeues and reuses MKAnnotationViews (similar to UITableView / UITableViewCell)
  • 29.
    Adding annotations tomap -(IBAction) plusButtonTapped: (id) sender { ! // todo: bounds check ! NSDictionary *poiDict = [poiArray objectAtIndex:nextPoiIndex++]; ! CLLocationCoordinate2D poiCoordinate; ! poiCoordinate.latitude = [[poiDict valueForKey:@"latitude"] doubleValue]; ! poiCoordinate.longitude = [[poiDict valueForKey:@"longitude"] doubleValue]; ! MyMapAnnotation *poiAnnotation = [[MyMapAnnotation alloc] ! ! ! ! ! ! ! ! ! initWithCoordinate:poiCoordinate ! ! ! ! ! ! ! ! ! title:[poiDict valueForKey:@"name"] ! ! ! ! ! ! ! ! ! color:MKPinAnnotationColorRed! ]; ! [mapView addAnnotation:poiAnnotation]; ! [self adjustMapZoom]; }
  • 30.
    Zooming MKMapView, pt.1 -(void) adjustMapZoom { ! if ([mapView.annotations count] == 1) { ! ! // if only one point, zoom smartly around it ! ! [mapView setRegion:MKCoordinateRegionMakeWithDistance ! ! ! ! ! ! ! ([[mapView.annotations objectAtIndex:0] coordinate], ! ! ! ! ! ! ! 2000, // 2 km lat span ! ! ! ! ! ! ! 2000) // 2 km lng span ! ! ! ! animated: YES]; }
  • 31.
    Zooming MKMapView, pt.2 ! else { ! ! // find a region encompassing all annotations ! ! CLLocationDegrees maxLatitude = -180; ! ! CLLocationDegrees minLatitude = 180; ! ! CLLocationDegrees maxLongitude = -180; ! ! CLLocationDegrees minLongitude = 180; ! ! ! ! for (id<MKAnnotation> annotation in [mapView annotations]) { ! ! ! if ([annotation coordinate].latitude > maxLatitude) { ! ! ! ! maxLatitude = [annotation coordinate].latitude; ! ! ! } ! ! ! if ([annotation coordinate].latitude < minLatitude) { ! ! ! ! minLatitude = [annotation coordinate].latitude; ! ! ! } ! ! ! if ([annotation coordinate].longitude > maxLongitude) { ! ! ! ! maxLongitude = [annotation coordinate].longitude; ! ! ! } ! ! ! if ([annotation coordinate].longitude < minLongitude) { ! ! ! ! minLongitude = [annotation coordinate].longitude; ! ! ! } ! ! } ! ! ! ! CLLocation *maxPoint = [[[CLLocation alloc] initWithLatitude:maxLatitude ! ! ! ! ! ! longitude:maxLongitude] autorelease]; ! ! CLLocation *minPoint = [[[CLLocation alloc] initWithLatitude:minLatitude ! ! ! ! ! ! longitude:minLongitude] autorelease]; ! ! CLLocationDistance distance = [maxPoint getDistanceFrom:minPoint]; ! ! MKCoordinateRegion region; ! ! region.center.latitude = (maxPoint.coordinate.latitude + minPoint.coordinate.latitude) / 2.0; ! ! region.center.longitude = (maxPoint.coordinate.longitude + minPoint.coordinate.longitude) / 2.0; ! ! region.span.latitudeDelta = (distance / METERS_PER_DEGREE_LATITUDE) * 1.10; ! ! region.span.longitudeDelta = 0.0; ! ! MKCoordinateRegion adjustedRegion = [mapView regionThatFits:region]; ! ! [mapView setRegion:adjustedRegion animated:YES]; ! }
  • 32.
    MKMapViewDelegate Called when regionchanges (scaling, scrolling), and as map images load Provides event when a “callout accessory view” is tapped (e.g., disclosure button) Requests views for newly-added annotations
  • 33.
    Providing views forannotations - (MKAnnotationView *)mapView:(MKMapView *)mapViewP viewForAnnotation:(id <MKAnnotation>)annotation { ! MKPinAnnotationView *pinView = (MKPinAnnotationView*) [mapViewP dequeueReusableAnnotationViewWithIdentifier:@"pin"]; ! if (pinView) { ! ! pinView.annotation = annotation; ! } else { ! ! pinView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"pin"] autorelease]; ! } ! ! pinView.animatesDrop = YES; ! pinView.canShowCallout = YES; ! if ([annotation isKindOfClass:[MyMapAnnotation class]]) { ! ! MyMapAnnotation *myAnnotation = (MyMapAnnotation*) annotation; ! ! pinView.pinColor = myAnnotation.color; ! } ! return pinView; }
  • 34.
  • 35.
    Also: Reverse Geocoding Geocoding:ïŹnding coordinates for an address. Map Kit doesn’t do this. Reverse geocoding: ïŹnding address for given coordinates MKReverseGeocoder and MKReverseGeocoderDelegate You must display a map to use this API
  • 36.
  • 38.
  • 39.
    This is nota body of water
  • 40.
    This is nota street
  • 41.
    Demo: Why AppleStore Finder is Broken
  • 42.
    Map Kit Doesn’tProvide Maps Map Kit provides map images Nothing in Map Kit has any awareness of geography, transportation, political borders, cultural or regional distinctions, etc. All it does is push pixels to your screen
  • 44.
    Bringing Your OwnMaps Google Maps terms prohibit Apple from providing map data in Map Kit You have to provide it yourself Typically via a third-party
  • 45.
  • 46.
    What Map ProvidersOffer POI search — businesses, geographic features, roads, etc. Directions and trafïŹc info Geocoding and reverse geocoding Map images
  • 47.
    Map data access On-boarddatabase Network access
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
    Calling Map Providers Safestapproach may be to use a network API, using Cocoa’s URL Loading Service or CFNetwork APIs e.g., MapQuest web services NAVTEQ Smart APIs for Mobile - iPhone-only API
  • 54.
  • 55.
    Fix Strategy Sort POIsby linear distance, then use a map provider to get driving distances for the ïŹrst few and re-sort Sort later POIs as more are consumed from array
  • 56.
    Using MapQuest API Signup at developer.mapquest.com Get an API key Provide this key with each request Compose webservice request URL Parse XML result
  • 57.
    Send web servicerequest #define MQ_DIRECTIONS_REQUEST_FORMAT @"http://www.mapquestapi.com/directions/v1/route?key=%@&from=%f,%f&to=%@, %@&outFormat=xml" -(void) addDrivingDistanceToPoiDict: (NSMutableDictionary*) poiDict { ! NSString *poiLat = [poiDict valueForKey: @"latitude"]; ! NSString *poiLng = [poiDict valueForKey: @"longitude"]; ! NSString *mqURLS = [NSString stringWithFormat:MQ_DIRECTIONS_REQUEST_FORMAT, ! ! ! ! ! ! MQ_APP_KEY, ! ! ! ! ! ! homeCoordinate.latitude, homeCoordinate.longitude, ! ! ! ! ! ! poiLat, poiLng]; ! NSURL *mqURL = [NSURL URLWithString:mqURLS]; ! NSURLRequest *mqURLRequest = [NSURLRequest requestWithURL:mqURL]; ! NSURLResponse *mqURLResponse = nil; ! NSError *mqURLError = nil; ! NSData *routeData = [NSURLConnection sendSynchronousRequest:mqURLRequest ! ! ! ! ! ! ! ! ! ! returningResponse:&mqURLResponse ! ! ! ! ! ! ! ! ! ! error:&mqURLError]; ! MQDirectionsParser *parser = [[MQDirectionsParser alloc] initWithXML:routeData];
  • 58.
    Get web serviceresult <?xml version="1.0" encoding="UTF-8"?> <response> <info> <statusCode>0</statusCode> <messages/> <copyright> <imageUrl>http://tile21.mqcdn.com/res/mqlogo.gif</imageUrl> <imageAltText>© 2010 MapQuest, Inc.</imageAltText> <text>© 2010 MapQuest, Inc.</text> </copyright> </info> <route> <sessionId>4bd0285d-01bc-0000-02b7-3827-001e4f148321</sessionId> <options> <shapeFormat>raw</shapeFormat> <generalize>-1.0</generalize> <maxLinkId>0</maxLinkId> <narrativeType>text</narrativeType> <stateBoundaryDisplay>true</stateBoundaryDisplay> <countryBoundaryDisplay>true</countryBoundaryDisplay> <sideOfStreetDisplay>true</sideOfStreetDisplay> <destinationManeuverDisplay>true</destinationManeuverDisplay> <avoidTimedConditions>false</avoidTimedConditions> <timeType>0</timeType> <routeType>FASTEST</routeType> <locale>en_US</locale> <unit>M</unit> <tryAvoidLinkIds/> <mustAvoidLinkIds/> <manmaps>true</manmaps> </options> <boundingBox> <ul> <lat>47.663799</lat> <lng>-122.348854</lng> </ul> <lr> <lat>47.60448</lat>
  • 59.
    Create an XMLparser -(id) initWithXML: (NSData*) xml { ! if (self = [super init]) { ! ! routeDict = [[NSMutableDictionary alloc] init]; ! ! routeDistance = -1; ! ! ! ! NSXMLParser *parser = [[NSXMLParser alloc] initWithData:xml]; ! ! [parser setDelegate:self]; ! ! [parser parse]; ! } ! return self; }
  • 60.
    Notice interesting tags - (void) parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName ! ! ! ! ! ! ! ! namespaceURI:(NSString *)namespaceURI ! ! ! ! ! ! ! ! qualifiedName:(NSString *)qualifiedName ! ! ! ! ! ! ! ! attributes:(NSDictionary *)attributeDict { ! // only care about "distance" and "narrative" tags ! if ([elementName isEqualToString:@"distance"] | ! ! [elementName isEqualToString:@"narrative"]) { ! ! currentCharacters = [[NSMutableString alloc] init]; ! } ! if ([elementName isEqualToString:@"legs"]) { ! ! narrativeArray = [[NSMutableArray alloc] init]; ! } } - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { ! [currentCharacters appendString:string]; }
  • 61.
    Retrieve values fromtags - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName ! ! ! ! ! ! ! ! ! namespaceURI:(NSString *)namespaceURI ! ! ! ! ! ! ! ! ! qualifiedName:(NSString *)qName { ! if ([elementName isEqualToString:@"distance"]) { ! ! // if this is first distance, not part of a leg, then it's the whole trip ! ! if (routeDistance == -1) { ! ! ! routeDistance = [currentCharacters doubleValue]; ! ! } ! } ! if ([elementName isEqualToString:@"narrative"]) { ! ! [narrativeArray addObject:currentCharacters]; ! } ! ! [currentCharacters release]; ! currentCharacters = nil; }
  • 62.
    Demo: Fixed AppleStore Finder
  • 64.
  • 65.
    TNSAAFL! All third-party mappingproviders have terms of service You should actually read them Because you’ll probably violate them without knowing it
  • 66.
    Interesting MapQuest TOS 01. (d) Traffic information shall not be (i) used for Real-Time Navigation; (ii) used in conjunction with in-car or stand alone portable navigation devices; or (iii) used for the primary purpose of Your page or application. For the purposes of this Agreement, “Real-Time Navigation” shall mean using a sensor to determine location and providing contemporaneous turn-by-turn directions as the user moves through the route. RESTRICTIONS. Except as expressly authorized by MapQuest, You shall not: â–Ș derive results from the Service based on sensor-derived location data or information or input in the form of coordinate data, provided that a coordinate location or location derived by a single sensor, including without limitation a sensor incorporated into, connected to or in communication with any mobile device or system, may be used solely as an origin or destination in deriving a map or direction; â–Ș [
] â–Ș use the Service with products, systems or applications capable of navigation, positioning, tracking or routing of a movable asset; http://developer.mapquest.com/web/info/terms-of- use-free
  • 67.
    Interesting Google MapsTOS Your Maps API Implementation must be generally accessible to users without charge. You may require users to log in to your Maps API Implementation if you do not require users to pay a fee. Unless you have entered into a separate written agreement with Google or obtained Google's written permission, your Maps API Implementation must not: (a) require a fee-based subscription or other fee-based restricted access; or (b) operate only behind a ïŹrewall or only on an internal network (except during the development and testing phase). http://code.google.com/apis/maps/terms.html
  • 68.
    Interesting Bing MapsTOS If you would like to develop or host an Application that is designed to access and use the service for commercial, non-commercial or government use, provided that such use is not educational or non-profit as defined under Section 2(i), and your Application and content will be available publically without restriction (for example, login or password must not be required) you may do so without entering into a MWS/BM agreement or licensing the service through Microsoft Volume Licensing by complying with the following terms: In addition to all of the restrictions on educational and non-profit use, including the limitations on Traffic Data, set forth in Section 2(i) above, the following restriction also applies: ‱ You may not exceed more than 125,000 sessions or 500,000 transactions, both as defined in the SDKs, in any twelve month period http://www.microsoft.com/maps/product/terms.html
  • 69.
    More Bing MapsTOS Restrictions on your use: We do have some restrictions on your use of the service. You may not: ‱ copy, store, archive, or create a database of the content, except that geocodes may be stored locally only for use with your Applications; ‱ exceed 50,000 geocoding transactions or requests in any 24 hour period; ‱ download more than 250 points of interest at any one time; ‱ use the service for business asset tracking, fleet management, or dispatch; ‱ present or alert an end user to individual maneuvers of a route in any way that is synchronized with the end-user’s sensor-based position along the route, (e.g. “real-time” navigation); ‱ [
] ‱ integrate the Bing Maps Platform or any of its content with any other mapping platform; http://www.microsoft.com/maps/product/terms.html
  • 70.
    Mapping terms ofservice Most free terms prohibit: Use in commercial apps Use with a GPS-determined location Use with competitors’ maps or data You can get around some of these with commercial licensing
  • 71.
    Other hazards Handle baddata gracefully Expect mis-formed or missing tags and values Expect some data to be out of date (e.g., closed businesses) Handle network latency gracefully
  • 72.
  • 73.
    iPhone Mapping Core Locationis great Map Kit is great Using them together is great But

  • 74.
    More Mapping Really understandingthe relationship between multiple points of interest requires real map data Look to third parties for this You will probably need a commercial license
  • 75.
  • 76.
  • 77.
    Thanks! http://www.subfurther.com/blog @invalidname invalidname [at] gmail [dot] com