🙌

Cloud Functions(Firebase Admin)でgeofirestoreを使う

2022/07/15に公開

Firestoreで緯度、経度を用いた検索をする場合、geofirestoreというパッケージが便利。

ただ、このパッケージはクライアント Firebase SDKを使用することを前提にしているようで、Cloud Functionsなどで使用する、Firebase Admin SDKとの組み合わせる場合はアレンジが必要だった。

いくつかの失敗を経て、Firebase Admin SDKgeofirestoreを組み合わせることに成功したので、参考として書き留めておく。

先に失敗例から

失敗1:admin.firestore.GeoPointを使用する

geofirestoreの実装例を見ると、データ登録は以下のように記述されている。

// Create a Firestore reference
const firestore = firebase.firestore();

// Create a GeoFirestore reference
const GeoFirestore = geofirestore.initializeApp(firestore);

// Create a GeoCollection reference
const geocollection = GeoFirestore.collection('restaurants');

// Add a GeoDocument to a GeoCollection
geocollection.add({
  name: 'Geofirestore',
  score: 100,
  // The coordinates field must be a GeoPoint!
  coordinates: new firebase.firestore.GeoPoint(40.7589, -73.9851)
})

ポイントは↓の部分で、firebase.firestore.GeoPointとして、位置情報を登録する必要がある。

coordinates: new firebase.firestore.GeoPoint(40.7589, -73.9851)

Admin SDKを使用している場合、同じように

import * as admin from "firebase-admin";

(省略)

coordinates: new admin.firestore.GeoPoint(40.7589, -73.9851)

と書くことができる。

これで特にエラーは出ないため問題なさそうだが、geocollection.near()で検索してみてもヒットしない。

こちらの記事も参考にさせてもらったが、本来であれば、gというフィールドが自動作成され、geohashも追加されるらしい。

しかし、new admin.firestore.GeoPoint(x, y)で登録したデータだと、gは追加されていない。

gがないことで、検索が機能していないと考えられる。

失敗2: クライアント用のFirebase SDKを導入する

Admin SDKがダメなら、クライアント用のFirebase SDKを導入すればいいのでは?、そう思い試してみた。

詳細は割愛するが、

  • Firebase ProjectにWebを追加
  • npm install firebase

して、firebase/appが使用できるようになったところで、geofirestoreと同じように実装した。

コード上はエラーはなくデプロイもできたが、動作させていると以下のようなエラーが出た。

@firebase/firestore: Firestore (9.8.4): Connection GRPC stream error. Code: 3 Message: 3 INVALID_ARGUMENT: Invalid resource field value in the request. 

@firebase/firestore: Firestore (9.8.4): Could not reach Cloud Firestore backend. Connection failed 1 times. Most recent error: FirebaseError: [code=invalid-argument]: 3 INVALID_ARGUMENT: Invalid resource field value in the request. 

This typically indicates that your device does not have a healthy Internet connection at the moment. The client will operate in offline mode until it is able to successfully connect to the backend. 

色々調べたり試したが解決せず。

最終的にこちらの記事にたどりついた。

どうやらFirebase SDKはサーバーからの利用は想定していないようで、この方法も無理そう。

解決策

ようやく本題の解決策。

クライアント用のSDK(firebase/app)は使用できないので、失敗1と同様に

coordinates: new firebase.firestore.GeoPoint(40.7589, -73.9851)

で登録する方法はそのままにする。

また、検索についても

geocollection.near({
    center: new admin.firestore.GeoPoint(x, y)
    radius: 1000
  });

というように、centeradmin.firestore.GeoPointを指定する。

これだけだと失敗1と変わらず検索はできないので、検索用のgを手動で追加する必要がある。

試しにFirestoreのコンソールから以下のようにデータを追加したところ、期待通り検索できた。

コード上追加する場合は、↓のように書けばいいはず。

coordinates: new firebase.firestore.GeoPoint(40.7589, -73.9851),
g: {
  geohash: "XXXXXX",
  geopoint: new firebase.firestore.GeoPoint(40.7589, -73.9851)
}

geopointfirebase.firestore.GeoPointそのままでいいが、geohashは自前で生成する必要がある。

geohashの生成

geohashについては、Firebase公式に記述があったので、これを参考にする。

$ npm install --save geofire-common

geofire-commonを追加し、データ登録部分を以下のように変更する。

import * as geofire from "geofire-common"; // 追加

// ついでに変数にしておく
const lat = 40.7589;
const long = -73.9851;

coordinates: new firebase.firestore.GeoPoint(lat, long),
g: {
    geohash: geofire.geohashForLocation([lat, long]),
  geopoint: new firebase.firestore.GeoPoint(lat, long)
}

これで動かしてみると・・・

無事gが追加され、検索も期待通り動作した 🎉

終わりに

かなり回り道をしてしまったが、Cloud Functionsでgeofirestoreを使うことができた。

Cloud Functions単体で使う場合はgeofirestoreにこだわる必要もないかもしれないが、今回はFlutterアプリとの組み合わせでそちらでも位置情報を使った検索を使いたかったので、こういった形で実装した。

参考ドキュメント

https://geofirestore.com/

https://firebase.google.com/docs/firestore/solutions/geoqueries?hl=ja#solution_geohashes

参考記事

https://zenn.dev/nekoniki/articles/a17cf47ac5612b969917

https://zenn.dev/ytr0903/articles/76386126e07549

Discussion