Swift Firestore GeoFire – How to properly use for best performance in location app with large database?

huangapple go评论67阅读模式
英文:

Swift Firestore GeoFire - How to properly use for best performance in location app with large database?

问题

你当前的数据库结构看起来是这样的:

FirestoreDB -> Users -> UserId -> 文档数据 = [ 纬度, 经度, GeoHash ]

你正在尝试理解如何在数据库和代码中优化性能和降低成本,但你对如何动态地处理位置信息和用户位置更新感到困惑。

你提到了 Firebase 的 GeoFire,但你对如何将其与 Firestore 集成以及如何使用动态位置信息进行查询感到困惑。

我建议你考虑将城市/位置信息存储为单独的文档,而不是为每个用户单独存储。这样你可以根据位置信息来查询,而不是遍历所有用户文档。

你可以使用 GeoHashes 来在 Firestore 中进行地理位置查询,但是确保你的查询是有限的,以避免性能问题。

如果你对如何实现这一点还有疑问,请随时提问。

英文:

Hi I am working on a location based app (ie uber, doordash, tinder, bumble, etc) that is expected to have a large database of users from around the world. I am new to both Xcode + swift and firebase and I'm currently using Firestore. I have recently found out about GeoFire and using a hash but it seems that most of the information I am finding is outdated.

What I am trying to understand is the proper way to structure my database and code for best performance and lower costs.

My current database looks something like this:

FirestoreDB -> Users -> UserId -> Document Data = [ Latitude, Longitude, GeoHash ]

And the code I am using is from Firebase docs (*I do not have any collections/documents currently for cities, countries etc.) -

// Compute the GeoHash for a lat/lng point
let latitude = 51.5074
let longitude = 0.12780
let location = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)

let hash = GFUtils.geoHash(forLocation: location)

// Add the hash and the lat/lng to the document. We will use the hash
// for queries and the lat/lng for distance comparisons.
let documentData: [String: Any] = [
    "geohash": hash,
    "lat": latitude,
    "lng": longitude
]

let londonRef = db.collection("cities").document("LON")
londonRef.updateData(documentData) { error in
    // ...
}

And in the same doc, the code to query -

// Find cities within 50km of London
let center = CLLocationCoordinate2D(latitude: 51.5074, longitude: 0.1278)
let radiusInM: Double = 50 * 1000

// Each item in 'bounds' represents a startAt/endAt pair. We have to issue
// a separate query for each pair. There can be up to 9 pairs of bounds
// depending on overlap, but in most cases there are 4.
let queryBounds = GFUtils.queryBounds(forLocation: center,
                                      withRadius: radiusInM)
let queries = queryBounds.map { bound -> Query in
    return db.collection("cities")
        .order(by: "geohash")
        .start(at: [bound.startValue])
        .end(at: [bound.endValue])
}

var matchingDocs = [QueryDocumentSnapshot]()
// Collect all the query results together into a single list
func getDocumentsCompletion(snapshot: QuerySnapshot?, error: Error?) -> () {
    guard let documents = snapshot?.documents else {
        print("Unable to fetch snapshot data. \(String(describing: error))")
        return
    }

    for document in documents {
        let lat = document.data()["lat"] as? Double ?? 0
        let lng = document.data()["lng"] as? Double ?? 0
        let coordinates = CLLocation(latitude: lat, longitude: lng)
        let centerPoint = CLLocation(latitude: center.latitude, longitude: center.longitude)

        // We have to filter out a few false positives due to GeoHash accuracy, but
        // most will match
        let distance = GFUtils.distance(from: centerPoint, to: coordinates)
        if distance <= radiusInM {
            matchingDocs.append(document)
        }
    }
}

// After all callbacks have executed, matchingDocs contains the result. Note that this
// sample does not demonstrate how to wait on all callbacks to complete.
for query in queries {
    query.getDocuments(completion: getDocumentsCompletion)
}

According to this, for my situation I would need to create a collection for each city/location(?) and query all the "userId" documents in the database, which of course wouldn't be optimal. I see the functions for saving a key and retrieving by key, such as -

  geoFire.getLocationForKey("firebase-hq") 

but I don't see the key being added to Firestore and I'm not sure how this would even work in my situation since any location and users' locations needs to be dynamic and needs to update on the go? So how can that be used in a dynamic way so documents can be grouped by key(?) and queries ran against those documents based on the key that matches the current location's hash?

I'm unsure what is the best approach on this, there are a lot of popular location based apps out there so I'm wondering how do they handle it. Any suggestions is appreciated, thanks.

EDIT:
To clarify further: I am trying to use Firestore geohashes but I am looking for a better way rather than querying every document in the database.

答案1

得分: 0

我想出了一个解决方案,我认为这对我来说是最有意义的,但我仍然不确定性能是否更好的方式。

这个 geoFire.getLocationForKey("firebase-hq") 给了我一个想法:

  1. 我正在使用反向地理编码器从用户坐标获取位置信息。

  2. 然后将关键位置存储为Firestore中的集合/文档,其中我分配用户ID或从位置中删除用户ID。

这样,我只需要查询正确位置的文档,而不是查询整个数据库。仍在努力解决问题,但从理论上讲,我认为这似乎更加优化。

英文:

I came up with a solution which I think makes the most sense to me however I'm still unsure about the performance and if there's a better way.

This geoFire.getLocationForKey("firebase-hq") gave me an idea:

  1. I am using reversegeocoder to get location info from user coordinates

  2. and then store key locations as collections/documents in Firestore, where I assign user ids or remove user ids from a location.

This way I only have to query the documents in the correct location instead of querying the entire database. Still working things out but in theory I think this seems a bit more optimal.

huangapple
  • 本文由 发表于 2023年5月18日 07:32:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/76276824.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定