Help us understand the problem. What is going on with this article?

Firestoreで一定距離内にいるユーザーetc..をMap上に表示させたい

More than 1 year has passed since last update.

位置情報を用いたサービスをFirestoreで作りたい時、
「一定距離内にいるユーザーetc..をMap上に表示させたい」
ということがあります。

今回Firestoreで実現する際に少し手間取ったのでまとめます

1. 【事前知識】Firestoreの位置情報

Firestoreの位置情報はGeopointという形で保管されています
緯度経度の位置情報が保管される値です。普通にFirestoreに位置情報を保管する場合こちらを使うことになります。

https://developers.google.com/android/reference/com/google/firebase/firestore/GeoPoint

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)
                }
          }

image.png

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

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away