将坐标转换为城市名?

53

如何使用MapKit从坐标获取地址?

我有这段代码,当在地图上长按时,它会获取坐标:

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)"
                }
}

我想在annotation.title中打印完整的地址(街道、城市、邮编、国家)。

9个回答

83

SWIFT 4.2:编辑


MapKit框架提供了一种从坐标获取地址详细信息的方法。

您需要使用地图工具的反向地理编码。使用CLGeocoder类可以从地址获取位置和从位置(坐标)获取地址。方法reverseGeocodeLocation将从坐标返回地址详细信息。

此方法接受CLLocation作为参数,并返回包含地址字典的CLPlacemark

因此,现在以上方法将被更新如下:

@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)
                }
        })
    }
}

3
使用条件解包地址键,而不是提取键值两次,这样不是更好吗? - Abizern
1
这不是我想要的。应该是if let locationName = placeMark.addressDictionary["Name"] { println(locationName) } - Abizern
@Abizern 你的意思是用字符串初始化键吗?(使用条件运算符)对吗? - Kampai
为什么要将其转换为NSString?你所做的只是打印结果。 - Abizern
如果我去掉类型转换,它会显示警告“常量<var-name>被推断为类型'AnyObject',可能是意外的”。可能是因为我使用的是旧版本的Xcode 6.0.1(6A317)。但你关于打印结果的想法是正确的。现在,可选关键字不会在控制台中打印出来。 - Kampai
显示剩余4条评论

37

对于Swift 3:Swift 4:

首先,您需要在info.plist中设置允许接收用户GPS。

enter image description here

设置:使用随机字符串设置NSLocationWhenInUseUsageDescription。 和/或:使用随机字符串设置NSLocationAlwaysUsageDescription

然后我设置了一个类来获取所需的数据,如邮政编码、城镇、国家等:

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)

                }

            }

        }

    }

}

被调用者:

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 {
               //
            }

        }

    }

}

完成


谢谢,非常好的解决方案。我做了一些更改,currentLocation是空的,所以我从viewController传递了它。 - Mina

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 ?? ""
    }
}

旧/过时的答案:

感谢@Kampai的回答,这是一种适用于Swift 3 并且更加安全(无需强制使用!)的方法:

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)
    }
})

不要忘记 NSLocationWhenInUseUsageDescriptionNSLocationAlwaysUsageDescription 键。


22

感谢 @Kampi 提供的内容。以下是更新后的 Swift 2.0 (Xcode 7) 版本:

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)
        }
    }
}

你是否曾经遇到过它多次运行这个循环的问题?我的代码有一个问题,它会在停止之前输出我的城市名称三次,这导致我的应用程序出现问题。我的SO帖子/问题在这里 - Matty

10

感谢 @Kampai 的回答,我稍作修改使其适用于 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)
        }

结果:

[
    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
]

感谢 superarts.org。 - Chetan Prajapati

6

Swift 4.2 尽可能简单,查看苹果文档并根据需要进行修改:

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)
     })
}

5

更新 Swift 4

addressDictionary 在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)
}
})

可以检索更多数据

名称,街道,子街道,地区,子地区,行政区,子行政区,邮编,ISO国家代码,国家,内陆水域,感兴趣的区域


要使用此方法获取城市名称,您必须使用placeMark.locality,谢谢,它对我有用。 - Giovanny Piñeros
是的。在描述中找到更多信息。 - iSrinivasan27

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))")



})
}

使用这段代码,可以解决问题。


0
在 didUpdateToLocation 方法中:
- (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);
        }
    }];
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接