18

I am using Firebase to store users with their last scanned latitude and longitude.

An entry looks like this:

"Bdhwu37Jdmd28DmenHahd221" : {
  "country_code" : "at",
  "firstname" : "John",
  "gender" : "m",
  "lat" : 11.2549387,
  "lon" : 17.3419559
}

Whenever a user presses a specific "search" button, I want my Firebase function to fetch the people nearest to the person who sent the request.

Since Firebase only allows for querying after one field, I decided to add the country_code, to kind of have some range-restrictions and query for that field. But it is still super slow when I load every user of a specific country and then check for the smallest distance between a given user and all the other users in the same country.

Already with 5 users, the function takes like 40 seconds to achieve the results.

I have also read about compound Indexes, but I would need to somehow combine the latitude and the longitude and query for both fields.

Is there any way to either get a second and third query involved here (e.g. search for the same country_code, and then for a similar longitude and latitude) or do I have to solve this inside my server code ?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Thomas
  • 2,375
  • 2
  • 17
  • 32

1 Answers1

34

The Firebase Database can only query by a single property. So the way to filter on latitude and longitude values is to combine them into a single property. That combined property must retain the filtering traits you want for numeric values, such as the ability to filter for a range.

While this at first may seem impossible, it actually has been done in the form of Geohashes. A few of its traits:

  1. It is a hierarchical spatial data structure which subdivides space into buckets of grid shape

So: Geohashes divide space into a grid of buckets, each bucket identified by a string.

  1. Geohashes offer properties like arbitrary precision and the possibility of gradually removing characters from the end of the code to reduce its size (and gradually lose precision).

The longer the string, the larger the area that the bucket covers

  1. As a consequence of the gradual precision degradation, nearby places will often (but not always) present similar prefixes. The longer a shared prefix is, the closer the two places are.

Strings starting with the same characters are close to each other.

Combining these traits and you can see why these Geohashes are so appealing for use with the Firebase Database: they combine the latitude and longitude of a location into a single string, where strings that are lexicographically close to each other point to locations that are physically close to each other. Magic!

Firebase provides a library called Geofire, which uses Geohashes to implement a Geolocation system on top of its Realtime Database. The library is available for JavaScript, Java and Objective-C/Swift.

To learn more about Geofire, check out:

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Thank you, I will read and try this. Is it possible, that Cloud Functions are super slow? Only querying for 3 people takes me like 40 seconds every time (not only the first time) and I only access 2 databases. – Thomas Apr 12 '17 at 10:43
  • If you consistently have performance that you think is unreasonable (even when you consider that Cloud Functions is in beta), open a question with the [minimal code that reproduces that problem](http://stackoverflow.com/help/mcve). – Frank van Puffelen Apr 12 '17 at 12:02
  • GeoFire works fine, but I can't make it work, when the location of a user is nested within the userdata. My geoFire reference points at "/user" and my geoFire data is at /user/$uid/location, does geoFire automatically search nested fields or do I have to define somewhere, where the "g" value is found? It seems like the algorithm is looking for "g" directly beneath my "/user" entry and not at "location" – Thomas Apr 13 '17 at 15:15
  • When using Geofire, you always get a separate geo-index. That location purely maps locations to keys (and vice versa). Your actual user data will be elsewhere in the database and you should load it was the keys come into range of your query and leave it again. – Frank van Puffelen Apr 13 '17 at 15:30
  • It is working now, thank you very much. The speed is also fine now, probably took so long, because I did not implement it correctly :) – Thomas Apr 13 '17 at 16:21
  • Will you be able to post the working code , Thanks in advance – andy bit1 Nov 09 '18 at 17:48
  • @FrankvanPuffelen used the GeoQuery for my app, but it doesn't work when the radius is below 8000km! . – Aditya Joardar Mar 16 '20 at 07:58