seven
seven

Reputation: 1654

How can I sort my data in firestore db by the location from the user / nearest geohash?

I have an app written in React Native with Firebase, I have a list of clients in db with their geohashes, now I need to display the list of my clients to the user sorted from the nearest to the furthest. In the docs examples, it says

    const q = db.collection('cities')
      .orderBy('geohash')
      .startAt(b[0])
      .endAt(b[1]);

however the problem is there is no .collection on my firebase object, I instantiate it like so

import { initializeApp } from "firebase/app";
import { Firestore, getFirestore } from "firebase/firestore";
import Constants from "expo-constants";

let firestore;
if (Constants.manifest?.extra) {
  const {
    FIREBASE_API_KEY,
    FIREBASE_APP_ID,
    FIREBASE_AUTH_DOMAIN,
    FIREBASE_MESSAGING_SENDER_ID,
    FIREBASE_PROJECT_ID,
    FIREBASE_STORAGE_BUCKET,
  } = Constants.manifest.extra;

  const firebaseConfig = {
    apiKey: FIREBASE_API_KEY,
    authDomain: FIREBASE_AUTH_DOMAIN,
    projectId: FIREBASE_PROJECT_ID,
    storageBucket: FIREBASE_STORAGE_BUCKET,
    messagingSenderId: FIREBASE_MESSAGING_SENDER_ID,
    appId: FIREBASE_APP_ID,
  };

  const app = initializeApp(firebaseConfig);
  firestore = getFirestore();
}
export default firestore as Firestore;

in the example, the Firebase db is instantiated like so

db = firebase.firestore(app);

but there is no firebase object to be imported from "firebase/firestore"

How can I sort my data in db by the location from the user?

Thanks.

Upvotes: 2

Views: 1009

Answers (2)

Frank van Puffelen
Frank van Puffelen

Reputation: 599571

The example code you started with is using the namespaced syntax of Firebase SDK version 8 and below. Your code however is importing Firebase SDK version 9 or later, which use a new modular syntax.

If we take this v8 code:

const q = db.collection('cities')
  .orderBy('geohash')
  .startAt(b[0])
  .endAt(b[1]);

The equivalent in v9 would be:

import { collection, query, orderBy, startAt, endAt } from "firebase/firestore";
...
const q = query(collection(q, 'cities'), 
  orderBy('geohash'),
  startAt(b[0]),
  endAt(b[1])
);

I typically keep the Firebase documentation handy for such code translations, as for example the section on ordering and limiting data contains samples for bth the v8 and v9 syntax side by side. Reading the v9 upgrade guide is also always a good idea.

Update (2023-07-19): the documentation for performing geoqueries on Firestore was just update to include code samples with the modular syntax too.

Upvotes: 2

Fiston Emmanuel
Fiston Emmanuel

Reputation: 4859

Let's break down the problem as below:

1. Save geohash for each client document

Assume that we have clients collection with documents as below:

{
"hy_uyttdhh":{
firstName:"Satya",
lastName:"Nadella",
geohash:"gbsuv7zt",
coordinate:{
lat:48.669,
lng:-4.32913}
}},
"90kuxddgty":{
firstName:"Sundar",
lastName:"Pichai",
geohash:"gbsuv7z",
coordinate:{
lat:47.669,
lng:-4.70913}
}
},
"iutybdfertyu":{
firstName:"Parag",
lastName:"Agrawal",
geohash:"gbsuv7zs",
coordinate:{
lat:47.669,
lng:-4.70913}
}
}
}

2. Get only clients location nearby user

There are 2 factors to determine if the client is a nearby user:

  • User current location coordinate

  • Boundary radius in mile or km where we expect client is close by

With that in mind, let's write function to only match and return client in the specified boundary.

// Utility library by Firebase team to work with Geolocation
const geofire = require("geofire-common");

const getNearbyClients = async () => {
  const boundaryRadius = 1; // km
  // Reference to clients collection - Syntax should change if you're using Firebase  V9
  const clientsRef = firebase.firestore.collection("clients");
  // Current user location - mocked for sake of simplicity
  const coordinates = { latitude: "48.669", longitude: "-4.32913" };

  const center = [coordinates.latitude, coordinates.longitude];
  const radiusInM = radius * 1000;

  // Generate geohash boundaries based on center radius, In our context, this will based on current users coordinates
  const bounds = geofire.geohashQueryBounds(center, radiusInM);
  // 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 the overlap, but in most cases, there are 4.

  const matchedBoundariesPromises = bounds.map((b) => {
    let query = clientsRef.orderBy("geohash").startAt(b[0]).endAt(b[1]);
    query = query.get();
    return query;
  });

  // Collect all the query results together into a single list
  const snapshots = await Promise.all(promises);
  let matchingDocs = [];
  snapshots.forEach((snap) => {
    snap.docs.forEach((doc) => {
      if (doc.data()) {
        matchingDocs.push(doc.data());
      }
    });
  });
  return matchingDocs;
};

3. Sort matched clients by closest to user

With nearby clients from firestore, we can sort by nearest with this function

const sortClientsByNearest = (clients = [])=>{

// Current user location - mocked for sake of simplicity
 const coordinates = { latitude: "48.669", longitude: "-4.32913" };

 const center = [coordinates.latitude, coordinates.longitude];
 
const  distanceInKm  =  geofire.distanceBetween([lat,  lng],  center);

return  [...clients].sort(function  (a,  b)  {
const distanceFromA =  geofire.distanceBetween([a.coordinate.lat,  a.coordinate.lng],  center)  
const distanceFromB =  geofire.distanceBetween([b.coordinate.lat,  b.coordinate.lng],  center

return  distanceFromA - distanceFromB

});

}

Upvotes: 1

Related Questions