Geocoding Location Data
Location data is usually returned as a pair of numerical values representing the latitude and longitude of the corresponding point on the globe. These coordinates offer a precise and easy way to specify location data in your code but they are not very intuitive for users. Instead of global coordinates, users are more likely to understand a location that is specified using information they are more familiar with such as street, city, state, and country information. For situations where you want to display a user friendly version of a location, you can use a geocoder object to obtain that information.
About Geocoder Objects
A geocoder object uses a network service to convert between latitude and longitude values and a user-friendly placemark, which is a collection of data such as the street, city, state, and country information. Reverse geocoding is the process of converting a latitude and longitude into a placemark. Forward geocoding is the process of converting place name information into a latitude and longitude value. Reverse geocoding is supported in all versions of iOS but forward geocoding is supported only in iOS 5.0 and later.
Because geocoders rely on a network service, a live network connection must be present in order for a geocoding request to succeed. If a device is in Airplane mode or the network is currently not configured, the geocoder cannot connect to the service it needs and must therefore return an appropriate error. Here are some rules of thumb for creating geocoding requests:
Send at most one geocoding request for any one user action.
If the user performs multiple actions that involve geocoding the same location, reuse the results from the initial geocoding request instead of starting individual requests for each action.
When you want to update the location automatically (such as when the user is moving), reissue the geocoding request only when the user's location has moved a significant distance and after a reasonable amount of time has passed. For example, in a typical situation, you should not send more than one geocoding request per minute.
Do not start a geocoding request at a time when the user will not see the results immediately. For example, do not start a request if your app is in the background or was interrupted and is currently in the inactive state.
Converting Coordinates Into Place Name Information
In iOS, you can use either the CLGeocoder or MKReverseGeocoder class to handle reverse-geocoding requests. The CLGeocoder is the preferred class to use and is available in iOS 5.0 and later. However, if your app must run on earlier versions of iOS, you can use the MKReverseGeocoder class.
Getting Placemark Information using CLGeocoder
To initiate a reverse-geocoding request using the CLGeocoder class, create an instance of the class and call the reverseGeocodeLocation:completionHandler: method. The geocoder object initiates the reverse geocoding request asynchronously and delivers the results to the block object you provide. The block object is executed whether the request succeeds or fails. In the event of a failure, an error object is passed to the block indicating the reason for the failure.
“Converting Coordinates Into Place Name Information” shows an example of how to reverse geocode a point on the map. The only code specific to the geocoding request are the first few lines, which allocate the geocoder object as needed and call the reverseGeocodeLocation:completionHandler: method to start the reverse-geocoding operation. (The geocoder variable represents a member variable used to store the geocoder object.) The rest of the code is specific to the sample app itself. In this case, the sample app stores the placemark with a custom annotation object (defined by the MapLocation class) and adds a button to the callout of the corresponding annotation view.
Listing 3-1 Geocoding a location using CLGeocoder
@implementation MyGeocoderViewController (CustomGeocodingAdditions) |
- (void)geocodeLocation:(CLLocation*)location forAnnotation:(MapLocation*)annotation |
{ |
if (!geocoder) |
geocoder = [[CLGeocoder alloc] init]; |
[theGeocoder reverseGeocodeLocation:location completionHandler: |
^(NSArray* placemarks, NSError* error){ |
if ([placemarks count] > 0) |
{ |
annotation.placemark = [placemarks objectAtIndex:0]; |
// Add a More Info button to the annotation's view. |
MKPinAnnotationView* view = (MKPinAnnotationView*)[map viewForAnnotation:annotation]; |
if (view && (view.rightCalloutAccessoryView == nil)) |
{ |
view.canShowCallout = YES; |
view.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure]; |
} |
} |
}]; |
} |
@end |
The advantage of using a block object in a sample like this is that information (such as the annotation object) can be easily captured and used as part of the completion handler. Without blocks, the process of wrangling data variables becomes much more complicated.
Getting Placemark Information from the Reverse Geocoder
For apps running on iOS 4.1 and earlier, you must perform reverse-geocoding requests using the MKReverseGeocoder class of the Map Kit framework. This class uses a delegate-based approach for geocoding a single location. This means that you can use a single instance of the MKReverseGeocoder class only once. In addition, the Google terms of service require that the MKReverseGeocoder class be used in conjunction with a Google map.
To initiate a reverse geocoding request, create an instance of the MKReverseGeocoder class, assign an appropriate object to the delegate property, and call the start method. If the query completes successfully, your delegate’s reverseGeocoder:didFindPlacemark: method is called and passed an MKPlacemark object with the results. If there is a problem reverse geocoding the location, the reverseGeocoder:didFailWithError: method is called instead.
Listing 3-2 shows the code required to use a reverse geocoder. Upon successful completion of the geocoding operation, the code adds a button to the annotation view’s callout so that it can display the placemark information. Because the annotation is not automatically available to the delegate, the custom annotationForCoordinate: method is included to find the appropriate annotation object from the map view.
Listing 3-2 Geocoding a location using MKReverseGeocoder
@implementation MyGeocoderViewController (CustomGeocodingAdditions) |
- (void)geocodeLocation:(CLLocation*)location forAnnotation:(MapLocation*)annotation |
{ |
MKReverseGeocoder* theGeocoder = [[MKReverseGeocoder alloc] initWithCoordinate:location.coordinate]; |
theGeocoder.delegate = self; |
[theGeocoder start]; |
} |
// Delegate methods |
- (void)reverseGeocoder:(MKReverseGeocoder*)geocoder didFindPlacemark:(MKPlacemark*)place |
{ |
MapLocation* theAnnotation = [map annotationForCoordinate:place.coordinate]; |
if (!theAnnotation) |
return; |
// Associate the placemark with the annotation. |
theAnnotation.placemark = place; |
// Add a More Info button to the annotation's view. |
MKPinAnnotationView* view = (MKPinAnnotationView*)[map viewForAnnotation:annotation]; |
if (view && (view.rightCalloutAccessoryView == nil)) |
{ |
view.canShowCallout = YES; |
view.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure]; |
} |
} |
- (void)reverseGeocoder:(MKReverseGeocoder*)geocoder didFailWithError:(NSError*)error |
{ |
NSLog(@"Could not retrieve the specified place information.\n"); |
} |
@end |
@implementation MKMapView (GeocoderAdditions) |
- (MapLocation*)annotationForCoordinate:(CLLocationCoordinate2D)coord |
{ |
// Iterate through the map view's list of coordinates |
// and return the first one whose coordinate matches |
// the specified value exactly. |
id<MKAnnotation> theObj = nil; |
for (id obj in [self annotations]) |
{ |
if (([obj isKindOfClass:[MapLocation class]])) |
{ |
MapLocation* anObj = (MapLocation*)obj; |
if ((anObj.coordinate.latitude == coord.latitude) && |
(anObj.coordinate.longitude == coord.longitude)) |
{ |
theObj = anObj; |
break; |
} |
} |
} |
return theObj; |
} |
@end |
Converting Place Names Into Coordinates
In iOS 5.0 and later, you use the CLGeocoder class to initiate forward-geocoding requests using either a dictionary of Address Book information or a simple string. There is no designated format for string-based requests; delimiter characters are welcome, but not required, and the geocoder server treats the string as case-insensitive. Therefore, any of the following strings would yield results:
"Apple Inc”
"1 Infinite Loop”
"1 Infinite Loop, Cupertino, CA USA”
The more information you can provide to the forward geocoder, the better the results returned to you. The geocoder object parses the information you give it and, if it finds a match, returns some number of placemark objects. The number of returned placemark objects depends greatly on the specificity of the information provided. Thus, providing street, city, province, and country information is much more likely to return a single address than just street and city information. The completion handler block you pass to the geocoder should therefore be prepared to handle multiple placemarks, as shown in the following example:
[geocoder geocodeAddressString:@"1 Infinite Loop" |
completionHandler:^(NSArray* placemarks, NSError* error){ |
for (CLPlacemark* aPlacemark in placemarks) |
{ |
// Process the placemark. |
} |
}]; |
© 2012 Apple Inc. All Rights Reserved. (Last updated: 2012-09-19)