📍

[Ruby]FoursquareAPIを使って場所の名前からスポット情報を検索する

2021/09/23に公開

FoursquareのplaceAPIにSuggest CompletionというAPIがあります。
https://developer.foursquare.com/docs/api-reference/venues/suggestcompletion/
場所の名前を与えるとその名前に近い名前の場所情報を返してくれます。

実際にどんなパラメータを付与できて、どんなレスポンスが返ってくるかは上記のドキュメントを見てもらうとして、今回は
・場所の名前
・中心となる緯度・経度
をパラメータとして与え、
・id(Foursquare上のid)
・名前
・緯度経度
・FoursquareでのカテゴリーのiconURL
を取得できるようにRubyで実装してみます。

Client

require 'foursquare/response_body'
require 'foursquare/response_data'

module Foursquare
  class Client
    API_BASE_URL = 'https://api.foursquare.com/v2'

    # FoursquareAPIに仕様変更があっても指定した日付時点での仕様でFoursquareAPIを利用できる
    API_VERSION = '20210918'

    RESPONSE_DATA_CLASS_MAPPING = {
      minivenue: Foursquare::ResponseData::Minivenue
    }

    def initialize
      @client_id     = ENV['FOURSQAURE_CLIENT_ID']
      @client_secret = ENV['FOURSQAURE_CLIENT_SECRET']
      @version       = API_VERSION
    end

    def suggest(query:, lat:, long:)
      send_request(
        data_class_name: :minivenue, # 取得した情報を詰めるClassの名前を指定する
        path:            'venues/suggestcompletion',
        params:          {
                            query: query,
                            ll:    "#{lat},#{long}" # カンマ区切りの文字列として与えます
                          }
      )
    end

    private

    def send_request(data_class_name:, path:, params:)
      response = connection.get(path) do |req|
        params.each do |k, v|
          req.params[k] = v
        end
      end
      response_body = JSON.parse(response.body)

      meta = response_body['meta']
      if meta['code'] == 200
        Foursquare::ResponseBody.new(
          target_data: response_body['response'],
          data_class:  data_class_of(data_class_name)
        )
      else
        # エラーをRaiseするなど
      end
    end

    # Faradayを使ってリクエストする
    def connection
      params = {
        client_id:     @client_id,
        client_secret: @client_secret,
        v:             @version
      }
      @connection ||= Faraday.new(url: API_BASE_URL, params: params)
    end

    def data_class_of(data_class_name)
      RESPONSE_DATA_CLASS_MAPPING.fetch(data_class_name)
    end
  end
end

Response

Foursquareから取得した配列,hashをそのまま使うこともできますが、使う値をクラスに詰めます。
これにより、もしfoursquareから取得できる情報のキーなどに変更があっても、取得したデータをクラスに詰めている箇所だけ変更すれば済むようになります。

module Foursquare
  class ResponseBody
    attr_reader :data

    def initialize(target_data:, data_class:)
      # 配列以外も扱うなら要変更 
      @data = target_data[data_class.array_key].map do |data|
        data_class.new(data)
      end
    end
  end
end
module Foursquare
  class ResponseData
    # Fousquareから取得されるデータの構造に変更があってもこのクラスを修正するだけでいい
    class Minivenue
      attr_reader :id
      attr_reader :name
      attr_reader :category_icon_url

      ICON_SIZE_PX = 64

      def self.array_key
        'minivenues'
      end

      def initialize(data)
        @id       = data['id']
        @name     = data['name']
        @location = data['location']
        @category = set_category_icon_url(data['categories'])
      end

      def lat
        @location['lat']
      end

      def long
        @location['lng']
      end

      private

      def set_category_icon_url(categories)
        category = categories.find { |c| c['primary'] }
        return nil if category.nil?
	# アイコンの画像URLは次のように生成できます: https://developer.foursquare.com/docs/api-reference/venues/categories/#response-fields
        @category_icon_url = "#{category['icon']['prefix']}#{ICON_SIZE_PX}#{category['icon']['suffix']}"
      end
    end
  end
end

使い方

require 'foursquare/client'
# インスタンス生成
client = Foursquare::Client.new
# 検索する名前と中心となる緯度経度を与える
response = client.suggest(lat: 35.65783614386869, long: 139.74067705232687, query: 'ミラノ')

# responseは以下のように、`@data`に値を詰めたクラスの配列がセットされている
=> #<Foursquare::ResponseBody:0x000055686354e6a8
 @data=
  [#<Foursquare::ResponseData::Minivenue:0x000055686354e630
    @category="https://ss3.4sqi.net/img/categories_v2/food/dessert_64.png",
    @category_icon_url="https://ss3.4sqi.net/img/categories_v2/food/dessert_64.png",
    @id="55efafa5498ef7f599dfe6ac",
    @location={"address"=>"東麻布2-12-3", "city"=>"港区", "state"=>"東京都", "postalCode"=>"106-0044", "country"=>"JP", "lat"=>35.65740086958714, "lng"=>139.74130872695832, "distance"=>74},
    @name="ミラノ ドルチェ トレ・スパーデ   (Milano Dolce Tre Spade)">,
   #<Foursquare::ResponseData::Minivenue:0x000055686354e540
    @category="https://ss3.4sqi.net/img/categories_v2/nightlife/default_64.png",
    @category_icon_url="https://ss3.4sqi.net/img/categories_v2/nightlife/default_64.png",
    @id="4b910378f964a520129f33e3",
    @location={"address"=>"銀座8-6-18", "crossStreet"=>"銀座カレラ弐番館ビル 4F", "city"=>"中央区", "state"=>"東京都", "country"=>"JP", "lat"=>35.66835498133474, "lng"=>139.7603679780853, "distance"=>2131},
    @name="銀座ミラノクラブ">,
    # 省略
response.data.first.name
=> "ミラノ ドルチェ トレ・スパーデ   (Milano Dolce Tre Spade)"

Discussion