位置情報を用いたサービスをFirestoreで作りたい時、
「一定距離内にいるユーザーetc..をMap上に表示させたい」
ということがあります。
今回Firestoreで実現する際に少し手間取ったのでまとめます
#1. 【事前知識】Firestoreの位置情報
Firestoreの位置情報はGeopointという形で保管されています
緯度経度の位置情報が保管される値です。普通にFirestoreに位置情報を保管する場合こちらを使うことになります。
#2. Geopointの問題点
緯度と経度を複合条件にした、検 索 が で き な い...
下記はjsSDKのissueですが、Geopointについてfirebaseのメンバーからこんなコメントが
https://github.com/firebase/firebase-js-sdk/issues/826
Sorry, we have nothing to announce at this time.
I don't have a good written summary of this, but we did announce a few things at Cloud Next '18--these things are just around the corner. Nearly all of our effort lately has been focused on transitioning from a limited beta to something much more broadly useful. I realize this is no consolation for this specific feature, but at least you can see we've been busy :-).
雑訳
”今それより優先することあるから時間ないよ!”
ということで「FireStore上で位置情報を元に検索するロジック」を別で作ることに
3. 【解決策】ライブラリGeoFirestoreでFireStore上で位置情報を元にドキュメントを検索する
一旦コードを書きます
宣言周り
import Geofirestore
// Firestore上で位置情報とドキュメントの組み合わせを保管したい場所
let geoFirestore:GeoFirestore = GeoFirestore(collectionRef: Firestore.UserLocation.reference)
位置情報とドキュメントの組み合わせを保存
geoFirestore.setLocation(location: location, forDocumentWithID: user.id) { (err) in
if let error = err {
print("ERROR CREATING DOCUMENT")
print(error)
}
}
ドキュメントの取得処理
geoFirestore.query(withCenter: location, radius: radius).observe(.documentEntered, with: { (key, location) in
// keyには該当する位置情報のドキュメントIDが入ってくる
if let key = key, let _ = location {
Firestore.User.get(key, block: { (user, error) in
if let user = user {
let annotation = MKPointAnnotation()
annotation.coordinate = location.coordinate
annotation.title = key
debugPrint("[documentEntered] Added userId : \(key)!")
}
})
}
})
geoFirestore.query(withCenter: location, radius: radius).observe(.documentMoved, with: { (key, location) in
if let key = key, let _ = location {
Firestore.User.get(key, block: { (user, error) in
if let user = user {
if let annotation =
self.mapView.annotations.first(where: { (annotation) -> Bool in
return annotation.title == key
})
{
self.mapView.removeAnnotation(annotation)
debugPrint("[documentMoved] Removed userId : \(key)!")
self.mapView.addAnnotation(annotation)
debugPrint("[documentMoved] Added userId : \(key)!")
}
}
})
}
})
//
geoFirestore.query(withCenter: location, radius: radius).observe(.documentExited, with: { (key, location) in
if let key = key {
if key == Auth.auth().currentUser?.uid { return }
Firestore.User.get(key, block: { (user, error) in
if let user = user {
if let annotation =
self.mapView.annotations.first(where: { (annotation) -> Bool in
return annotation.title == key
})
{
self.mapView.removeAnnotation(annotation)
debugPrint("[documentExited] Removed userId \(key)!")
}
}
})
}
})
4. 【解決策】GeoFirestore 宣言編
宣言編です
importと
位置情報とドキュメントの組み合わせを保管しておくFirestoreのドキュメントを宣言する必要があります。
import Geofirestore
// FireStore上で位置情報とドキュメントの組み合わせを保管したい場所
let geoFirestore:GeoFirestore = GeoFirestore(collectionRef: Firestore.UserLocation.reference)
5. 【解決策】GeoFirestore 位置情報保存編
位置情報とドキュメントの組み合わせを保存しています。
上手くいくとFireStore上で下記のように保存されます
geoFirestore.setLocation(location: location, forDocumentWithID: user.id) { (err) in
if let error = err {
print("ERROR CREATING DOCUMENT")
print(error)
}
}
6. 【解決策】GeoFirestore ドキュメント取得編
ドキュメントの取得処理 は
- 新たに取得データが増えた時 --> .documentEntered
- 取得データが更新された時 ---> .documentMoved
- 取得できていたデータが見えなくなった時 ---> .documentExited
の3パターンの処理が必要なようです。
下記のSampleではannotationのtitleにドキュメントIDを保存して、それを元に削除の処理を行なっています。
ドキュメントの取得処理
geoFirestore.query(withCenter: location, radius: radius).observe(.documentEntered, with: { (key, location) in
// keyには該当する位置情報のドキュメントIDが入ってくる
if let key = key, let location = location {
Firestore.User.get(key, block: { (user, error) in
if let user = user {
let annotation = MKPointAnnotation()
annotation.coordinate = location.coordinate
annotation.title = key
debugPrint("[documentEntered] Added userId : \(key)!")
}
})
}
})
geoFirestore.query(withCenter: location, radius: radius).observe(.documentMoved, with: { (key, location) in
if let key = key, let location = location {
Firestore.User.get(key, block: { (user, error) in
if let user = user {
if let annotation =
self.mapView.annotations.first(where: { (annotation) -> Bool in
return annotation.title == key
})
{
self.mapView.removeAnnotation(annotation)
debugPrint("[documentMoved] Removed userId : \(key)!")
annotation.coordinate = location.coordinate
self.mapView.addAnnotation(annotation)
debugPrint("[documentMoved] Added userId : \(key)!")
}
}
})
}
})
//
geoFirestore.query(withCenter: location, radius: radius).observe(.documentExited, with: { (key, location) in
if let key = key {
if key == Auth.auth().currentUser?.uid { return }
Firestore.User.get(key, block: { (user, error) in
if let user = user {
if let annotation =
self.mapView.annotations.first(where: { (annotation) -> Bool in
return annotation.title == key
})
{
self.mapView.removeAnnotation(annotation)
debugPrint("[documentExited] Removed userId \(key)!")
}
}
})
}
})
以上で「Firestore上で位置情報を元に検索するロジック」ができました。
7. 備考
GeoFirestoreはiosだけでなく、Androidやjs版もあるので、同じ考え方でできると思います。
(Firestore標準機能にならないかなぁ...)
https://github.com/imperiumlabs/GeoFirestore-IOS
また今回はPringというFirestoreを扱う際のライブラリを使っています。
https://github.com/1amageek/Pring
8. サンプル
余裕があればどっかで作ります...
9. 参考
Firestore を扱う際の便利なライブラリ
https://qiita.com/1amageek/items/ca7574698c50cc076347
GeoFirestore ios版
https://github.com/imperiumlabs/GeoFirestore-IOS
firebase時間無い宣言issue
https://github.com/firebase/firebase-js-sdk/issues/826