【ヨシモト∞ホール×Loupeコラボ企画】GoogleAPIで全国対応した話

## はじめに

株式会社Relicが運営しているLoupeでは、現在ヨシモト∞ホールとのコラボ企画を開催しています！
「オズワルド」や「蛙亭」などヨシモト∞ホールで活躍中の芸人さんたちが、渋谷のオススメスポットを紹介してくれる企画になっているのでご興味がある方はぜひ確認してみてください。

https://lp.loupe-app.com/yoshimoto

今回はこの企画を開催するにあたってより多くのユーザーさんに企画を楽しんでもらえるように、Loupeの登録フローに変更を行ったので、その話を書きたいと思います。

※LoupeはiOS/Androidの両OSでサービスを提供していますが、今回の全国対応を実施したのはiOSのみでAndroidは旧仕様のままとなっております。

## UIをどう変えたか

まずは視覚的に取り組み内容を理解してもらいやすいと思うので、UIの変更内容を共有します。

コラボ企画開催前は下記のUIで、1都3県の郵便番号もしくは住所を必須で登録する必要がありましたが、

<img width="300" alt="IMG_2292.PNG (200.9 kB)" src="https://img.esa.io/uploads/production/attachments/10655/2023/09/05/155229/d30527ca-7cbf-4feb-8645-586cf4a6d19d.PNG"> <img width="300" alt="IMG_2291.PNG (226.3 kB)" src="https://img.esa.io/uploads/production/attachments/10655/2023/09/05/155229/98def516-1bcb-44a4-8992-c698608c6aeb.PNG">

コラボ企画開催以降は下記のUIに変更し、1都3県外も含めて最寄駅や地名などのランドマークでも登録できるようになりました。

<img width="300" alt="IMG_2294.PNG (167.6 kB)" src="https://img.esa.io/uploads/production/attachments/10655/2023/09/05/155229/263c2b98-2874-487c-aaeb-97c35bfeba74.PNG">


## 実装をどう変えたか

### 変更前
コラボ企画以前は、あらかじめデータベースに1都3県分の住所情報をまとめる住所情報テーブルを用意しておき、郵便番号や住所を入力される毎に住所情報テーブルからデータを抽出してサジェストで表示していました。

- サジェストの様子
    <img width="300" alt="image.png (347.6 kB)" src="https://img.esa.io/uploads/production/attachments/10655/2023/09/16/141429/a2dd3e13-bbc3-4422-98c8-78481859f5ab.png">

- 住所情報テーブル（map_addresses）
    | カラム名 | 説明 |
    | -------- | -------- |
    | id     | ID |
    | zipcode     | 郵便番号|
    | prefecture     | 都道府県名 |
    | city     | 市区町村名     |
    | town     | 大字町丁目名     |
    | latitude     | 緯度     |
    | longitude     | 経度     |
    | radius_type     | 最大半径のタイプ     |
    | prefecture_key     | 都道府県名_yomi     |
    | city_key     | 市区町村名_yomi     |

データベースの住所情報は、国土交通省の[国土数値情報](https://nlftp.mlit.go.jp/cgi-bin/isj/dls/_choose_method.cgi)と日本郵便の[郵便番号情報](https://www.post.japanpost.jp/zipcode/download.html)を引っ張ってきて合わせたもので構成されています。

下のフロー図はマイエリア登録で郵便番号・住所が入力されてサジェストされるまでの流れです。

```mermaid
sequenceDiagram
    participant U as ユーザー
    participant M as モバイル
    participant A as API
    participant DB as Database
    
    U->>M:郵便番号・住所を入力
    M->>A:入力された郵便番号で住所検索<br>GET api/v1/map_addresses
    A->DB:郵便番号を元にmap_addressesから、<br>都道府県、市区町村を取得
    A-->>M:取得内容を返却
    M-->>U:表示
```

1. 住所情報テーブルをDBに用意し1都3県の住所情報を作成しておき、入力された郵便番号を元に住所情報テーブルから都道府県、市区町村、大字町丁目を取得する。

2. 取得した住所情報をサジェストで表示し、ユーザは当てはまる住所を選択する。



### 変更後

1. 入力された地点名  or 郵便番号を元にGoogle APIで住所情報を取得する。
    - 地点名の場合: 
    ```mermaid
    sequenceDiagram
        participant U as ユーザー
        participant M as モバイル
        participant A as Google API
        
        U->>M:地点名を入力
        M->>A:入力された情報からGoogle APIで住所情報を取得
        A-->>M:取得内容を返却
        M-->>U:表示
    ```
    1. TextFieldに入力された文字列をクエリとして指定し、 `findAutocompletePredictions()`メソッドを使用して、`[GMSAutocompletePrediction]`（GMSAutocompletePredictionオブジェクトの配列）を引数として受け取り、住所情報を取得する。
       
        ```swift
        private func searchPlaces(with query: String, completion: @escaping (([GMSAutocompletePrediction]) -> Void)) {
            let filter = GMSAutocompleteFilter()
            filter.countries = ["JP"]
            let token = GMSAutocompleteSessionToken.init()
    
            placesClient.findAutocompletePredictions(
                fromQuery: query,
                filter: filter,
                sessionToken: token
            ) { (results, _) in
                if let results = results {
                    completion(results)
                }
            }
        }
        ```

    3. GMSAutocompletePredictionの配列から取得したい地点の`placeID`を取得し、`lookUpPlaceID(_ placeID: NSString)`メソッドを使用して、地点のの詳細情報を取得する
    
         ```swift
            /// 選択した場所の座標を取得
            private func lookUpPlaceID(with id: String, completion: @escaping ((GMSPlace) -> Void)) {
                placesClient.lookUpPlaceID(id) { [weak self] (place, error) in
                    guard let self else { return }                
                    if let place = place {
                        completion(place)
                    }
                }
            }
        ```

    <br>

    - 郵便番号の場合: 

        ```mermaid
        sequenceDiagram
            participant U as ユーザー
            participant M as モバイル
            participant A as API
            participant DB as Database
            participant CL as CoreLocation
            
            U->>M:郵便番号を入力
            M->>A:入力された郵便番号で住所検索<br>GET api/v1/map_addresses
            A->DB:郵便番号を元にmap_addressesから、<br>都道府県、市区町村を取得
            A-->>M:取得内容を返却
            M->>CL:取得した内容が1都3県以外の場合、CoreLocationで郵便番号検索をかける
            CL-->>M:取得内容を返却
            M-->>U:表示
        ```

        - 1都3県以外も`GET api/v1/map_addresses`から取得したかったが、DBに1都3県以外のの住所情報を用意する必要があったため、サーバ側の変更コストが高かった
        - そのため、DBに登録されていない郵便番号についてはCoreLocationから住所情報を取得している
        
        
        ```swift
        /// CoreLocationで郵便番号を検索
        func fetchAddressFromPostCode(postCode: String, completionHandler: @escaping ([CLPlacemark]?, Error?) -> Void) {
            let geocoder = CLGeocoder()
            geocoder.geocodeAddressString(postCode) { (placemarks, error) in
                completionHandler(placemarks, error)
            }
        }
        ```

### 注意点
住所情報をGoogle Map APIのPlaces APIを使って取得することになるので、無料枠以上の利用になれば別途料金がかかります。

- Places APIの1リクエストあたりの料金（2023年9月現在）
    | 0～100,000/月	 | 100,001＋/月 |
    | -------- | -------- |
    | 0.017米ドル | 0.0136米ドル |
- 1か月あたりの200米ドル分は無料
- [料金設定について](https://mapsplatform.google.com/intl/ja/pricing/)

## 最後に

この記事に記載の仕様は、今後のプロダクト状況次第で変更になる可能性もありますので、その点は予めご了承ください。

詳細な数値は開示できないのですが、GoogleAPIを利用した登録処理の変更でCVRも改善することが出来ましたので、同じような取り組みをされている方の参考になれば幸いです。

ヨシモト∞ホールとのコラボ企画は10/10まで開催していて、毎日芸人さんがおすすめする渋谷の投稿が更新されますので、1都3県内の方も1都3県外の方も、ぜひLoupeを楽しんでもらえると嬉しいです。

https://app.adjust.com/14dj2ku9