Skip to content

Commit 1bb200e

Browse files
authored
Add snippets for Firestore geoqueries (firebase#52)
1 parent 52b0c20 commit 1bb200e

File tree

6 files changed

+274
-2
lines changed

6 files changed

+274
-2
lines changed

firestore-next/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
},
77
"license": "Apache-2.0",
88
"dependencies": {
9-
"firebase": "exp"
9+
"firebase": "exp",
10+
"geofire-common": "^5.1.0"
1011
},
1112
"devDependencies": {
1213
"@types/chai": "^4.2.12",
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// [SNIPPETS_SEPARATION enabled]
2+
const { FirebaseFirestore } = require('firebase/firestore');
3+
4+
const geofire = require('geofire-common');
5+
6+
/** @type {FirebaseFirestore} */
7+
let db;
8+
9+
async function addHash(done) {
10+
// [START fs_geo_add_hash]
11+
const { doc, updateDoc } = require('firebase/firestore');
12+
13+
// Compute the GeoHash for a lat/lng point
14+
const lat = 51.5074;
15+
const lng = 0.1278;
16+
const hash = geofire.geohashForLocation([lat, lng]);
17+
18+
// Add the hash and the lat/lng to the document. We will use the hash
19+
// for queries and the lat/lng for distance comparisons.
20+
const londonRef = doc(db, 'cities', 'LON');
21+
await updateDoc(londonRef, {
22+
geohash: hash,
23+
lat: lat,
24+
lng: lng
25+
});
26+
// [END fs_geo_add_hash]
27+
done();
28+
}
29+
30+
async function queryHashes(done) {
31+
// [START fs_geo_query_hashes]
32+
const { collection, query, orderBy, startAt, endAt, getDocs } = require('firebase/firestore');
33+
34+
// Find cities within 50km of London
35+
const center = [51.5074, 0.1278];
36+
const radiusInKm = 50;
37+
38+
// Each item in 'bounds' represents a startAt/endAt pair. We have to issue
39+
// a separate query for each pair. There can be up to 9 pais of bounds
40+
// depending on overlap, but in most cases there are 4.
41+
const bounds = geofire.geohashQueryBounds(center, radiusInKm);
42+
const promises = [];
43+
for (const b of bounds) {
44+
const q = query(
45+
collection(db, 'cities'),
46+
orderBy('geohash'),
47+
startAt(b[0]),
48+
endAt(b[1]));
49+
50+
promises.push(getDocs(q));
51+
}
52+
53+
// Collect all the query results together into a single list
54+
const snapshots = await Promise.all(promises);
55+
56+
const matchingDocs = [];
57+
for (const snap of snapshots) {
58+
for (const doc of snap.docs) {
59+
const lat = doc.get('lat');
60+
const lng = doc.get('lng');
61+
62+
// We have to filter out a few false positives due to GeoHash
63+
// accuracy, but most will match
64+
const distance = geofire.distanceBetween([lat, lng], center);
65+
if (distance <= radiusInKm) {
66+
matchingDocs.push(doc);
67+
}
68+
}
69+
}
70+
// [END fs_geo_query_hashes]
71+
done(matchingDocs);
72+
}
73+
74+
describe("firestore-solution-geoqueries", () => {
75+
before(() => {
76+
const { initializeApp } = require("firebase/app");
77+
const { getFirestore} = require("firebase/firestore");
78+
79+
const config = {
80+
apiKey: "AIzaSyArvVh6VSdXicubcvIyuB-GZs8ua0m0DTI",
81+
authDomain: "firestorequickstarts.firebaseapp.com",
82+
projectId: "firestorequickstarts",
83+
};
84+
const app = initializeApp(config, "solution-geoqueries");
85+
db = getFirestore(app);
86+
});
87+
88+
describe("solution-geoqueries", () => {
89+
it("should add a hash to a doc", (done) => {
90+
addHash(done)
91+
});
92+
93+
it("should query hashes", (done) => {
94+
queryHashes(done);
95+
});
96+
});
97+
});
98+
99+
100+

firestore/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
},
77
"license": "Apache-2.0",
88
"dependencies": {
9-
"firebase": "^8.0.2"
9+
"firebase": "^8.0.2",
10+
"geofire-common": "^5.1.0"
1011
},
1112
"devDependencies": {
1213
"@types/chai": "^4.2.12",

firestore/test.solution-geoqueries.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import firebase from 'firebase/app';
2+
import 'firebase/firestore';
3+
4+
const geofire = require('geofire-common');
5+
6+
/**
7+
* @type firebase.firestore.Firestore
8+
*/
9+
var db;
10+
11+
function addHash(done) {
12+
// [START fs_geo_add_hash]
13+
14+
// Compute the GeoHash for a lat/lng point
15+
const lat = 51.5074;
16+
const lng = 0.1278;
17+
const hash = geofire.geohashForLocation([lat, lng]);
18+
19+
// Add the h and the lat/lng to the document. We will use the hash
20+
// for queries and the lat/lng for distance comparisons.
21+
const londonRef = db.collection('cities').doc('LON');
22+
londonRef.update({
23+
geohash: hash,
24+
lat: lat,
25+
lng: lng
26+
}).then(() => {
27+
// [START_EXCLUDE]
28+
done();
29+
// [END_EXCLUDE]
30+
});
31+
// [END fs_geo_add_hash]
32+
}
33+
34+
function queryHashes(done) {
35+
// [START fs_geo_query_hashes]
36+
// Find cities within 50km of London
37+
const center = [51.5074, 0.1278];
38+
const radiusInKm = 50;
39+
40+
// Each item in 'bounds' represents a startAt/endAt pair. We have to issue
41+
// a separate query for each pair. There can be up to 9 pais of bounds
42+
// depending on overlap, but in most cases there are 4.
43+
const bounds = geofire.geohashQueryBounds(center, radiusInKm);
44+
const promises = [];
45+
for (const b of bounds) {
46+
const q = db.collection('cities')
47+
.orderBy('geohash')
48+
.startAt(b[0])
49+
.endAt(b[1]);
50+
51+
promises.push(q.get());
52+
}
53+
54+
// Collect all the query results together into a single list
55+
Promise.all(promises).then((snapshots) => {
56+
const matchingDocs = [];
57+
58+
for (const snap of snapshots) {
59+
for (const doc of snap.docs) {
60+
const lat = doc.get('lat');
61+
const lng = doc.get('lng');
62+
63+
// We have to filter out a few false positives due to GeoHash
64+
// accuracy, but most will match
65+
const distance = geofire.distanceBetween([lat, lng], center);
66+
if (distance <= radiusInKm) {
67+
matchingDocs.push(doc);
68+
}
69+
}
70+
}
71+
72+
return matchingDocs;
73+
}).then((matchingDocs) => {
74+
// Process the matching documents
75+
// [START_EXCLUDE]
76+
done(matchingDocs);
77+
// [END_EXCLUDE]
78+
})
79+
80+
// [END fs_geo_query_hashes]
81+
}
82+
83+
describe("firestore-solution-geoqueries", () => {
84+
before(() => {
85+
var config = {
86+
apiKey: "AIzaSyArvVh6VSdXicubcvIyuB-GZs8ua0m0DTI",
87+
authDomain: "firestorequickstarts.firebaseapp.com",
88+
projectId: "firestorequickstarts",
89+
};
90+
var app = firebase.initializeApp(config, "solution-geoqueries");
91+
db = firebase.firestore(app);
92+
});
93+
94+
describe("solution-geoqueries", () => {
95+
it("should add a hash to a doc", (done) => {
96+
addHash(done)
97+
});
98+
99+
it("should query hashes", (done) => {
100+
queryHashes(done);
101+
});
102+
});
103+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// This snippet file was generated by processing the source file:
2+
// ./firestore-next/test.solution-geoqueries.js
3+
//
4+
// To make edits to the snippets in this file, please edit the source
5+
6+
// [START fs_geo_add_hash_modular]
7+
import { doc, updateDoc } from 'firebase/firestore';
8+
9+
// Compute the GeoHash for a lat/lng point
10+
const lat = 51.5074;
11+
const lng = 0.1278;
12+
const hash = geofire.geohashForLocation([lat, lng]);
13+
14+
// Add the hash and the lat/lng to the document. We will use the hash
15+
// for queries and the lat/lng for distance comparisons.
16+
const londonRef = doc(db, 'cities', 'LON');
17+
await updateDoc(londonRef, {
18+
geohash: hash,
19+
lat: lat,
20+
lng: lng
21+
});
22+
// [END fs_geo_add_hash_modular]
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// This snippet file was generated by processing the source file:
2+
// ./firestore-next/test.solution-geoqueries.js
3+
//
4+
// To make edits to the snippets in this file, please edit the source
5+
6+
// [START fs_geo_query_hashes_modular]
7+
import { collection, query, orderBy, startAt, endAt, getDocs } from 'firebase/firestore';
8+
9+
// Find cities within 50km of London
10+
const center = [51.5074, 0.1278];
11+
const radiusInKm = 50;
12+
13+
// Each item in 'bounds' represents a startAt/endAt pair. We have to issue
14+
// a separate query for each pair. There can be up to 9 pais of bounds
15+
// depending on overlap, but in most cases there are 4.
16+
const bounds = geofire.geohashQueryBounds(center, radiusInKm);
17+
const promises = [];
18+
for (const b of bounds) {
19+
const q = query(
20+
collection(db, 'cities'),
21+
orderBy('geohash'),
22+
startAt(b[0]),
23+
endAt(b[1]));
24+
25+
promises.push(getDocs(q));
26+
}
27+
28+
// Collect all the query results together into a single list
29+
const snapshots = await Promise.all(promises);
30+
31+
const matchingDocs = [];
32+
for (const snap of snapshots) {
33+
for (const doc of snap.docs) {
34+
const lat = doc.get('lat');
35+
const lng = doc.get('lng');
36+
37+
// We have to filter out a few false positives due to GeoHash
38+
// accuracy, but most will match
39+
const distance = geofire.distanceBetween([lat, lng], center);
40+
if (distance <= radiusInKm) {
41+
matchingDocs.push(doc);
42+
}
43+
}
44+
}
45+
// [END fs_geo_query_hashes_modular]

0 commit comments

Comments
 (0)