Geocoding Location Data

Location data is usually returned as a pair of numerical values that represent 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 aren’t 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 get 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 latitude and longitude values. Reverse geocoding is supported in all versions of iOS, but forward geocoding is supported only in iOS 5.0 and later. Both reverse and forward geocoding are supported in OS X v10.8 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 can’t connect to the service it needs and must therefore return an appropriate error. Here are some rules of thumb for creating geocoding requests:

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.

Listing 4-1 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 4-1  Geocoding a location using CLGeocoder

@implementation MyGeocoderViewController (CustomGeocodingAdditions)
- (void)geocodeLocation:(CLLocation*)location forAnnotation:(MapLocation*)annotation
{
    if (!geocoder)
        geocoder = [[CLGeocoder alloc] init];
 
    [geocoder 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.

Converting Place Names Into Coordinates

Use the CLGeocoder class with a dictionary of Address Book information or a simple string to initiate forward-geocoding requests. 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. For example, any of the following strings would yield results:

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 you provide. For this reason, providing street, city, province, and country information is much more likely to return a single address than providing only street and city information. The completion handler block you pass to the geocoder should be prepared to handle multiple placemarks, as shown below.

[geocoder geocodeAddressString:@"1 Infinite Loop"
     completionHandler:^(NSArray* placemarks, NSError* error){
         for (CLPlacemark* aPlacemark in placemarks)
         {
             // Process the placemark.
         }
}];