Geocoding in iOS



Not so long ago I wrote an Introduction to MapKit and at the end of that article I was suggesting, as an improvement, the ability to obtain real location data from the web instead of hard-cording locations. In this article I want to introduce you to geocoding as a way to obtain information about geographical locations. Geocoding can be forward, when we obtain the geographical coordinates (latitude and longitude) from other location data such as postal addresses, or reverse, when we obtain the address of a location by having the latitude and longitude as inputs.

In this article, I want to show how to do geocoding using both Apple Maps and Google Maps. The major difference between the two is that for Apple Maps you do not need to access an external URL to get location data, while for Google Maps you do. I will start with Google Maps geocoding, so let’s create a new Xcode project first. In ViewController add two properties, one to hold the base URL for the Google Maps API, and a second one for your API key.

let baseUrl = "https://maps.googleapis.com/maps/api/geocode/json?"
let apikey = "YOUR_API_KEY"

Next, let’s see how forward geocoding is done using Google Maps. For that let’s create a new method:

func getLatLngForZip(zipCode: String) {
    let url = NSURL(string: "\(baseUrl)address=\(zipCode)&key=\(apikey)")
    let data = NSData(contentsOfURL: url!)
    let json = try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments) as! NSDictionary
    if let result = json["results"] as? NSArray {
        if let geometry = result[0]["geometry"] as? NSDictionary {
            if let location = geometry["location"] as? NSDictionary {
                let latitude = location["lat"] as! Float
                let longitude = location["lng"] as! Float
                print("\n\(latitude), \(longitude)")
            }
        }
    }
}

This method looks pretty straightforward. First it constructs a URL using the base URL, a zip code (provided as input argument), and the API key, then it serializes the data it finds at this URL into JSON feed, and finally it parses the JSON response to obtain the latitude and longitude we were looking for. Now let’s call this method in viewDidLoad():

getLatLngForZip("95014")

If you run the project now, you will see the following output in the console:

37.3132, -122.072

It worked! Next, let’s write a method for reverse geocoding using Google Maps:

func getAddressForLatLng(latitude: String, longitude: String) {
    let url = NSURL(string: "\(baseUrl)latlng=\(latitude),\(longitude)&key=\(apikey)")
    let data = NSData(contentsOfURL: url!)
    let json = try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments) as! NSDictionary
    if let result = json["results"] as? NSArray {
        if let address = result[0]["address_components"] as? NSArray {
            let number = address[0]["short_name"] as! String
            let street = address[1]["short_name"] as! String
            let city = address[2]["short_name"] as! String
            let state = address[4]["short_name"] as! String
            let zip = address[6]["short_name"] as! String
            print("\n\(number) \(street), \(city), \(state) \(zip)")
        }
    }
}

This method is pretty similar to the first one. The only thing we changed is we provided the latitude and longitude as inputs to get the address information. Let’s call this method in viewDidLoad():

getAddressForLatLng("37.331", longitude: "-122.031")

Run the project and you should see the following output at the console:

1 Infinite Loop, Cupertino, CA 95014

This is great! We were able to get the exact location of Apple’s headquarters just by using the latitude and longitude. Ok, it’s time to move on to using Apple Maps now and see how that works. First, let’s import what we need:

import CoreLocation
import AddressBookUI

Then, let’s write a method for forward geocoding using Apple Maps:

func forwardGeocoding(address: String) {
    CLGeocoder().geocodeAddressString(address, completionHandler: { (placemarks, error) in
        if error != nil {
            print(error)
            return
        }
        if placemarks?.count > 0 {
            let placemark = placemarks?[0]
            let location = placemark?.location
            let coordinate = location?.coordinate
            print("\nlat: \(coordinate!.latitude), long: \(coordinate!.longitude)")
            if placemark?.areasOfInterest?.count > 0 {
                let areaOfInterest = placemark!.areasOfInterest![0]
                print(areaOfInterest)
            } else {
                print("No area of interest found.")
            }
        }
    })
}

Let’s look at what this method does. First, it uses the CLGeocoder class from the CoreLocation framework to geocode an address we provide as input. In the method’s completion handler we parse the response and see what placemark objects we found. We are particularly interested in the first one found, if there are more than one. Then we get the location coordinates and any areas of interest - as properties of the placemark. Now let’s call this method in viewDidLoad():

forwardGeocoding("1 Infinite Loop")

If you run the app now, you will see the data you would expect:

lat: 37.3316851, long: -122.0300674
Apple Inc.

Now let’s write a method for reverse geocoding using Apple Maps:

func reverseGeocoding(latitude: CLLocationDegrees, longitude: CLLocationDegrees) {
    let location = CLLocation(latitude: latitude, longitude: longitude)
    CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
        if error != nil {
            print(error)
            return
        }
        else if placemarks?.count > 0 {
            let pm = placemarks![0]
            let address = ABCreateStringWithAddressDictionary(pm.addressDictionary!, false)
            print("\n\(address)")
            if pm.areasOfInterest?.count > 0 {
                let areaOfInterest = pm.areasOfInterest?[0]
                print(areaOfInterest!)
            } else {
                print("No area of interest found.")
            }
        }
    })
}

The only detail we need to pay attention to in this method is the ABCreateStringWithAddressDictionary class from the AddressBookUI framework. This class does the address parsing for us which is really convenient as we do not need to care for any of the location fields, if we just want to see the entire address. Let’s also call this method in viewDidLoad():

reverseGeocoding(37.3316851, longitude: -122.0300674)

Run the app and you will see this nicely formatted output in the console:

1 Infinite Loop
Cupertino CA 95014
United States
Apple Inc.

You can read more about Apple Geocoding as well as Google Geocoding. As a next step, you could take this information and place pins on a map view, for example, and show areas of interest. You could also implement a search feature for finding addresses while you’re typing. You could also implement an autocompletion feature while at it. There are many opportunities you could think of.

Until next time!