在Swift中通过经纬度查找城市名称和所属国家

41

我正在使用Swift3编写应用程序,但遇到无法解决的问题。

如何基于经纬度获取城市名称和国家简称?

import UIKit
import CoreLocation

class ViewController: UIViewController, CLLocationManagerDelegate{
    let locationManager = CLLocationManager()
    var latitude: Double = 0
    var longitude: Double = 0
    override func viewDidLoad() {
        super.viewDidLoad()
        // For use when the app is open & in the background
        locationManager.requestAlwaysAuthorization()
        // For use when the app is open
        //locationManager.requestWhenInUseAuthorization()
        locationManager.delegate = self
        locationManager.startUpdatingLocation()
        if CLLocationManager.locationServicesEnabled() {
            locationManager.delegate = self
            locationManager.desiredAccuracy = kCLLocationAccuracyBest
            locationManager.startUpdatingLocation()
        }
    }
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if let location = locations.first {
            print(location.coordinate)
            latitude = location.coordinate.latitude
            longitude = location.coordinate.longitude
        }
    }
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        if (status == CLAuthorizationStatus.denied){
            showLocationDisabledpopUp()
        }
    }
    func showLocationDisabledpopUp() {
        let alertController = UIAlertController(title: "Background Location Access  Disabled", message: "We need your location", preferredStyle: .alert)
        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
        alertController.addAction(cancelAction)
        let openAction = UIAlertAction(title: "Open Setting", style: .default) { (action) in
            if let url = URL(string: UIApplicationOpenSettingsURLString){
                UIApplication.shared.open(url, options: [:], completionHandler: nil)
            }
        }
        alertController.addAction(openAction)
        self.present(alertController, animated: true, completion: nil)
    }
}

https://developer.apple.com/reference/corelocation/clgeocoder - dan
请查看我在Swift 4.1 Xcode 9.4.1中的答案。您甚至可以获取村庄名称的详细信息。https://dev59.com/XGQn5IYBdhLWcg3wroyL#51797299 - Naresh
https://medium.com/@tunvirrahmantusher/get-location-from-latitude-and-longitude-postman-46245fa35cad - Tunvir Rahman Tusher
11个回答

69
您可以使用CLGeocoder的reverseGeocodeLocation方法来获取一个CLPlacemark并获得其countrylocality属性信息。请注意,这是一个异步方法,因此在获取该信息时需要添加完成处理程序到您的方法中:
import UIKit
import MapKit
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

extension CLLocation {
    func fetchCityAndCountry(completion: @escaping (_ city: String?, _ country:  String?, _ error: Error?) -> ()) {
        CLGeocoder().reverseGeocodeLocation(self) { completion($0?.first?.locality, $0?.first?.country, $1) }
    }
}

使用
let location = CLLocation(latitude: -22.963451, longitude: -43.198242)
location.fetchCityAndCountry { city, country, error in
    guard let city = city, let country = country, error == nil else { return }
    print(city + ", " + country)  // Rio de Janeiro, Brazil
}

编辑/更新:

iOS 11或更高版本 CLPlacemark有一个postalAddress属性。您可以导入Contacts框架并使用CNPostalAddressFormatterstring(from:)方法获取本地化格式化地址。您还可以扩展CLPlacemark并添加一些计算属性来更好地描述其某些属性:

import MapKit
import Contacts

extension CLPlacemark {
    /// street name, eg. Infinite Loop
    var streetName: String? { thoroughfare }
    /// // eg. 1
    var streetNumber: String? { subThoroughfare }
    /// city, eg. Cupertino
    var city: String? { locality }
    /// neighborhood, common name, eg. Mission District
    var neighborhood: String? { subLocality }
    /// state, eg. CA
    var state: String? { administrativeArea }
    /// county, eg. Santa Clara
    var county: String? { subAdministrativeArea }
    /// zip code, eg. 95014
    var zipCode: String? { postalCode }
    /// postal address formatted
    @available(iOS 11.0, *)
    var postalAddressFormatted: String? {
        guard let postalAddress = postalAddress else { return nil }
        return CNPostalAddressFormatter().string(from: postalAddress)
    }
}

extension CLLocation {
    func placemark(completion: @escaping (_ placemark: CLPlacemark?, _ error: Error?) -> ()) {
        CLGeocoder().reverseGeocodeLocation(self) { completion($0?.first, $1) }
    }
}

使用方法:

let location = CLLocation(latitude: 37.331676, longitude: -122.030189)
location.placemark { placemark, error in
    guard let placemark = placemark else { 
        print("Error:", error ?? "nil")
        return
    }
    print(placemark.postalAddressFormatted ?? "")
}

这将会打印出:

1 Infinite Loop
Cupertino CA 95014
美国



1
非常感谢,这非常有用,但是我如何找到国家的简称呢? - joshua
1
您可以使用isocountrycode代替country。请参考:https://developer.apple.com/reference/corelocation/clplacemark/1423796-isocountrycode - Leo Dabus
@LeoDabus,我还想知道你能不能帮我解决另一个相关的问题。假设我正在使用你的fetchCityAndCountryCLLogationManager获取用户当前的latitudelongitude,将let location = CLLocation(latitude: -22.963451, longitude: -43.198242)替换为let latitude = locationManager.location?.coordinate.latitudelet longitude = locationManager.location?.coordinate.longitude,然后从那里创建一个CLLocation对象是不行的,因为当执行fetch时,locationManager还没有足够的时间来获取位置。 - Swifty
你需要在locationManager的didUpdateLocations方法中完成这个任务。 - Leo Dabus
@LeoDabus 我想获取所有的国家,如果我选择印度,则会显示该国所有的州和城市。你能帮我吗?谢谢。 - Yogesh Patel

17
我建议将Google Maps API与您的项目集成。如果这样做,您可以使用Google提供的逆地理编码来完成您的任务。
此外,对于IOS开发,Google提供了Google Maps SDK,也值得考虑。 更新:您可以在不将地图集成到您的项目中的情况下完成此操作。根据this的答案,您可以使用向Google API发送http请求来实现这一点。请求为:
https://maps.googleapis.com/maps/api/geocode/json?latlng=40.714224,-73.961452&key=API_KEY 

将返回一个包含有关所请求位置的信息的JSON对象,包括国家和城市名称。
顺便说一下,我强烈建议在Swift中使用Alamofire进行http请求。

我在我的项目中没有插入地图,我只是使用这段代码来获取纬度和经度。 - joshua
根据这个答案,你可以使用Alamofire发出HTTP请求,轻松地使用Google API而无需集成地图。 - pomo_mondreganto
如果您已经使用 GPS 并且需要国家简称 isoCountryCode,则无需包括其他 API 和网络请求,您应该在苹果文档中深入了解,而不需要进行网络调用。 - Khalid Afridi
Alamofire确实很好,可以在Swift中进行HTTP请求 ;) - Paulo Mattos
13
建议使用Google可能不是最佳答案(除非您对该应用有一些良好的投资)。Google APIs 让你以为它是免费的,直到你仔细阅读了条款和条件。同样也存在使用限制,因此请注意如何使用它。如果您只是想学习,那么请尽情探索Google API,但我建议首先学习苹果的CoreLocation,因为它可以做几乎所有Google的事情(除了一些高级功能),但需要编写更多代码(这很有趣),如果您编写得很有效率,它是免费的。推荐@LeoDabus下面的答案。 - Anjan Biswas
显示剩余2条评论

11

你需要的是反向地理编码。由于你已经在顶部声明了一些属性,所以你需要添加 CLGeocoderCLPlacemark

let locationManager = CLLocationManager()
var location: CLLocation?

let geocoder = CLGeocoder()
var placemark: CLPlacemark?

// here I am declaring the iVars for city and country to access them later

var city: String?
var country: String?
var countryShortName: String?
创建一个函数,可以启动位置服务。
func startLocationManager() {
    // always good habit to check if locationServicesEnabled
    if CLLocationManager.locationServicesEnabled() {
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.startUpdatingLocation()
    }
}

完成位置地理编码后,还需要创建另一个功能来停止程序执行。

func stopLocationManager() {
   locationManager.stopUpdatingLocation()
   locationManager.delegate = nil
}

在viewDidLoad或者任何你想开始定位管理器的地方,先加入一个检查

override func viewDidLoad() {
super.viewDidLoad()

    let authStatus = CLLocationManager.authorizationStatus()
    if authStatus == .notDetermined {
        locationManager.requestWhenInUseAuthorization()
    }

    if authStatus == .denied || authStatus == .restricted {
        // add any alert or inform the user to to enable location services 
    }

   // here you can call the start location function
   startLocationManager()

}

实现位置管理器的didFailedWithError代理方法。

func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
    // print the error to see what went wrong
    print("didFailwithError\(error)")
    // stop location manager if failed
    stopLocationManager()
}

实现位置管理器的didUpdateLocations委托方法

 func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    // if you need to get latest data you can get locations.last to check it if the device has been moved
    let latestLocation = locations.last!

    // here check if no need to continue just return still in the same place
    if latestLocation.horizontalAccuracy < 0 {
        return
    }
    // if it location is nil or it has been moved
    if location == nil || location!.horizontalAccuracy > lastLocation.horizontalAccuracy {

        location = lastLocation
        // stop location manager
        stopLocationManager()

        // Here is the place you want to start reverseGeocoding
        geocoder.reverseGeocodeLocation(lastLocation, completionHandler: { (placemarks, error) in
                // always good to check if no error
                // also we have to unwrap the placemark because it's optional
                // I have done all in a single if but you check them separately 
                if error == nil, let placemark = placemarks, !placemark.isEmpty {
                    self.placemark = placemark.last
                }
                // a new function where you start to parse placemarks to get the information you need
                self.parsePlacemarks()

           })
    }
}

添加parsePlacemarks函数

parsePlacemarks() {
   // here we check if location manager is not nil using a _ wild card 
   if let _ = location {
        // unwrap the placemark 
        if let placemark = placemark {
            // wow now you can get the city name. remember that apple refers to city name as locality not city
            // again we have to unwrap the locality remember optionalllls also some times there is no text so we check that it should not be empty
            if let city = placemark.locality, !city.isEmpty {
                // here you have the city name
                // assign city name to our iVar
                self.city = city
            }
            // the same story optionalllls also they are not empty
            if let country = placemark.country, !country.isEmpty {

                self.country = country
            }
            // get the country short name which is called isoCountryCode
            if let countryShortName = placemark.isoCountryCode, !countryShortName.isEmpty {

                self.countryShortName = countryShortName
            }

        }


    } else {
       // add some more check's if for some reason location manager is nil
    }

}

要看到所有可访问的属性,例如街道名称称为thoroughfare,号码称为subThoroughfare,您必须在CLPlacemark上进行cmd +单击。继续阅读文档以获取更多信息。

注意:您还必须检查位置错误和地理编码器错误,尽管我没有在此处实现,但您必须处理这些错误,并且最好的检查错误代码和其他内容的地方是苹果文档。

更新:请检查paresPlacemarks函数,其中我添加了isoCountryCode,它等于国家shortName。无需添加额外的网络调用到谷歌API和Alamofire,因为您已经在使用位置服务。


8

这里是Swift 4代码:

  var locationManager = CLLocationManager()

  override func viewDidLoad() {
    super.viewDidLoad()
    locationManager.delegate = self
    locationManager.requestWhenInUseAuthorization()
    locationManager.desiredAccuracy = kCLLocationAccuracyBest
    locationManager.startUpdatingLocation()
    locationManager.startMonitoringSignificantLocationChanges()
    // Here you can check whether you have allowed the permission or not.
    if CLLocationManager.locationServicesEnabled()
    {
        switch(CLLocationManager.authorizationStatus())
        {
        case .authorizedAlways, .authorizedWhenInUse:
            print("Authorize.")
            let latitude: CLLocationDegrees = (locationManager.location?.coordinate.latitude)!
            let longitude: CLLocationDegrees = (locationManager.location?.coordinate.longitude)!
            let location = CLLocation(latitude: latitude, longitude: longitude) //changed!!!
            CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
                if error != nil {
                    return
                }else if let country = placemarks?.first?.country,
                    let city = placemarks?.first?.locality {
                    print(country)
                    self.cityNameStr = city
                }
                else {
                }
            })
            break

        case .notDetermined:
            print("Not determined.")
            self.showAlertMessage(messageTitle: "Bolo Board", withMessage: "Location service is disabled!!")
            break

        case .restricted:
            print("Restricted.")
            self.showAlertMessage(messageTitle: "Bolo Board", withMessage: "Location service is disabled!!")
            break

        case .denied:
            print("Denied.")
        }
    }
}

func showAlertMessage(messageTitle: NSString, withMessage: NSString) ->Void  {
    let alertController = UIAlertController(title: messageTitle as String, message: withMessage as String, preferredStyle: .alert)
    let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (action:UIAlertAction!) in

    }
    alertController.addAction(cancelAction)

    let OKAction = UIAlertAction(title: "Settings", style: .default) { (action:UIAlertAction!) in
        if let url = URL(string: "App-Prefs:root=Privacy&path=LOCATION/com.company.AppName") {
            if #available(iOS 10.0, *) {
                UIApplication.shared.open(url, options: [:], completionHandler: nil)
            } else {
                // Fallback on earlier versions
            }
        }
    }
    alertController.addAction(OKAction)
    self.present(alertController, animated: true, completion:nil)
}

由于这行代码,苹果拒绝了该应用。代码为:app-prefs:root=privacy&path=location/com.company.appname。 - Disha

6
import Foundation
import CoreLocation

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 streetName: String      // eg. Infinite Loop
    let streetNumber: String    // eg. 1
    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 ?? ""
    }
}

5
您可以使用 CoreLocation 中的 CLGeocoder 来实现。据苹果文档(我强调):
单次操作对象,用于在地理坐标和地名之间进行转换。 CLGeocoder 类提供服务,用于在坐标(指定为纬度和经度)和用户友好表示的该坐标之间进行转换。 用户友好的坐标表示通常包括与给定位置对应的街道、城市、州和国家信息...
这项服务与 MapKit 无关,因此您的应用程序不需要使用/显示地图。

MKReverseGeocoder 已经在多年前被弃用。您应该使用 CLGeocoder - dan
我在我的项目中没有插入地图,我只是使用这段代码来获取纬度和经度,并且我需要国家名称来进行链接。 - joshua

1

1. 导入 CoreLocation 2. 在你的类中插入 CLLocationManagerDelegate 3. 按照下面描述的代理方法进行操作... 希望能帮到你 通过以下步骤,你可以找到城市名和国家... 这是我的代码

    import UIKit

    import CoreLocation 

    class MyViewController:UIViewController,CLLocationManagerDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()


        self.locationManager.delegate = self
        self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
        self.locationManager.requestWhenInUseAuthorization()
        self.locationManager.requestAlwaysAuthorization()
        self.locationManager.startUpdatingLocation()


}

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {



        if( CLLocationManager.authorizationStatus() == .authorizedWhenInUse ||
            CLLocationManager.authorizationStatus() ==  .authorizedAlways){

           if let currentLocation = locationManager.location
           {

           if NetworkFunctions.NetworkRechability()
           {

            getAddressFromLatLon(pdblLatitude: "\(Double((currentLocation.coordinate.latitude)))", withLongitude: "\(Double((currentLocation.coordinate.longitude)))")

            }

            }
        }



    }

    func getAddressFromLatLon(pdblLatitude: String, withLongitude pdblLongitude: String) {
        var center : CLLocationCoordinate2D = CLLocationCoordinate2D()
        let lat: Double = Double("\(pdblLatitude)")!

        let lon: Double = Double("\(pdblLongitude)")!

        let ceo: CLGeocoder = CLGeocoder()
        center.latitude = lat
        center.longitude = lon

        let loc: CLLocation = CLLocation(latitude:center.latitude, longitude: center.longitude)


        ceo.reverseGeocodeLocation(loc, completionHandler:
            {(placemarks, error) in
                if (error != nil)
                {
                }

                if placemarks != nil
                {

                    let pm = placemarks! as [CLPlacemark]

                    if pm.count > 0 {

                        let pm = placemarks![0]

                        print(pm.country ?? "")
                        print(pm.locality ?? "")
                       print(pm.subLocality ?? "")
                       print(pm.thoroughfare ?? "")
                        print(pm.postalCode ?? "")
                        print(pm.subThoroughfare ?? "")
                        var addressString : String = ""
                        if pm.subLocality != nil {
                            addressString = addressString + pm.subLocality! + ", "
                        }
                        if pm.thoroughfare != nil {
                            addressString = addressString + pm.thoroughfare! + ", "
                        }
                        if pm.locality != nil {
                            addressString = addressString + pm.locality! + ", "
                            if pm.country != nil {
                                addressString = addressString + pm.country! + ", "
                                //uuuuu
                                if(location_city != pm.locality!.trimmingCharacters(in: .whitespaces))
                                {
                                    location_city=pm.locality!.trimmingCharacters(in: .whitespaces)
                                      DispatchQueue.main.async{
                                    self.GetBeeWatherDetails(district: pm.locality!, country: pm.country!)
                                    }
                                }
                            }

                        }

                        if pm.postalCode != nil {
                            addressString = addressString + pm.postalCode! + " "
                        }

                    }
                }
        })

    }

}

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

这个方法将会给你当前的位置、城市名、国家名等信息。

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    let location: CLLocation = locations.last!
    print("Location: \(location)")

    let geocoder = CLGeocoder()
    geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
        // Process Response
        if let error = error {
            print("Unable to Reverse Geocode Location (\(error))")
        } else {
            if let placemarks = placemarks, let placemark = placemarks.first {
                self.city = placemark.locality!

                //self.country = placemark.country!
            }
        }
    }

    let camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude,
                                          longitude: location.coordinate.longitude,
                                          zoom: zoomLevel)

     self.locationv = CLLocation(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)

    if myView.isHidden {
        myView.isHidden = false
        myView.camera = camera
    } else {
        myView.animate(to: camera)
    }
}

0

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