Cloud Functions(Firebase Admin)でgeofirestoreを使う
Firestoreで緯度、経度を用いた検索をする場合、geofirestore
というパッケージが便利。
ただ、このパッケージはクライアント Firebase SDK
を使用することを前提にしているようで、Cloud Functionsなどで使用する、Firebase Admin SDK
との組み合わせる場合はアレンジが必要だった。
いくつかの失敗を経て、Firebase Admin SDK
とgeofirestore
を組み合わせることに成功したので、参考として書き留めておく。
先に失敗例から
失敗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
});
というように、center
にadmin.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)
}
geopoint
はfirebase.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アプリとの組み合わせでそちらでも位置情報を使った検索を使いたかったので、こういった形で実装した。
参考ドキュメント
参考記事
Discussion