Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reverse geocoding in Swift 4

I'm attempting to write a simple method that's fed CLLocationDegrees and returns a CLPlacemark. Looking at Apple's documentation, it seems like a simple task.

Below is what I've dumped into a playground:

import CoreLocation
// this is necessary for async code in a playground
import PlaygroundSupport 
// this is necessary for async code in a playground
PlaygroundPage.current.needsIndefiniteExecution = true

func geocode(latitude: CLLocationDegrees, longitude: CLLocationDegrees) -> CLPlacemark? {
    let location = CLLocation(latitude: latitude, longitude: longitude)
    let geocoder = CLGeocoder()
    var placemark: CLPlacemark?
    geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
        if error != nil {
            print("something went horribly wrong")
        }
        if let placemarks = placemarks {
            placemark = placemarks.first
        }
    }
    return placemark
}
let myPlacemark = geocode(latitude: 37.3318, longitude: 122.0312)

As it stands, my method is returning nil. I'm not sure where my error lies, but I rest assured it's something starlingly stupid on my part. Thank you for reading.

like image 902
Adrian Avatar asked Oct 22 '17 00:10

Adrian


People also ask

How do you reverse geocoding in Swift?

Reverse Geocoding With Swift. Reverse geocoding is a simple concept. We hand the CLGeocoder class a set of coordinates, latitude and longitude, and ask it for the corresponding address, a physical location that has meaning to the user.

What is CLGeocoder?

An interface for converting between geographic coordinates and place names.

How does reverse geocoding work?

Reverse geocoding identifies locations nearest to a latitude and longitude coordinate, enabling increased reliability of the location data. Even when addresses are unavailable, reverse geocoding software can provide corresponding addresses, based on nearby landmarks, for geolocation queries.

What is geocoding in Swift?

Geocoding is the computational process of transforming a postal address description to a location on the Earth's surface (spatial representation in numerical coordinates). Wikipedia. The free and easy way to implement a Geocoding function on iOS is using CoreLocation and the CLGeocoder class.


1 Answers

CLGeocoder methods are asynchronous. What you would need a completion handler:

import UIKit
import CoreLocation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

func geocode(latitude: Double, longitude: Double, completion: @escaping (_ placemark: [CLPlacemark]?, _ error: Error?) -> Void)  {
    CLGeocoder().reverseGeocodeLocation(CLLocation(latitude: latitude, longitude: longitude)) { placemark, error in
        guard let placemark = placemark, error == nil else {
            completion(nil, error)
            return
        }
        completion(placemark, nil)
    }
}

or simply:

func geocode(latitude: Double, longitude: Double, completion: @escaping (_ placemark: [CLPlacemark]?, _ error: Error?) -> Void)  {
    CLGeocoder().reverseGeocodeLocation(CLLocation(latitude: latitude, longitude: longitude), completionHandler: completion)
}

or extending CLLocation:

extension CLLocation {
    func geocode(completion: @escaping (_ placemark: [CLPlacemark]?, _ error: Error?) -> Void) {
        CLGeocoder().reverseGeocodeLocation(self, completionHandler: completion)
    }
}

To format your placemark as a mailing address you can use Contacts framework CNPostalAddressFormatter:

import Contacts

extension Formatter {
    static let mailingAddress: CNPostalAddressFormatter = {
        let formatter = CNPostalAddressFormatter()
        formatter.style = .mailingAddress
        return formatter
    }()
}

extension CLPlacemark {
    var mailingAddress: String? {
        postalAddress?.mailingAddress
    }
}

extension CNPostalAddress {
    var mailingAddress: String {
        Formatter.mailingAddress.string(from: self)
    }
}

placemark

Contains an array of CLPlacemark objects. For most geocoding requests, this array should contain only one entry. However, forward-geocoding requests may return multiple placemark objects in situations where the specified address could not be resolved to a single location. If the request was canceled or there was an error in obtaining the placemark information, this parameter is nil.

For more information about the CLPlacemark properties you can check this CLPlacemark


Usage:

let location = CLLocation(latitude: -22.963451, longitude: -43.198242)
location.geocode { placemark, error in
    if let error = error as? CLError {
        print("CLError:", error)
        return
    } else if let placemark = placemark?.first {
        // you should always update your UI in the main thread
        DispatchQueue.main.async {
            //  update UI here
            print("name:", placemark.name ?? "unknown")
            
            print("address1:", placemark.thoroughfare ?? "unknown")
            print("address2:", placemark.subThoroughfare ?? "unknown")
            print("neighborhood:", placemark.subLocality ?? "unknown")
            print("city:", placemark.locality ?? "unknown")
            
            print("state:", placemark.administrativeArea ?? "unknown")
            print("subAdministrativeArea:", placemark.subAdministrativeArea ?? "unknown")
            print("zip code:", placemark.postalCode ?? "unknown")
            print("country:", placemark.country ?? "unknown", terminator: "\n\n")
            
            print("isoCountryCode:", placemark.isoCountryCode ?? "unknown")
            print("region identifier:", placemark.region?.identifier ?? "unknown")
    
            print("timezone:", placemark.timeZone ?? "unknown", terminator:"\n\n")

            // Mailind Address
            print(placemark.mailingAddress ?? "unknown")
        }
    }
}

This will print

name: Morro da Saudade
address1: Rua Casuarina
address2: 597
neighborhood: Lagoa
city: Rio de Janeiro
state: RJ
subAdministrativeArea: unknown
zip code: 22011-040
country: Brazil

isoCountryCode: BR
region identifier: <-22.96345100,-43.19824200> radius 141.83
timezone: America/Sao_Paulo (current)

Rua Casuarina, 597
Lagoa
Rio de Janeiro RJ
22011-040
Brazil

like image 153
Leo Dabus Avatar answered Sep 20 '22 14:09

Leo Dabus