Closed vascoV closed 3 years ago
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
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
};
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...)
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');
};
pls for android java
We split the GeoFire libraries into two:
For documentation on how to use #1 with Cloud Firestore, see the documentation here: https://firebase.google.com/docs/firestore/solutions/geoqueries
Geofire is an amazing library that can be used with the FIrebase Real-time Database, is gonna be updated for the Cloud Firestore?