Reverse geocoding in Swift 4

时光总嘲笑我的痴心妄想 提交于 2019-12-29 04:42:12

问题


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.


回答1:


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 place mark 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? {
        return postalAddress?.mailingAddress
    }
}

extension CNPostalAddress {
    var mailingAddress: String {
        return 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




回答2:


There are so many questions dealing with 'reverseGeocodeLocation' on Stack Overflow, I've seen so many of them. I'm using Swift 4.2 and I thought my 'reverseGeocodeLocation' requests might be timing out. Well, this is NOT the case, when starting to move with an iPhone the 'didUpdateLocations' cycles need to mature. It can take two or more cycles before 'reverseGeocodeLocation' returns placemarks.

I wrote a simple app that uses 'reverseGeocodeLocation' until 'placemarks' are returned after repeated 'didUpdateLocations' cycles. This is my own home grown 'SampleCode'. I learned a lot from using the app, it might help others understand how reverse geocode requests work in the real world.

Please give your comments on possible improvements. This code is freely given and freely taken.

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

And then the Storyboard 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>


来源:https://stackoverflow.com/questions/46869394/reverse-geocoding-in-swift-4

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!