Sort Array by Distance Near User Location from Firebase

Sort array by distance near user location from firebase

This is a complex process, requiring multiple steps. I'll try to explain the steps, but you'll have to do quite some work to turn it into a functioning solution.

Geocoding

First up: an address string like the one you have is not a location. There is no way for a computer to compare two of such strings and reliably know how far apart they are.

So the first thing you'll have to do is to turn the addresses into a more reliable indication of a location, i.e. into a latitude and longitude (a.k.a. lat/lon). The process of turning an address into lat/lon is known as geocoding. It is not really an exact science, but there are plenty of services that are quite good at this geocoding bit.

At the end of this geocoding process you will have a lat/lon combination for each address. That puts the problem back into mathematics, which is a much more exact science.

Geoqueries

Next up you'll need to compare the lat/lon of each address and calculate the distance between them. This is a relatively exact science, if you're willing to ignore inaccuracies near the poles and things like that.

Unfortunately the Firebase Realtime Database can natively only order/filter on a single property. Since a location consists of two properties (latitude and longitude) it can't filter on location without some magic.

Luckily somebody came up with a way to translate lat/lon information into a single string, known as a geohash. For example: the Google office in San Francisco is at lat/lon 37.7900515,-122.3923805, which translate to geohash 9q8yyz. The Googleplex in Mountain View is at lat/lon 37.4219999,-122.0862515, which translates to geohash 9q9hvu.

Unlike the addresses you started with, geohashes are very nicely comparable. To quote the (linked) wikipedia explanation:

nearby places [have] similar prefixes. The longer a shared prefix is, the closer the two places are.

In our two examples above, you can see the the two locations are relatively close to each other because they both start with 9q

There is an open-source library for Firebase called GeoFire that:

  1. makes it easy to store locations (for which you must have the lat/lon) in Firebase as geohashes.
  2. provides querying capabilities so that you can get nodes that are within maximum distance of a location you specify.

I recommend that you check out the iOS version of GeoFire.

How to orderBy the closest distance from current location in AngularFire2?

There are two questions in your post. Let's try to answer each of them.

Q1: "I need a query which will return 10 rows from the firebase which are at the sortest distance from the current locationList item"

A1: As you may know, you will not be able to have the "Firebase database querying engine" triggering your calculateDistance() function while querying, i.e. Firebase can only sort based on the methods detailed here https://firebase.google.com/docs/database/web/lists-of-data#sorting_and_filtering_data.

In other words, you will have to loop over the entire set of data from the data node in an environment that can execute your function: in your front-end or maybe in a Cloud Function (e.g. called by HTTPS, as a REST API)


Q2: "Why is that? startAt and endAt should work same as the limitToFirst(10)."

A2: No they are different.

As explained in the doc, "startAt() returns items greater than or equal to the specified key or value, depending on the order-by method chosen."

So first, you have to include a order-by-method at this line

this.firebaseDBProvider.list('data', ref => ref.startAt(0).endAt(10));

And secondly, depending on the order-by-method, this line is not going to necessarily return 10 records but all the records for which the order-by parameter is between 0 and 10.

How to sort users according to distances,i.e arrange them has nearest users?,i am able to bring users within certain radius, without sorting

This can easily solved by adding "id" in the for loop itself,
like shown below:

       for(var value in val){ // this loop is to calculate the distance of the current user to the other users 
info.add(certainrangeusers(
away: await Geolocator().distanceBetween(currentUser.point.latitude,currentUser.point.longitude,value.userpoint.latitude, value.userpoint.longitude),
id: value.id,
));
info.sort((a,b) => a.away.compareTo(b.away));//here we r sorting them
}

Swift Firebase Sort By Distance From Post

There's a number of ways to accomplish this task but here's what I would start with:

a class to keep track of requests and populate a tableView

class RequestClass {
var requestId = ""
var userId = ""
var distance = ""
}

var requestsArray = [RequestClass]()

then observe the requests node (this is pseudo code but provides the concept)

requestsNode.observe(.childAdded.... { snapshot in 
let dict = snapshot.value as! [String: Any]
let reqId = snapshot.key
let userId = dict["user_id"] as! String
let long = dict["longitute"] as! Double
let lat = dict["latitide"] as! Double

let distance = //calc distance between this user position and incoming request position

let request = RequestClass()
request.requestId = regId
request.userId = userId
request.distance = distance

self.requestsArray.append(request)
self.requestsArray.sort { $0.distance < $1.distance }
self.tableView.reloadData()
}

with the above code, any time a new request comes in, the Firebase observe closure fires and calculates the distance between the user and the person making the request.

That request is added to an array and then sorted by distance and then the tableView used to display the data is updated.

You can expand on this to take in account a vehicle moving so it will automatically update the distance to all current requests etc.

Query for nearby locations

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:

  • this blog post introducing Geofire 2
  • the demo app that used to show local busses moving on a map app showing SF busses.
    The app doesn't work anymore (the data isn't being updated), but the code is still available.
  • this video and documentation on how to implement geoqueries on Cloud Firestore.


Related Topics



Leave a reply



Submit