Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Firebase Cloud Firestore #163

Closed
vascoV opened this issue Nov 12, 2017 · 5 comments
Closed

Firebase Cloud Firestore #163

vascoV opened this issue Nov 12, 2017 · 5 comments

Comments

@vascoV
Copy link

vascoV commented Nov 12, 2017

Geofire is an amazing library that can be used with the FIrebase Real-time Database,
is gonna be updated for the Cloud Firestore?

@sonaye
Copy link

sonaye commented Mar 2, 2018

You can utilize geohashes.

import g from 'ngeohash';

const store = firestore();

const addLocation = (lat, lng) =>
  store
    .collection('locations')
    .add({
      g10: g.encode_int(lat, lng, 24), // ~10 km radius
      g5: g.encode_int(lat, lng, 26), // ~5 km radius
      g1: g.encode_int(lat, lng, 30) // ~1 km radius
    });

const nearbyLocationsRef = (lat, lng, d = 1) => {
  const bits =  d === 10 ? 24 : d === 5 ? 26 : 30;

  const h = g.encode_int(lat, lng, bits);

  return store
    .collection('locations')
    .where(`g${d}`, '>=', g.neighbor_int(h, [-1, -1], bits))
    .where(`g${d}`, '<=', g.neighbor_int(h, [1, 1], bits));
};

Edit: Added some explanation.

We convert from (lat, lng) to an integer geohash that has a max. resolution of 52 bits in JS.

// Empire State Building, New York
const lat = 40.748676;
const lng = -73.985654;

// geohash with a 10 km radius resolution
g.encode_int(lat, lng, 24); // 6671229

// geohash with a 5 km radius resolution
g.encode_int(lat, lng, 26); // 26684916

// geohash with a 1 km radius resolution
g.encode_int(lat, lng, 30); // 426958662

// geohash at full resolution (most accurate)
g.encode_int(lat, lng, 52); // 1790794426114269

In order to do a proximity search, one could compute the southwest corner (low geohash with low latitude and longitude) and northeast corner (high geohash with high latitude and longitude) of a bounding box and search for geohashes between those two.

g.neighbors_int(26684916, 26); // 5 km

// [ 26684917,
//   26684919,
//   26684918,
//   26684915,
//   26684913,
//   26684891,
//   26684894,
//   26684895 ]

This translates to something like:

All locations in this 3x3 grid will have a geohash prefix that is in the range of [neighborsMin, neighborsMax] geohashes, or to be exact [southWestNeighbor, northEastNeighbor] geohashes. We can now do a simple range look up using where().

This can be done with the realtime database as well.

db
  .ref('locations')
  .orderByChild('g5')
  .startAt(g.neighbor_int(h, [-1, -1], 26))
  .endAt(g.neighbor_int(h, [1, 1], 26));

More info on how it's done + a ref. implementation (but for strings, for Redis) can be found here.

Bits to radius table

screen shot 2018-03-03 at 12 23 48 pm

Source.

For more accurate results (as described here) you will need 9 listeners for each piece of the 3x3 grid that check for strict equality.

const nearbyLocationsRefs = (lat, lng, d = 1) => {
 const bits = d === 10 ? 24 : d === 5 ? 26 : 30;

 const h = g.encode_int(lat, lng, bits);

 const n = [h, ...g.neighbors_int(h, bits)];

 const refs = [];

 n.forEach(
   (hash, i) =>
     (refs[i] = store.collection('locations').where(`g${d}`, '==', hash))
 );

 return refs;
};

Edit: Added a note about possible duplicate results.

In your .onSnapshot(handleSnapshot) handler make sure that you merge unique docs.

let locations = [];

handleSnapshot = snapshot => {
  const docs = [];

  snapshot.forEach(doc => {
    const data = doc.data();
    data.id = doc.id;
    docs.push(data);
  });

  locations = _.uniqBy(locations.concat(docs), 'id'); // lodash
};

@MichaelSolati
Copy link
Contributor

To those who care, I had a PR for Firestore support but after a little conversation it was deemed not to be the appropriate time to bring it into the official library. However for any of you that are interested I have my geofirestore library code available here, and on npm npm i geofirestore. It works very effectively the same as the official geofire library (same class names/functions/etc...)

@sonaye
Copy link

sonaye commented Apr 20, 2018

Apparently we can execute a very basic text search operation in Firebase with the help of \uf8ff. We can drop the nine listeners.

db
  .collection('locations')
  .add({ g: g.encode(lat, lng) });

// ..

locations = [];

const h = g
  .encode(lat, lng)
  .substring(0, 5);
  // why 5? http://www.elastic.co/guide/en/elasticsearch/guide/current/geohashes.html

db
  .collection('locations')
  .orderBy('g')
  .startAt(h)
  .endAt(`${h}\uf8ff`)
  .onSnapshot(handleSnapshot);

handleSnapshot = snapshot => {
  const docs = [];
  
  snapshot.forEach(doc => {
    const data = doc.data();
    data.id = doc.id;
    docs.push(data);
  });

  locations = uniqBy(locations.concat(docs), 'id');
};

@zoro238
Copy link

zoro238 commented Feb 18, 2019

pls for android java

@puf
Copy link
Contributor

puf commented Feb 17, 2021

We split the GeoFire libraries into two:

  1. A core GeoFireUtils library with just the bits having to do with geohashes and queries
  2. The complete library that implements GeoFire demo has very high CPU usage #1 on top of Realtime Database

For documentation on how to use #1 with Cloud Firestore, see the documentation here: https://firebase.google.com/docs/firestore/solutions/geoqueries

@puf puf closed this as completed Feb 17, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants