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.
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:
I am using reversegeocoder to get location info from user coordinates
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.