[iOS]what3words API を触ってみた


# what3words とは
[what3words](https://what3words.com/about-us/) とは住所などで表現される位置情報を、3つの単語で表現できるサービスです。
地球上を 3m × 3m のマスに区切り、その1マスを3つの単語で指定することができます。

「住所と違って何がいいの？」と思うかもしれませんが、
お花見の場所取りや、大型ショッピングセンターの特定の入り口など、
住所だけでは説明できない位置情報を短い3つの単語だけで指定することができます。

「それなら緯度経度の座標でいいやん」って思うかもしれませんが、
誰かに共有する際、緯度経度は不規則な数字であるため
コピー＆ペースト以外の方法では共有が難しいというデメリットがあります。

what3word を使用すると、3つの単語を共有するだけで位置情報を共有できるので、
座標よりサクッと共有できるといったメリットがあります。

「住所とか緯度経度とか長いよね、カーナビの行き先指定とかめんどくさいし。
what3words を使うと3つの単語でサクッと位置情報を指定、共有できちゃうよ」
って感じです。


# 開発環境

- Xcode：11.1（11A1027）
- Swift 5
- iOS：13.1

# 下準備
[こちら](https://developer.what3words.com/tutorial/swift/)の手順に沿って進めていきます

## API キーの取得
1. [こちら](https://accounts.what3words.com/register/)から利用登録をする
1. ログイン後、Developer API Keys を選択
  <img width="732" alt="スクリーンショット 2020-01-03 17.05.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53717/1bd892d4-4d5e-b2e9-b50d-23d1917103b8.png">
1. Create API Key を選択
 <img width="1174" alt="スクリーンショット 2020-01-03 17.05.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53717/956d6976-4d05-9736-d71b-4e5eb1370a5e.png">
1. Name、Description、Country を入力し、API Key を作成
 <img width="439" alt="スクリーンショット 2020-01-03 17.05.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53717/22b6e158-7e49-595f-9457-5c473ab15889.png">
1. 以下の画像の矢印で示した所に表示されているものが API Key になります
 <img width="882" alt="スクリーンショット 2020-01-03 17.05.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53717/33f35f79-6455-1ce8-14bc-2a42fb02cddd.png">


## what3words のインストール


 - CocoaPods

```
platform :ios, '9.0'
use_frameworks!

target 'MyApp' do
    pod 'what3words', :git => 'https://github.com/what3words/w3w-swift-wrapper.git'
end      
```

 - Charthage

```
github "what3words/w3w-swift-wrapper"
```

## セットアップ

- API Key のセット

AppDelegate.swift で API Key の登録を行います

```swift:AppDelegate.swift
import what3words

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    W3wGeocoder.setup(with: "input your API key")
    return true
}
```



 - what3words の API を使用したいところに以下を記述します

```swift
import what3words
import CoreLocation
```

以上で what3words を使用するための準備は完了です

# what3words を使ってみた
今回は、

1. 現在地の 3words を確認
2. 検索欄から 3words を検索して位置を確認

の2つの機能を試してみようと思います

## 1. 現在地の 3words を確認

現在地の 3words を確認するために、まずは　Google Maps SDK for iOS を使用し、
現在地の取得、表示を行います

Google Maps SDK for iOS を使用して現在地を地図上に表示にするまでの実装は別記事でまとめようと思います

```swift:ViewController.swift
import UIKit
import what3words
import GoogleMaps

class ViewController: UIViewController {

    /// Google Map
    let mapView: GMSMapView = {
        let camera = GMSCameraPosition.camera(withLatitude: 35.6812226, longitude: 139.7670594, zoom: 12.0)
        let view = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
        view.isMyLocationEnabled = true
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    /// 現在地の 3words を表示するラベル
    let what3wordsLabel: UILabel = {
        let label = UILabel()
        label.numberOfLines = 1
        label.textAlignment = .left
        label.textColor = .black
        label.font = UIFont.boldSystemFont(ofSize: 17.0)
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    let locationManager = CLLocationManager()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(mapView)
        view.addSubview(what3wordsLabel)

        searchBar.delegate = self
        requestLoacion()
    }

    override func viewWillLayoutSubviews() {
        setupSubViews()
    }

    private func setupSubViews() {
        NSLayoutConstraint.activate([
            what3wordsLabel.topAnchor.constraint(equalTo: view.top, constant: view.safeAreaInsets.top + 16.0),
            what3wordsLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16.0),

            mapView.topAnchor.constraint(equalTo: view.topAnchor),
            mapView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            mapView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])

        searchBar.delegate = self
    }

    private func requestLoacion() {
        locationManager.delegate = self
        locationManager.requestWhenInUseAuthorization()
    }
}

extension ViewController: CLLocationManagerDelegate {

    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        switch status {
        case .authorizedAlways, .authorizedWhenInUse:
            // 現在の位置情報を取得
            locationManager.startUpdatingLocation()
        case .denied, .notDetermined, .restricted:
            print("許可されていません")
        @unknown default:
            fatalError()
        }
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let currentLocation = locations.last?.coordinate else { return }

        let camera = GMSCameraPosition(latitude: currentLocation.latitude,
                                       longitude: currentLocation.longitude,
                                       zoom: 17.0)
        mapView.animate(to: camera)
        W3wGeocoder.shared.convertTo3wa(coordinates: currentLocation, language: "ja", completion: { [weak self] (place, error) in
            DispatchQueue.main.async {
                // place.words に 位置情報から変換した 3words を取得できる
                self?.what3wordsLabel.text = place?.words
            }
        })
        locationManager.stopUpdatingLocation()
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(error.localizedDescription)
    }
}
```

## 2. 検索欄から 3words を検索して位置を確認

次に画面上に検索欄を表示させ、そこに 3words を入力し、Map 上にピンを立てるようなことをしたいと思います

what3words の検索 API の使用上、3つの単語は . で区切る必要があるようなので、
検索バーでは入力しやすいよう、スペース区切りで入力してもらって、それを . 区切りに変換するような処理を行なっています

```swift:ViewController
class ViewController: UIViewController {
    // 追記
    /// what3words を入力する検索バー
    let searchBar: UISearchBar = {
        let view = UISearchBar()
        view.placeholder = "スペース区切りで3つの単語を入れてください"
        view.backgroundColor = .white
        view.barTintColor = .black
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(mapView)
        view.addSubview(what3wordsLabel)
        view.addSubview(searchBar) // 追記

        searchBar.delegate = self // 追記
        requestLoacion()
    }

    override func viewWillLayoutSubviews() {
        setupSubViews()
    }

    /// view の layout を指定
    private func setupSubViews() {
        // レイアウトを修正しています
        NSLayoutConstraint.activate([
            searchBar.topAnchor.constraint(equalTo: view.topAnchor, constant: view.safeAreaInsets.top + 16.0),
            searchBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            searchBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),

            what3wordsLabel.topAnchor.constraint(equalTo: searchBar.bottomAnchor, constant: 16.0),
            what3wordsLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16.0),

            mapView.topAnchor.constraint(equalTo: searchBar.bottomAnchor),
            mapView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            mapView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }

// ~~~~~~~~~省略~~~~~~~~~~~~

extension ViewController: UISearchBarDelegate {

    /// 検索ボタンをタップした際に呼び出されるメソッド
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        guard let text = searchBar.text else { return }
        // スペース区切りで入力したテキストを . 区切りに変換
        let what3words = text.replacingOccurrences(of: " ", with: ".")

        // ex) "ふつか そうさ ぜんぜん" で皇居へ移動します
        W3wGeocoder.shared.convertToCoordinates(words: what3words, completion: { [weak self] (place, error) in
            guard let location = place?.coordinates else { return }
            let camera = GMSCameraPosition(latitude: location.latitude, longitude: location.longitude, zoom: 17.0)
            let marker = GMSMarker(position: location)
            marker.title = what3words
            DispatchQueue.main.async {
                marker.map = self?.mapView
                self?.mapView.animate(to: camera)
            }
        })
    }
}
```


### 

# コードの全容

```swift:ViewController.swift
import UIKit
import what3words
import GoogleMaps

class ViewController: UIViewController {

    /// Google Map
    let mapView: GMSMapView = {
        let camera = GMSCameraPosition.camera(withLatitude: 35.6812226, longitude: 139.7670594, zoom: 12.0)
        let view = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
        view.isMyLocationEnabled = true
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    /// 現在地の 3words を表示するラベル
    let what3wordsLabel: UILabel = {
        let label = UILabel()
        label.numberOfLines = 1
        label.textAlignment = .left
        label.textColor = .black
        label.font = UIFont.boldSystemFont(ofSize: 17.0)
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    /// what3words を入力する検索バー
    let searchBar: UISearchBar = {
        let view = UISearchBar()
        view.placeholder = "スペース区切りで3つの単語を入れてください"
        view.backgroundColor = .white
        view.barTintColor = .black
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    let locationManager = CLLocationManager()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(mapView)
        view.addSubview(what3wordsLabel)
        view.addSubview(searchBar)

        searchBar.delegate = self
        requestLoacion()
    }

    override func viewWillLayoutSubviews() {
        setupSubViews()
    }

    /// view の layout を指定
    private func setupSubViews() {
        NSLayoutConstraint.activate([
            searchBar.topAnchor.constraint(equalTo: view.topAnchor, constant: view.safeAreaInsets.top + 16.0),
            searchBar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            searchBar.trailingAnchor.constraint(equalTo: view.trailingAnchor),

            what3wordsLabel.topAnchor.constraint(equalTo: searchBar.bottomAnchor, constant: 16.0),
            what3wordsLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16.0),

            mapView.topAnchor.constraint(equalTo: searchBar.bottomAnchor),
            mapView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            mapView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }

    private func requestLoacion() {
        locationManager.delegate = self
        // 位置情報を取得
        locationManager.requestWhenInUseAuthorization()
    }
}

extension ViewController: CLLocationManagerDelegate {

    /// 位置情報の取得の認可状態が更新された際に呼び出されるメソッド
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        switch status {
        case .authorizedAlways, .authorizedWhenInUse:
            locationManager.startUpdatingLocation()
        case .denied, .notDetermined, .restricted:
            print("許可されていません")
        @unknown default:
            fatalError()
        }
    }

    /// 位置情報が更新された際に呼び出されるメソッド
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let currentLocation = locations.last?.coordinate else { return }

        let camera = GMSCameraPosition(latitude: currentLocation.latitude,
                                       longitude: currentLocation.longitude,
                                       zoom: 17.0)
        mapView.animate(to: camera)
        W3wGeocoder.shared.convertTo3wa(coordinates: currentLocation, language: "ja", completion: { [weak self] (place, error) in
            DispatchQueue.main.async {
                // 現在の座標から変換した 3words をラベルに表示する
                self?.what3wordsLabel.text = place?.words
            }
        })
        locationManager.stopUpdatingLocation()
    }

    /// 位置情報の取得に失敗した際に呼び出されるメソッド
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(error.localizedDescription)
    }
}

extension ViewController: UISearchBarDelegate {

    /// 検索ボタンをタップした際に呼び出されるメソッド
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        guard let text = searchBar.text else { return }
        // スペース区切りで入力したテキストを . 区切りに変換
        let what3words = text.replacingOccurrences(of: " ", with: ".")

        // ex) "ふつか そうさ ぜんぜん" で皇居へ移動します
        W3wGeocoder.shared.convertToCoordinates(words: what3words, completion: { [weak self] (place, error) in
            guard let location = place?.coordinates else { return }
            let camera = GMSCameraPosition(latitude: location.latitude, longitude: location.longitude, zoom: 17.0)
            let marker = GMSMarker(position: location)
            marker.title = what3words
            DispatchQueue.main.async {
                marker.map = self?.mapView
                self?.mapView.animate(to: camera)
            }
        })
    }
}
```


# 参考

- [what3words swift tutorial](https://developer.what3words.com/tutorial/swift/)
- [Google Maps SDK for iOS](https://developers.google.com/maps/documentation/ios-sdk/start)