【Swift/iOS】CoreLocationで取得した位置情報を住所に変換する

# 概要
下記の記事で説明した位置情報から自然な住所の表記に変換する方法について説明します。
[【Swift/iOS】CoreLocationで緯度・経度を取得して位置情報を表示する](https://qiita.com/vyeah321/items/ec01530012b5b3020301)

位置情報を取得した場所によって地名として取得される情報の粒度が異なるため、地名をつなげるだけでは自然な住所表記にならない場合があります。
具体例としては、下記の実行結果をご覧ください。

# 確認バージョン
Xcode 11.2.1
Swift 5.1.2
iOS 13.2.2

# 要件
1. 地名が丁目や番地まで取得できる場所では丁目や番地までの住所を出力
2. 地名が「〜通り」や「〜道路」などの場合には丁目なしの住所を出力
3. 「〜通り」や「〜道路」は住所に含めない

# 結論
下記のように実装すると要件を満たす住所が出力されました。

```
//位置情報
let administrativeArea = placemark.administrativeArea == nil ? "" : placemark.administrativeArea!
let locality = placemark.locality == nil ? "" : placemark.locality!
let subLocality = placemark.subLocality == nil ? "" : placemark.subLocality!
let thoroughfare = placemark.thoroughfare == nil ? "" : placemark.thoroughfare!
let subThoroughfare = placemark.subThoroughfare == nil ? "" : placemark.subThoroughfare!
let placeName = !thoroughfare.contains( subLocality ) ? subLocality : thoroughfare

//住所
let address = administrativeArea + locality + placeName + subThoroughfare
```

# 説明
| プロパティ名 | 概要 | 説明 |
|---|---|---|
| administrativeArea | 都道府県 | そのまま（nilにはならないはず） | 
| subAdministrativeArea | 郡 | 町村の場合、localityに〜郡〜町／村と出力されるのでこちらは使用しません |
| locality | 市区町村 | そのまま（nilにはならないはず）|
| subLocality | 丁番なしの地名 | thoroughfareにsubLocalityが含まれない場合はsubLocality |
| thoroughfare | 地名 | 上記以外はthoroughfare |
| subThoroughfare | 番地 | nilの場合は空白 |

# 実行結果
出力される住所は下記の画面の最終行に表示されています。

|永田町2丁目|永田町|日比谷公園|奥多摩町|
| --- | --- | --- | --- |
| <img width="483" alt="スクリーンショット 2019-11-26 23.49.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537736/624010c8-831f-89be-3729-6f613fa9a379.png"> | <img width="483" alt="スクリーンショット 2019-11-26 23.58.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537736/a73b686a-39fc-8c51-fd43-198ea5881b67.png"> | <img width="483" alt="スクリーンショット 2019-11-26 23.53.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537736/183bbbd4-0be2-f367-6fae-474665091085.png"> | <img width="483" alt="スクリーンショット 2019-11-26 23.51.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537736/efe28dfb-b741-cf25-4cfb-37a64a94da86.png"> |



# 実装
1. 情報表示用のUILabelを追加
2. 位置情報を利用許可状態を判定
3. 位置情報の更新開始
3. CLLocationデータを逆ジオコーディングして位置情報を取得
4. 位置情報を住所に変換
5. 位置情報が更新されるごとにUILabelに設定


```
import UIKit
import CoreLocation

class ViewController: UIViewController, CLLocationManagerDelegate {
    let locationManager = CLLocationManager()
    let geocoder = CLGeocoder()
    let text = [ "緯度", "経度", "国名", "郵便番号", "都道府県", "郡", "市区町村", "丁番なしの地名", "地名", "番地" ]
    var item: [ UILabel ] = []
    var location: [ UILabel ] = []
    var address: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //サイズ
        let width = self.view.frame.width / 2
        let height = self.view.frame.height / CGFloat( self.text.count + 2 )
        
        //ラベル
        for ( i, text ) in self.text.enumerated() {
            //項目
            self.item.append( UILabel() )
            self.item.last!.frame.size = CGSize( width: width, height: height )
            self.item.last!.frame.origin = CGPoint( x: 0, y: height * CGFloat( i + 1 ) )
            self.item.last!.textAlignment = .center
            self.item.last!.text = text
            self.view.addSubview( self.item.last! )
            
            //データ
            self.location.append( UILabel() )
            self.location.last!.frame.size = CGSize( width: width, height: height )
            self.location.last!.frame.origin = CGPoint( x: width, y: height * CGFloat( i + 1 ) )
            self.location.last!.textAlignment = .center
            self.view.addSubview( self.location.last! )
        }
        
        //住所
        self.address = UILabel()
        self.address.frame.size = CGSize( width: self.view.frame.width, height: height )
        self.address.frame.origin = CGPoint( x: 0, y: height * CGFloat( self.text.count + 1 ) )
        self.address.textAlignment = .center
        self.view.addSubview( self.address )
        
        //ロケーションマネージャ
        self.locationManager.requestWhenInUseAuthorization()
        let status = CLLocationManager.authorizationStatus()
        if status == .authorizedWhenInUse {
            self.locationManager.delegate = self
            self.locationManager.distanceFilter = 10
            self.locationManager.startUpdatingLocation()
        }
    }
    
    func locationManager( _ manager: CLLocationManager, didUpdateLocations locations: [ CLLocation ] ) {
        //表示更新
        if let location = locations.first {
            //緯度・経度
            self.location[0].text = location.coordinate.latitude.description
            self.location[1].text = location.coordinate.longitude.description
            
            //逆ジオコーディング
            self.geocoder.reverseGeocodeLocation( location, completionHandler: { ( placemarks, error ) in
                if let placemark = placemarks?.first {
                    //位置情報
                    self.location[2].text = placemark.country
                    self.location[3].text = placemark.postalCode
                    self.location[4].text = placemark.administrativeArea
                    self.location[5].text = placemark.subAdministrativeArea
                    self.location[6].text = placemark.locality
                    self.location[7].text = placemark.subLocality
                    self.location[8].text = placemark.thoroughfare
                    self.location[9].text = placemark.subThoroughfare
                    
                    //住所
                    let administrativeArea = placemark.administrativeArea == nil ? "" : placemark.administrativeArea!
                    let locality = placemark.locality == nil ? "" : placemark.locality!
                    let subLocality = placemark.subLocality == nil ? "" : placemark.subLocality!
                    let thoroughfare = placemark.thoroughfare == nil ? "" : placemark.thoroughfare!
                    let subThoroughfare = placemark.subThoroughfare == nil ? "" : placemark.subThoroughfare!
                    let placeName = !thoroughfare.contains( subLocality ) ? subLocality : thoroughfare
                    self.address.text = administrativeArea + locality + placeName + subThoroughfare
                }
            } )
        }
    }
}

```
Githubのソースコードは[こちら](https://github.com/vyeah321/Location)

# 緯度・経度の確認方法について
住所から緯度・経度を確認するのに下記のサイトを使用しました。
いろいろな住所で変換結果が正しいことを確認するのに重宝しました。
https://www.geocoding.jp

このサイトで検索した緯度・経度を下記のように入力すると、その場所での位置情報を確認することができます。

```
//緯度・経度
let location = CLLocation( latitude: 35.675775, longitude: 139.743488 )
            
//逆ジオコーディング
self.geocoder.reverseGeocodeLocation( location, completionHandler: {
   //以下、略
```


# 参考
[【CoreLocation】位置情報を取得する](https://qiita.com/chino_tweet/items/db3a536234a43a3c31d9)
[[iOS] MapKitを使って”ジオコーディング・逆ジオコーディング”をやってみる](https://dev.classmethod.jp/smartphone/iphone/geocoding-use-mapkit/)

# 開発アプリ
この記事で紹介した位置情報の取得＆住所変換処理を適用したアプリです。
[Go Back Home - 自宅に帰るためのポータルアプリ](https://www.vyeah.jp/go-back-home)