zerebom / tsukumen-backend

つくばのラーメンを推薦するアプリ”ツクめん”のバックエンド実装レポジトリ
2 stars 0 forks source link

現在地周辺データの取得方法に関する実装方針と課題 #11

Open zerebom opened 3 years ago

zerebom commented 3 years ago

概要

つくめんからちかめんに変更する場合、ユーザーの現在地に沿ってデータを表示する必要がある。 これを実現するAPIには以下の種類がある。

  1. ~アプリ起動時に、ユーザーの現在地周辺のデータをGCPから取得し、データを整形して返す(DB経由なし)~
  2. ~日本全国の全ラーメン店舗ををDBに格納しておき、ユーザーの現在地周辺のデータを絞り込んで返す~
    • 初回検索時: フロント側の要求に沿って、関連データをGCPから直接とってくる。その後、DBに入力する
      • 次回検索時: 手持ちのDBから取得する

GCPのAPI利用制限的に、3を実装したい。

3を実装する上での現状の課題

次のアクション

その他課題

zerebom commented 3 years ago

gcpのAPIが受け取れる検索クエリの種類を検証。緯度経度を用いた検索は行えるのか?

A. Nearby Search requestsを使用すれば行える。

https://developers.google.com/maps/documentation/places/web-service/search#find-place-examples

gcpの場所検索で使用できるAPIはFind Place requestsNearby Search requests がある。 ある位置情報を中心にその周辺地域で検索する場合は検索結果が複数帰ってくるNearby Search を使う。 しかし、Nearby Searchはfiledsによる絞り込みができないため少数回使用しただけで、API使用制限に引っかかってしまう可能性が高い。

Nearby Searchが受け取れる引数

以下のように検索ができる。

place_result = client.places_nearby(
                        location=loc, 
                        radius=5000, 
                        type='food', 
                        language='ja', 
                        keyword='ラーメン') 

place_resultに各店舗データが格納されている。 Google mapを触った感じ、radiusが大きくなると、キーワードから少しずれたデータは欠損してしまうみたい。

広域での検索 image

より寄った検索 image

今まで、全然つくばのラーメン屋がヒットしてなかったのは、radius=20000 にしていたため。 すごく広域のデータが格納されていたが、漏れだらけだった。

Nearby Searchを使いつつGCP APIのリクエスト回数を減らす方法

以下のように工夫をこらす。

  1. フロント側からリクエストのあった緯度経度の履歴をためておく
  2. フロント側からリクエストがあった場合、ユーザーの緯度経度(現在位置)と、過去の緯度経度履歴(位置履歴)を比較する
  3. 位置履歴と現在位置が近くにあった場合はDBから検索をかける
  4. 位置履歴と現在位置が遠かった場合は、新規にGCP APIから検索をかけて値を返す

位置履歴と現在位置がどれくらい近ければ検索を行うか?

決め打ちだけど、半径3km以内の店舗を検索するようにする。 そして、現在位置と半径検索が1.5km以内だったら(面積換算で)7割のデータは被るので検索は行わない。

半径3km同士の円の面積が被る割合。横軸が円の中心の距離。縦軸がかぶっている面積比率。 参考: https://tjkendev.github.io/procon-library/python/geometry/circles_intersection_area.html image

距離計算はどうやって行うの?

現在位置とすべての位置履歴の距離を ヒュベニの公式というので緯度経度→距離に直す。 その後、最も近い位置履歴が1.5km以内ならそのデータを返すようにする O(N)の計算量なので、位置履歴が溜まってきたら別途方法を考える。

https://www.gis-py.com/entry/py-latlon2distance

``` def cal_distance(latlon_1=(35.562479, 139.716073), latlon_2=(35.278699, 139.670040)): POLE_RADIUS = 6356752.314245 # 極半径 EQUATOR_RADIUS = 6378137.0 # 赤道半径 # 緯度経度をラジアンに変換 lat1, lon1 = math.radians(latlon_1[0]), math.radians(latlon_1[1]) lat2, lon2 = math.radians(latlon_2[0]), math.radians(latlon_2[1]) lat_difference = lat1 - lat2 # 緯度差 lon_difference = lon1 - lon2 # 経度差 lat_average = (lat1 + lat2) / 2 # 平均緯度 e2 = (math.pow(EQUATOR_RADIUS, 2) - math.pow(POLE_RADIUS, 2)) \ / math.pow(EQUATOR_RADIUS, 2) # 第一離心率^2 w = math.sqrt(1 - e2 * math.pow(math.sin(lat_average), 2)) m = EQUATOR_RADIUS * (1 - e2) / math.pow(w, 3) # 子午線曲率半径 n = EQUATOR_RADIUS / w # 卯酉線曲半径 distance = math.sqrt(math.pow(m * lat_difference, 2) + math.pow(n * lon_difference * math.cos(lat_average), 2)) # 距離計測 print(distance / 1000) ```
zerebom commented 3 years ago

APIに必要な機能は何か?

zerebom commented 3 years ago

Rails APIとgcp APIはどうやって接続するか?

Railsのデフォルト機能とPythonのgcp クライアントを両方使うために、以下のような構成にする

前提: サーバー内部にgcp, DB間のデータフローを司るPythonサーバーを立てておく

  1. フロント側からRails APIにアクセスが飛んでくる
  2. RailsからPythonサーバーにリクエストを飛ばす
  3. PythonサーバーでDBを確認し、新たにgcpサーバーにデータを取ってくる必要があるかを確認する
  4. RailsAPIは↑でPythonがデータをよしなにしてくれてるという前提のもとDBに検索をかけて返す

懸念点

Python→gcp→DB insert→API Responseにどれくらい時間がかかるのか?

調査中