ElixirDesktopで作るスマホアプリ Part 12 GooglePlacesAPIとの通信

# はじめに

この記事はElixirアドベントカレンダー2024のシリーズ２、16日目の記事です

https://qiita.com/advent-calendar/2024/elixir

Google Places APIと通信する部分を作ります


# Google Places APIとは

テキストや緯度経度から周辺の施設を検索するAPIです

https://developers.google.com/maps/documentation/places/web-service/overview?hl=ja


今回はライブラリを使うほどでもないので、GoogleのAPIを直に叩きます

使用するAPIキーは以下を参考にして取得してください

https://developers.google.com/maps/documentation/places/web-service/get-api-key?hl=ja


# ライブラリの追加

APIと通信を行うHTTP Clientを追加します


```diff_elixir:mix.exs
  defp deps do
    [
      ...
-     {:ex_machina, "~> 2.8.0", only: :test}
+     {:ex_machina, "~> 2.8.0", only: :test},
+     {:req, "~> 0.5.0"}      
    ]
  end
```

# API通信部分作成

通信部分を実装します

APIエンドポイントやキーをモジュール定数として設定します

このモジュールでは以下の関数を実装します

テキスト検索を行う serach_by_text
緯度経度から周辺の施設を検索する near_search
一覧から詳細を取得する search_by_place_id
スポットの写真（写真はレスポンスにないのでIDを元に別APIを叩く)を取得するget_place_photo
住所から国と郵便番号を除外するsormat_address

基本的にAPIを叩いてレスポンスから必要なデータだけ取り出すだけです

```elixir
defmodule Trarecord.GoogleApi do
  @base_url "https://maps.googleapis.com/maps/api/place/"
  @api_key Application.compile_env(:trarecord, :google_api_key)
  @key "key=#{@api_key}"
  @lang "&language=ja"

  def search_by_text(text) do
    (@base_url <> "textsearch/json?" <> @key <> "&query=#{text}" <> @lang)
    |> URI.encode()
    |> Req.post!(headers: [{"Content-Length", 0}])
    |> Map.get(:body)
    |> Map.get("results")
    |> Enum.map(fn result ->
      %{
        name: result["name"],
        address: result["formatted_address"] |> format_address(),
        lat: result["geometry"]["lat"],
        lng: result["geometry"]["lng"],
        place_id: result["place_id"],
        icon: result["icon"]
      }
    end)
  end

  def near_search(lat, lng) do
    (@base_url <>
       "nearbysearch/json?" <>
       @key <>
       "&location=#{lat},#{lng}" <>
       "&radius=500" <>
       @lang)
    |> URI.encode()
    |> Req.post!(headers: [{"Content-Length", 0}])
    |> Map.get(:body)
    |> Map.get("results")
    |> Enum.map(fn result ->
      %{
        name: result["name"],
        address: result["vicinity"],
        lat: result["geometry"]["lat"],
        lng: result["geometry"]["lng"],
        place_id: result["place_id"],
        icon: result["icon"]
      }
    end)
  end

  def search_by_place_id(id) do
    endpoint = "details/json?"
    ref = "&reference=#{id}"

    params = [
      "formatted_address",
      "geometry",
      "icon",
      "name",
      "formatted_phone_number",
      "place_id",
      "photos",
      "url",
      "website"
    ]

    fields = "&fields=#{Enum.join(params, ",")}"

    (@base_url <> endpoint <> @key <> ref <> fields <> @lang)
    |> URI.encode()
    |> Req.post!(headers: [{"Content-Length", 0}])
    |> Map.get(:body)
    |> Map.get("result")
    |> then(&[&1])
    |> Enum.map(fn result ->
      image =
        case result["photos"] do
          nil ->
            "/images/blank.png"

          photos ->
            List.first(photos) |> Map.get("photo_reference") |> get_place_photo()
        end

      %{
        name: result["name"],
        address: result["formatted_address"] |> format_address(),
        lat: result["geometry"]["location"]["lat"],
        lng: result["geometry"]["location"]["lng"],
        place_id: result["place_id"],
        icon: result["icon"],
        image: image,
        map_url: result["url"],
        website: result["website"],
        phone: result["formatted_phone_number"]
      }
    end)
    |> List.first()
  end

  def get_place_photo(photo_ref) do
    endpoint = "photo?"
    q = "&maxwidth=400&photoreference=#{photo_ref}"

    (@base_url <> endpoint <> @key <> q)
    |> URI.encode()
    |> Req.post!(redirect: false, headers: [{"Content-Length", 0}])
    |> Map.get(:headers)
    |> Enum.find_value(fn {k, v} -> if k == "location", do: v end)
    |> List.first()
  end

  def format_address(str) do
    str
    |> String.replace(~r/日本、/, "")
    |> String.replace(~r/〒\d{3}-?\d{4}/, "")
  end
end
```

![スクリーンショット 2025-01-01 1.50.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/8ee1d222-16fc-79b9-7b65-1d1ebb69d7ad.png)

# 最後に

GooglePlacesAPIをElixirから叩いて必要なデータを取得するモジュールを作成しました

Elixirだとレスポンスを|>で繋いでデータを整形でき、シンプルにできて非常に楽でした

次は実際に使ってデータを保存する箇所を実装していきます

本記事は以上になりますありがとうございました