Convert coordinates to City name?
Asked Answered
S

9

53

How to get an address from coordinates using MapKit?

I have this code when long press on the map it gets the coordinates:

func didLongPressMap(sender: UILongPressGestureRecognizer) {

    if sender.state == UIGestureRecognizerState.Began {
        let touchPoint = sender.locationInView(self.mapView)
        let touchCoordinate = self.mapView.convertPoint(touchPoint, toCoordinateFromView: self.mapView)
        var annotation = MKPointAnnotation()
        annotation.coordinate = touchCoordinate
        annotation.title = "Your position"
        self.mapView.addAnnotation(annotation) //drops the pin
        println("lat:  \(touchCoordinate.latitude)")
        var num = (touchCoordinate.latitude as NSNumber).floatValue
        var formatter = NSNumberFormatter()
        formatter.maximumFractionDigits = 4
        formatter.minimumFractionDigits = 4
        var str = formatter.stringFromNumber(num)
        println("long: \(touchCoordinate.longitude)")
        var num1 = (touchCoordinate.longitude as NSNumber).floatValue
        var formatter1 = NSNumberFormatter()
        formatter1.maximumFractionDigits = 4
        formatter1.minimumFractionDigits = 4
        var str1 = formatter1.stringFromNumber(num1)
        self.adressLoLa.text = "\(num),\(num1)"
                }
}

and I want to print in annotation.title the complete address (street, city, zip, country).

Supposing answered 1/1, 2015 at 23:52 Comment(0)
C
83

SWIFT 4.2 : EDIT


MapKit framework does provide a way to get address details from coordinates.

You need to use reverse geocoding of map kit. CLGeocoder class is used to get the location from address and address from the location (coordinates). The method reverseGeocodeLocation will returns the address details from coordinates.

This method accepts CLLocation as a parameter and returns CLPlacemark, which contains address dictionary.

So now above method will be updated as:

@objc func didLongPressMap(sender: UILongPressGestureRecognizer) {

    if sender.state == UIGestureRecognizer.State.began {
        let touchPoint = sender.location(in: mapView)
        let touchCoordinate = mapView.convert(touchPoint, toCoordinateFrom: self.mapView)
        let annotation = MKPointAnnotation()
        annotation.coordinate = touchCoordinate
        annotation.title = "Your position"
        mapView.addAnnotation(annotation) //drops the pin
        print("lat:  \(touchCoordinate.latitude)")
        let num = touchCoordinate.latitude as NSNumber
        let formatter = NumberFormatter()
        formatter.maximumFractionDigits = 4
        formatter.minimumFractionDigits = 4
        _ = formatter.string(from: num)
        print("long: \(touchCoordinate.longitude)")
        let num1 = touchCoordinate.longitude as NSNumber
        let formatter1 = NumberFormatter()
        formatter1.maximumFractionDigits = 4
        formatter1.minimumFractionDigits = 4
        _ = formatter1.string(from: num1)
        self.adressLoLa.text = "\(num),\(num1)"

        // Add below code to get address for touch coordinates.
        let geoCoder = CLGeocoder()
        let location = CLLocation(latitude: touchCoordinate.latitude, longitude: touchCoordinate.longitude)
        geoCoder.reverseGeocodeLocation(location, completionHandler:
            {
                placemarks, error -> Void in

                // Place details
                guard let placeMark = placemarks?.first else { return }

                // Location name
                if let locationName = placeMark.location {
                    print(locationName)
                }
                // Street address
                if let street = placeMark.thoroughfare {
                    print(street)
                }
                // City
                if let city = placeMark.subAdministrativeArea {
                    print(city)
                }
                // Zip code
                if let zip = placeMark.isoCountryCode {
                    print(zip)
                }
                // Country
                if let country = placeMark.country {
                    print(country)
                }
        })
    }
}
Charleycharlie answered 2/1, 2015 at 10:29 Comment(9)
Wouldn't it be better to use conditional unwrapping of the address keys rather than extracting the key values twice?Basuto
That isn't what I meant. It should be if let locationName = placeMark.addressDictionary["Name"] { println(locationName) }Basuto
@Basuto You mean to initialise keys with String? (using conditional operator) right?Charleycharlie
Why the cast to NSString? all you are doing is printing the result.Basuto
It is because if we'll not cast it then it will print Optional(city). So its just for remove this annoying optional keyword.Charleycharlie
Since you are using if let then the result cannot be an optional, it has been unwrapped. The cast is pointless.Basuto
If I remove casting it'll show warning Constant <var-name> inferred to have type 'AnyObject', which may be unexpected. May be it is because I'm using old Xcode Version 6.0.1 (6A317). But you are right about printing result. Now optional keyword is not printed in console.Charleycharlie
In Swift 2.1 onwards atleast, you don't need to import MapKit, the CoreLocation framework itself has the CLGeocoder classInterplanetary
Force unwrap var placeMark: CLPlacemark! is dangerous. It is an optional type for a reason. You should at least do a safety check like if placeMark != nil or optional chaining before processing data.Camphorate
P
37

For Swift 3: and Swift 4

First you need to set allowance to receive User's GPS in the info.plist.

enter image description here

Set: NSLocationWhenInUseUsageDescription with a random String. And/or: NSLocationAlwaysUsageDescription with a random String.

Then I have set up a class to get the desired data like zip, town, country...:

import Foundation
import MapKit

typealias JSONDictionary = [String:Any]

class LocationServices {

    let shared = LocationServices()
    let locManager = CLLocationManager()
    var currentLocation: CLLocation!

    let authStatus = CLLocationManager.authorizationStatus()
    let inUse = CLAuthorizationStatus.authorizedWhenInUse
    let always = CLAuthorizationStatus.authorizedAlways

    func getAdress(completion: @escaping (_ address: JSONDictionary?, _ error: Error?) -> ()) {

        self.locManager.requestWhenInUseAuthorization()

        if self.authStatus == inUse || self.authStatus == always {

            self.currentLocation = locManager.location

            let geoCoder = CLGeocoder()

            geoCoder.reverseGeocodeLocation(self.currentLocation) { placemarks, error in

                if let e = error {

                    completion(nil, e)

                } else {

                    let placeArray = placemarks as? [CLPlacemark]

                    var placeMark: CLPlacemark!

                    placeMark = placeArray?[0]

                    guard let address = placeMark.addressDictionary as? JSONDictionary else {
                        return
                    }

                    completion(address, nil)

                }

            }

        }

    }

}

Called by:

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        LocationServices.shared.getAdress { address, error in

            if let a = address, let city = a["City"] as? String {
               //
            }

        }

    }

}

Done

Patterson answered 9/11, 2016 at 20:51 Comment(1)
Thanks, a very good solution. i did a little changes, currentLocation was empty, so i passed it from viewController.Overstuffed
A
31
import Foundation
import CoreLocation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

let location = CLLocation(latitude: 37.3321, longitude: -122.0318)
CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in
    
    guard let placemark = placemarks?.first else {
        let errorString = error?.localizedDescription ?? "Unexpected Error"
        print("Unable to reverse geocode the given location. Error: \(errorString)")
        return
    }
    
    let reversedGeoLocation = ReversedGeoLocation(with: placemark)
    print(reversedGeoLocation.formattedAddress)
    // Apple Inc.,
    // 1 Infinite Loop,
    // Cupertino, CA 95014
    // United States
}

struct ReversedGeoLocation {
    let name: String            // eg. Apple Inc.
    let streetNumber: String    // eg. 1
    let streetName: String      // eg. Infinite Loop
    let city: String            // eg. Cupertino
    let state: String           // eg. CA
    let zipCode: String         // eg. 95014
    let country: String         // eg. United States
    let isoCountryCode: String  // eg. US
    
    var formattedAddress: String {
        return """
        \(name),
        \(streetNumber) \(streetName),
        \(city), \(state) \(zipCode)
        \(country)
        """
    }
    
    // Handle optionals as needed
    init(with placemark: CLPlacemark) {
        self.name           = placemark.name ?? ""
        self.streetName     = placemark.thoroughfare ?? ""
        self.streetNumber   = placemark.subThoroughfare ?? ""
        self.city           = placemark.locality ?? ""
        self.state          = placemark.administrativeArea ?? ""
        self.zipCode        = placemark.postalCode ?? ""
        self.country        = placemark.country ?? ""
        self.isoCountryCode = placemark.isoCountryCode ?? ""
    }
}

Old/Deprecated answer:

Thanks to @Kampai's answer, here's a Swift 3 compatible and safer way (no forcing !):

let geoCoder = CLGeocoder()
let location = CLLocation(latitude: touchCoordinate.latitude, longitude: touchCoordinate.longitude)

geoCoder.reverseGeocodeLocation(location, completionHandler: { placemarks, error in
    guard let addressDict = placemarks?[0].addressDictionary else {
        return
    }
    
    // Print each key-value pair in a new row
    addressDict.forEach { print($0) }
    
    // Print fully formatted address
    if let formattedAddress = addressDict["FormattedAddressLines"] as? [String] {
        print(formattedAddress.joined(separator: ", "))
    }
    
    // Access each element manually
    if let locationName = addressDict["Name"] as? String {
        print(locationName)
    }
    if let street = addressDict["Thoroughfare"] as? String {
        print(street)
    }
    if let city = addressDict["City"] as? String {
        print(city)
    }
    if let zip = addressDict["ZIP"] as? String {
        print(zip)
    }
    if let country = addressDict["Country"] as? String {
        print(country)
    }
})

Don't forget NSLocationWhenInUseUsageDescription and NSLocationAlwaysUsageDescription keys

Absorbing answered 5/1, 2017 at 19:54 Comment(0)
E
22

Thanks to @Kampi for this. This is an updated Swift 2.0 (Xcode 7) Version:

func setUsersClosestCity()
{
    let geoCoder = CLGeocoder()
    let location = CLLocation(latitude: _point1.coordinate.latitude, longitude: _point1.coordinate.longitude)
    geoCoder.reverseGeocodeLocation(location)
    {
        (placemarks, error) -> Void in

        let placeArray = placemarks as [CLPlacemark]!

        // Place details
        var placeMark: CLPlacemark!
        placeMark = placeArray?[0]

        // Address dictionary
        print(placeMark.addressDictionary)

        // Location name
        if let locationName = placeMark.addressDictionary?["Name"] as? NSString
        {
            print(locationName)
        }

        // Street address
        if let street = placeMark.addressDictionary?["Thoroughfare"] as? NSString
        {
            print(street)
        }

        // City
        if let city = placeMark.addressDictionary?["City"] as? NSString
        {
            print(city)
        }

        // Zip code
        if let zip = placeMark.addressDictionary?["ZIP"] as? NSString
        {
            print(zip)
        }

        // Country
        if let country = placeMark.addressDictionary?["Country"] as? NSString
        {
            print(country)
        }
    }
}
Exsanguinate answered 28/9, 2015 at 22:8 Comment(1)
Have you ever had the issue of it running through this loop multiple times? I have an issue with my code where it'll output my city name three times before stopping and it is causing my app problems. my SO post/question is hereAstilbe
E
10

Thanks @Kampai for his answer, I revised a bit so it works with Swift 1.2:

        var geocoder = CLGeocoder()
        var location = CLLocation(latitude: IC.coordinate!.latitude, longitude: IC.coordinate!.longitude)
        geocoder.reverseGeocodeLocation(location) {
            (placemarks, error) -> Void in
            if let placemarks = placemarks as? [CLPlacemark] where placemarks.count > 0 {
                var placemark = placemarks[0]
                println(placemark.addressDictionary)
        }

Result:

[
    SubLocality: Sydney, 
    Street: 141 Harrington Street, 
    State: NSW, 
    SubThoroughfare: 141, 
    CountryCode: AU, ZIP: 2000, 
    Thoroughfare: Harrington Street, 
    Name: 141 Harrington Street, 
    Country: Australia, FormattedAddressLines: (
        "141 Harrington Street",
        "The Rocks NSW 2000",
        Australia
    ), 
    City: The Rocks
]
Ecliptic answered 25/6, 2015 at 2:38 Comment(0)
T
6

Swift 4.2 Keep it as simple as possible, look at the Apple doc and modify it as you need:

func retreiveCityName(lattitude: Double, longitude: Double, completionHandler: @escaping (String?) -> Void)
{
    let geocoder = CLGeocoder()
    geocoder.reverseGeocodeLocation(CLLocation(latitude: latitude, longitude: longitude), completionHandler:
    {
        placeMarks, error in

        completionHandler(placeMarks?.first?.locality)
     })
}
Tridactyl answered 20/11, 2018 at 19:19 Comment(0)
S
5

Update Swift 4

addressDictionary was deprecated in iOS 11.0

let geoCoder = CLGeocoder()
let location = CLLocation(latitude: 37.769193, longitude: -122.426512)
geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in

// Place details
var placeMark: CLPlacemark!
placeMark = placemarks?[0]

// Complete address as PostalAddress
print(placeMark.postalAddress as Any)  //  Import Contacts

// Location name
if let locationName = placeMark.name  {
    print(locationName)
}

// Street address
if let street = placeMark.thoroughfare {
   print(street)
}

// Country
if let country = placeMark.country {
   print(country)
}
})

More Data can be retrieved

name, thoroughfare, subThoroughfare, locality, subLocality, administrativeArea, subAdministrativeArea, postalcode, isoCountryCode, country, inlandWater, areaOfInterest

Slaphappy answered 22/1, 2018 at 14:2 Comment(2)
For getting the city name whit this method you hav to use placeMark.locality, and thanks it worked for meChromolithograph
Yeah. Find more on description.Slaphappy
I
0

In didUpdateToLocation method:

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:
    (CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{
    CLLocation *location = [locationManager location];


    CLLocationCoordinate2D coordinate = [location coordinate];


    latitude = [NSString stringWithFormat:@"%.12f", coordinate.latitude];
    longitude = [NSString stringWithFormat:@"%.12f", coordinate.longitude];

    CLLocation *location1 = [[CLLocation alloc]
                             initWithLatitude:latitude.floatValue
                             longitude:longitude.floatValue];

    self.myGeocoder = [[CLGeocoder alloc] init];

    [self.myGeocoder
     reverseGeocodeLocation:location1
     completionHandler:^(NSArray *placemarks, NSError *error) {
        if (error == nil &&
             [placemarks count] > 0){
            placemark = [placemarks lastObject];
            NSString*    vendorLocation=[NSString stringWithFormat:@"%@ %@",
                                          placemark.locality,
                                          placemark.subLocality];
            NSLog(@"%@",vendorLocation);
        }
    }];
}
Immunology answered 24/6, 2017 at 9:9 Comment(0)
M
0
func placePicker(_ viewController: GMSPlacePickerViewController, didPick place: GMSPlace) {

    viewController.dismiss(animated: true, completion: nil)
    let geoCoder = CLGeocoder()
    let location = CLLocation(latitude: place.coordinate.latitude, longitude: place.coordinate.longitude)
    geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in

        // Place details
        var placeMark: CLPlacemark!
        placeMark = placemarks?[0]

        // Address dictionary
        print(placeMark.addressDictionary as Any)
   // 

    print("Place name \(place.name)")
    print("Place address \(String(describing: place.formattedAddress))")
    print("Place attributions \(String(describing: place.attributions))")



})
}

use this code this will resolve the issue.

Molal answered 14/2, 2018 at 7:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.