Swift 4 中的反向地理编码

Reverse geocoding in Swift 4

我正在尝试编写一个简单的方法,该方法提供 CLLocationDegrees 和 returns 一个 CLPlacemark。看着Apple's documentation,好像是个简单的任务。

下面是我扔到操场上的东西:

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)

就目前而言,我的方法返回零。我不确定我的错误在哪里,但我确信这对我来说是非常愚蠢的事情。感谢阅读。

CLGeocoder 方法是异步的。您需要一个完成处理程序:

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

或者简单地说:

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

或扩展 CLLocation:

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

要将地标格式化为邮寄地址,您可以使用联系人框架 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.

有关 CLPlacemark 属性的更多信息,您可以查看此 CLPlacemark


用法:

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

这将打印

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

Stack Overflow 上有很多关于 'reverseGeocodeLocation' 的问题,我见过很多。我正在使用 Swift 4.2,我认为我的 'reverseGeocodeLocation' 请求可能会超时。好吧,情况并非如此,当开始以 iPhone 移动时,'didUpdateLocations' 周期需要成熟。在 'reverseGeocodeLocation' returns 个地标之前可能需要两个或更多周期。

我写了一个简单的应用程序,它使用 'reverseGeocodeLocation' 直到 'placemarks' 在重复 'didUpdateLocations' 循环后返回。这是我自己种的 'SampleCode'。我从使用该应用程序中学到了很多东西,它可能会帮助其他人了解反向地理编码请求在现实世界中的工作原理。

请就可能的改进发表您的意见。此代码免费提供和免费使用。

import Foundation
import UIKit
import CoreLocation

class ViewController: UIViewController, CLLocationManagerDelegate {
    @IBAction func RefreshLocationButton(_ sender: Any) {
        self.requestingPlacemark = true
        self.placemarkData = nil
        //^Make another Placemark Request
        self.requestCounter = 0
        self.RequestCounterLabel.text = ""
        self.LocationLabel.text = ""
        self.PlacemarkLabel.text = ""}
    @IBOutlet weak var LocationCounterLabel: UILabel!
    @IBOutlet weak var RequestCounterLabel: UILabel!
    @IBOutlet weak var LocationLabel: UILabel!
    @IBOutlet weak var PlacemarkLabel: UILabel!
    let locationManager = CLLocationManager()
    var placemarkData: CLPlacemark!
    var placemarkString: String!
    var printPlacemarkData: Bool!
    var didUpdateLocationsCounter: Int = 0
    var requestCounter: Int = 0
    var requestingPlacemark: Bool = true

override func viewDidLoad() {
    super.viewDidLoad()
    if CLLocationManager.locationServicesEnabled() {
        locationManager.requestAlwaysAuthorization()
        locationManager.delegate = self
        //locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
        locationManager.startUpdatingLocation()
        self.RequestCounterLabel.text = ""
        self.LocationLabel.text = ""
        self.PlacemarkLabel.text = ""}}
func printPlacemarks() {
    if printPlacemarkData {
        self.placemarkString = "Placemark Data:"
        self.placemarkString = buildPlacemarkString(item: 1)
        self.placemarkString = buildPlacemarkString(item: 2)
        self.placemarkString = buildPlacemarkString(item: 3)
        self.placemarkString = buildPlacemarkString(item: 4)
        self.placemarkString = buildPlacemarkString(item: 5)
        self.placemarkString = buildPlacemarkString(item: 6)
        self.placemarkString = buildPlacemarkString(item: 7)
        self.placemarkString = buildPlacemarkString(item: 8)
        self.placemarkString = buildPlacemarkString(item: 9)
        self.placemarkString = buildPlacemarkString(item: 10)
        self.PlacemarkLabel.text = self.placemarkString
        self.printPlacemarkData = false}}
func buildPlacemarkString(item: Int) -> String {
    var elementText: String!
    var newString: String!
        switch item {
        case 1: elementText = "name: " + self.placemarkData.name!
        case 2: elementText = "subThoroughfare: " + self.placemarkData.subThoroughfare!
        case 3: elementText = "thoroughfare: " + self.placemarkData.thoroughfare!
        case 4: elementText = "postalCode: " + self.placemarkData.postalCode!
        case 5: elementText = "subLocality: " + self.placemarkData.subLocality!
        case 6: elementText = "locality: " + self.placemarkData.locality!
        case 7: elementText = "subAdministrativeArea: " + self.placemarkData.subAdministrativeArea!
        case 8: elementText = "administrativeArea: " + self.placemarkData.administrativeArea!
        case 9: elementText = "country: " + self.placemarkData.country!
        case 10: elementText = "isoCountryCode: " + self.placemarkData.isoCountryCode!
        default: print("Error: incorrect item number!")}
        newString = self.placemarkString + "\n" + elementText
    return newString
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        //location prints one time and 'didUpdateLocations' is stopped:
    self.didUpdateLocationsCounter = self.didUpdateLocationsCounter + 1
    let labelString = String(describing: self.didUpdateLocationsCounter)
    self.LocationCounterLabel.text = "Location Update Counter: " + labelString
    if self.placemarkData == nil {
        //location variable:
        self.requestCounter = self.requestCounter + 1
        let textString = String(describing: self.requestCounter)
        self.RequestCounterLabel.text = "Request Counter: " + textString
        if locations.count == 0 {
            self.LocationLabel.text = "Locations: There was NONE"}
        else {
            if locations.count > 0 {
                let coordinate2D: CLLocation = locations.first!
                let location = CLLocation(latitude: coordinate2D.coordinate.latitude, longitude: coordinate2D.coordinate.longitude)
                let printString = String(describing: location)
                self.LocationLabel.text = "Location: " + printString
                let geocoder: CLGeocoder = CLGeocoder()
                geocoder.reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
                    if error != nil {
                        let errorString = String(describing: error?.localizedDescription)
                        print("reverse geodcode fail: \(errorString)")
                        self.LocationCounterLabel.text = ""
                        self.RequestCounterLabel.text = ""
                        self.LocationLabel.text = "Reverse Geodcode fail: \(errorString)"
                        self.PlacemarkLabel.text = ""
                        self.requestingPlacemark = false
                        return}
                    else {
                        let pm = placemarks! as [CLPlacemark]
                        //There is ALWAYS 'placemarks' Data
                        if pm.count > 0 {
                            self.placemarkData = placemarks![0]
                            self.printPlacemarkData = true
                            self.printPlacemarks()
                            self.requestingPlacemark = false}}})}
            else {
                if self.requestingPlacemark {
                    self.LocationLabel.text = "Problem: There is no 'location.first'"}}}}}}

然后是情节提要 UI:

Storyboard Image

View Controller Image

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
    <device id="retina4_7" orientation="portrait">
        <adaptation id="fullscreen"/>
    </device>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="tne-QT-ifu">
            <objects>
                <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="MyThoroughfare" customModuleProvider="target" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Location Counter" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZIg-u9-TMR">
                                <rect key="frame" x="16" y="20" width="343" height="20.5"/>
                                <color key="backgroundColor" red="0.80000001190000003" green="0.80000001190000003" blue="0.80000001190000003" alpha="1" colorSpace="calibratedRGB"/>
                                <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                <nil key="textColor"/>
                                <nil key="highlightedColor"/>
                            </label>
                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Location Count Label" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="csF-KS-xGE">
                                <rect key="frame" x="16" y="48" width="343" height="20.5"/>
                                <color key="backgroundColor" red="0.80000001190000003" green="0.80000001190000003" blue="0.80000001190000003" alpha="1" colorSpace="calibratedRGB"/>
                                <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                <nil key="textColor"/>
                                <nil key="highlightedColor"/>
                            </label>
                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Location String" lineBreakMode="tailTruncation" numberOfLines="8" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qOc-gk-gSp">
                                <rect key="frame" x="16" y="76" width="343" height="20.5"/>
                                <color key="backgroundColor" red="0.80000001190000003" green="0.80000001190000003" blue="0.80000001190000003" alpha="1" colorSpace="calibratedRGB"/>
                                <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                <nil key="textColor"/>
                                <nil key="highlightedColor"/>
                            </label>
                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Placemark String" lineBreakMode="tailTruncation" numberOfLines="14" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="42A-nO-tNf">
                                <rect key="frame" x="16" y="104" width="343" height="20.5"/>
                                <color key="backgroundColor" red="0.80000001190000003" green="0.80000001190000003" blue="0.80000001190000003" alpha="1" colorSpace="calibratedRGB"/>
                                <fontDescription key="fontDescription" type="system" pointSize="17"/>
                                <nil key="textColor"/>
                                <nil key="highlightedColor"/>
                            </label>
                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="HgN-oB-ds0">
                                <rect key="frame" x="121.5" y="578" width="132" height="30"/>
                                <state key="normal" title="Request Placemark"/>
                                <connections>
                                    <action selector="RefreshLocationButton:" destination="BYZ-38-t0r" eventType="touchUpInside" id="izY-S5-wjg"/>
                                </connections>
                            </button>
                        </subviews>
                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <constraints>
                            <constraint firstItem="ZIg-u9-TMR" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="16" id="6HO-kN-l1z"/>
                            <constraint firstItem="qOc-gk-gSp" firstAttribute="top" secondItem="csF-KS-xGE" secondAttribute="bottom" constant="7.5" id="6ux-dx-gJr"/>
                            <constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="42A-nO-tNf" secondAttribute="trailing" constant="16" id="8dG-gV-Cob"/>
                            <constraint firstItem="HgN-oB-ds0" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="A38-Nt-i3D"/>
                            <constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="ZIg-u9-TMR" secondAttribute="trailing" constant="16" id="Bb0-YC-oov"/>
                            <constraint firstItem="csF-KS-xGE" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="16" id="FJU-ha-HkL"/>
                            <constraint firstItem="42A-nO-tNf" firstAttribute="top" secondItem="qOc-gk-gSp" secondAttribute="bottom" constant="7.5" id="SpR-XB-MLG"/>
                            <constraint firstItem="6Tk-OE-BBY" firstAttribute="bottom" secondItem="HgN-oB-ds0" secondAttribute="bottom" constant="59" id="Vr6-QE-230"/>
                            <constraint firstItem="42A-nO-tNf" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="16" id="aHo-kJ-aCb"/>
                            <constraint firstItem="csF-KS-xGE" firstAttribute="top" secondItem="ZIg-u9-TMR" secondAttribute="bottom" constant="7.5" id="c63-Mq-ItW"/>
                            <constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="csF-KS-xGE" secondAttribute="trailing" constant="16" id="dlV-Hc-8XJ"/>
                            <constraint firstItem="qOc-gk-gSp" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="16" id="m5y-Q8-jxI"/>
                            <constraint firstItem="ZIg-u9-TMR" firstAttribute="top" secondItem="6Tk-OE-BBY" secondAttribute="top" id="sdw-WQ-Bx9"/>
                            <constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="qOc-gk-gSp" secondAttribute="trailing" constant="16" id="woB-ig-i5v"/>
                        </constraints>
                        <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
                    </view>
                    <connections>
                        <outlet property="LocationCounterLabel" destination="ZIg-u9-TMR" id="K4g-WT-f82"/>
                        <outlet property="LocationLabel" destination="qOc-gk-gSp" id="Cdw-bw-EWt"/>
                        <outlet property="PlacemarkLabel" destination="42A-nO-tNf" id="dMh-bw-cRE"/>
                        <outlet property="RequestCounterLabel" destination="csF-KS-xGE" id="ai6-zn-Toi"/>
                    </connections>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="53.600000000000001" y="66.11694152923539"/>
        </scene>
    </scenes>
</document>