Skip to content

Commit 41a224b

Browse files
authored
Merge pull request #78 from codediodeio/node-support
Node support
2 parents a1d2859 + e7ae646 commit 41a224b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+21301
-20123
lines changed

README.md

Lines changed: 82 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -8,53 +8,67 @@
88

99
# GeoFireX
1010

11-
Realtime Geolocation with Firestore & RxJS
11+
Realtime Geolocation with Firestore & RxJS. Query geographic points within a radius on the web or Node.js.
1212

13-
:point_right: [Live Demo](https://geo-test-c92e4.firebaseapp.com)
14-
:tv: [Video Tutorial](https://angularfirebase.com/lessons/geolocation-query-in-firestore-realtime/)
13+
- :point_right: [Live Demo](https://geo-test-c92e4.firebaseapp.com)
14+
- :tv: [Video Tutorial](https://angularfirebase.com/lessons/geolocation-query-in-firestore-realtime/)
1515

16-
## :checkered_flag: QuickStart
16+
## :zap: QuickStart
1717

1818
```shell
19-
npm install geofirex
20-
21-
# peer dependencies
22-
npm install rxjs firebase
19+
npm install geofirex rxjs firebase
2320
```
2421

2522
### Initialize
2623

27-
The library is a lightweight client for the Firebase Web SDK that provides tools for wrangling geolocation data in Firestore. You need a [Firebase project](https://firebase.google.com/docs/storage/web/start) to get started.
24+
The library is a lightweight extension for the Firebase Web and Admin JavaScript SDKs to provide tools for wrangling geolocation data in Firestore.
2825

29-
```ts
26+
Web:
27+
28+
```js
3029
// Init Firebase
31-
import * as firebase from 'firebase/app';
30+
import firebase from 'firebase/app';
3231
firebase.initializeApp(yourConfig);
3332

3433
// Init GeoFireX
35-
import * as geofirex from 'geofirex';
34+
import geofirex from 'geofirex';
3635
const geo = geofirex.init(firebase);
3736
```
3837

39-
### Write Geo Data
38+
Node.js with the Firebase Admin SDK:
4039

41-
Next, add some geolocation data in your database. A `collection` creates a reference to Firestore (just like the SDK), but with some extra geolocation tools. The `point` method returns a class that helps you create geolocation data.
40+
```js
41+
const admin = require('firebase-admin');
42+
admin.initializeApp();
43+
44+
const geo = require('geofirex').init(admin);
45+
```
46+
47+
With Typescript:
4248

4349
```ts
44-
const cities = geo.collection('cities');
50+
import * as geofirex from 'geofirex';
51+
const geo = geofirex.init(firebase);
52+
```
53+
54+
### Write Geolocation Data
4555

46-
const point = geo.point(40, -119);
56+
Next, add some geolocation data in your database using the main Firebase SDK. You can add multiple points to a single doc. Calling `geo.point(lat, lng)` creates an object with a [geohash string](https://www.movable-type.co.uk/scripts/geohash.html) and a [Firestore GeoPoint](https://firebase.google.com/docs/reference/android/com/google/firebase/firestore/GeoPoint). Data must be saved in this format to be queried.
4757

48-
cities.add({ name: 'Phoenix', position: point.data });
58+
```ts
59+
const cities = firestore().collection('cities');
60+
61+
const position = geo.point(40, -119);
62+
63+
cities.add({ name: 'Phoenix', position });
4964
```
5065

51-
Calling `point.data` returns an object that contains a [geohash string](https://www.movable-type.co.uk/scripts/geohash.html) and a [Firestore GeoPoint](https://firebase.google.com/docs/reference/android/com/google/firebase/firestore/GeoPoint). It should look like this in your database. You can name the object whatever you want and even save multiple points on a single document.
5266

5367
![](https://firebasestorage.googleapis.com/v0/b/geo-test-c92e4.appspot.com/o/point1.png?alt=media&token=0c833700-3dbd-476a-99a9-41c1143dbe97)
5468

5569
### Query Geo Data
5670

57-
Now let's query Firestore for _cities.position within 100km radius of a centerpoint_.
71+
Query Firestore for _cities.position within 100km radius of a centerpoint_.
5872

5973
```ts
6074
const center = geo.point(40.1, -119.1);
@@ -64,11 +78,11 @@ const field = 'position';
6478
const query = cities.within(center, radius, field);
6579
```
6680

67-
The query returns a realtime Observable of the document data, plus some useful metadata like _distance_ and _bearing_ from the query centerpoint.
81+
Each hit returns a realtime Observable of the document data, plus some useful `hitMetadata` like _distance_ and _bearing_ from the query centerpoint.
6882

6983
```ts
7084
query.subscribe(console.log);
71-
// [{ ...documentData, queryMetadata: { distance: 1.23232, bearing: 230.23 } }]
85+
// [{ ...documentData, hitMetadata: { distance: 1.23232, bearing: 230.23 } }]
7286
```
7387

7488
You now have a realtime stream of data to visualize on a map.
@@ -77,73 +91,74 @@ You now have a realtime stream of data to visualize on a map.
7791

7892
## :notebook: API
7993

80-
### `collection(path: string, query? QueryFn)`
94+
### `query(ref: CollectionReference | Query | string)`
8195

82-
Creates reference to a Firestore collection that can be used to make geo-queries and perform writes If you pass an optional Firestore query function, all subsequent geo-queries will be limited to this subset of documents
96+
Creates reference to a Firestore collection or query that can be used to make geo-queries.
8397

8498
Example:
8599

86100
```ts
87-
const collection = geo.collection('cities');
101+
const geoQuery = geo.query('cities');
102+
103+
// OR make a geoquery on top of a firestore query
104+
105+
const query = firestore().collection('cities').where('name', '==', 'Phoenix');
106+
const geoQuery = geo.query(query);
88107
```
89108

90-
#### Performing Geo-Queries
109+
#### Perform Geo-Queries
91110

92-
`collection.within(center: GeoFirePoint, radius: number, field: string)`
111+
```js
112+
geoQuery.within(center: FirePoint, radius: number, field: string)
113+
.subscribe((hits) => console.log((hits)))
114+
```
93115

94116
Query the parent Firestore collection by geographic distance. It will return documents that exist within X kilometers of the centerpoint.
95117

96-
Each doc also contains returns _distance_ and _bearing_ calculated on the query on the `queryMetadata` property.
118+
Each doc also contains returns _distance_ and _bearing_ calculated on the query on the `hitMetadata` property.
97119

98-
**Returns:** `Observable<object[]>`
120+
**Returns:** `Observable<T[]>`
99121

100-
#### Write Data
101122

102-
Write data just like you would in Firestore
123+
### `point(latitude: number, longitude: number): FirePoint`
103124

104-
`collection.add(data)`
125+
Returns an object with the required geohash format to save to Firestore.
105126

106-
Or use one of the client's conveniece methods
107-
108-
- `collection.setDoc(id, data)` - Set a document in the collection with an ID.
109-
- `collection.setPoint(id, field, lat, lng)`- Add a geohash to an existing doc
127+
Example: `const point = geo.point(38, -119)`
110128

111-
#### Read Data
129+
A point is a plain JS object with two properties.
112130

113-
In addition to Geo-Queries, you can also read the collection like you would normally in Firestore, but as an Observable
131+
- `point.geohash` Returns a geohash string at precision 9
132+
- `point.geopoint` Returns a Firestore GeoPoint
114133

115-
- `collection.data()`- Observable of document data
116-
- `collection.snapshot()`- Observable of Firestore QuerySnapshot
117134

118-
### `point(latitude: number, longitude: number)`
135+
## Additional Features
119136

120-
Returns a GeoFirePoint allowing you to create geohashes, format data, and calculate relative distance/bearing.
137+
The goal of this package is to facilitate rapid feature development with tools like MapBox, Google Maps, and D3.js. If you have an idea for a useful feature, open an issue.
121138

122-
Example: `const point = geo.point(38, -119)`
139+
### Logging
123140

124-
#### Getters
141+
Each query runs on a set of geohash squares, so you may read more documents than actually exist inside the radius. Use the `log` option to examine the total query size and latency.
125142

126-
- `point.hash` Returns a geohash string at precision 9
127-
- `point.geoPoint` Returns a Firestore GeoPoint
128-
- `point.geoJSON` Returns data as a GeoJSON `Feature<Point>`
129-
- `point.coords` Returns coordinates as `[latitude, longitude]`
130-
- `point.data` Returns data object suitable for saving to the Firestore database
143+
```js
144+
ref.within(center, radius, field, { log: true })
145+
```
131146

132-
#### Geo Calculations
147+
![Logging GeoQueries](https://firebasestorage.googleapis.com/v0/b/geo-test-c92e4.appspot.com/o/geofirex-logging.PNG?alt=media&token=9b8b487d-18b2-4e5f-bb04-564fa6f2996d)
133148

134-
- `point.distance(latitude, longitude)` Haversine distance to a point
135-
- `point.bearing(latitude, longitude)` Haversine bearing to a point
149+
### Geo Calculations
136150

137-
## :pizza: Additional Features
151+
Convenience methods for calculating distance and bearing.
138152

139-
The goal of this package is to facilitate rapid feature development with tools like MapBox, Google Maps, and D3.js. If you have an idea for a useful feature, open an issue.
153+
- `geo.distance(geo.point(38, -118), geo.point(40, -115))` Haversine distance
154+
- `geo.bearing(to, from)` Haversine bearing
140155

141156
### `toGeoJSON` Operator
142157

143158
A custom RxJS operator that transforms a collection into a [GeoJSON FeatureCollection](https://macwright.org/2015/03/23/geojson-second-bite.html#featurecollection). Very useful for tools like [MapBox](https://blog.mapbox.com/real-time-maps-for-live-events-fad0b334e4e) that can use GeoJSON to update a realtime data source.
144159

145160
```ts
146-
const query = geo.collection('cars').within(...)
161+
const query = geo.query('cars').within(...)
147162

148163
query.pipe( toGeoJSON() )
149164

@@ -162,29 +177,32 @@ Don't need a realtime stream? Convert any query observable to a promise by wrapp
162177
import { get } from 'geofirex';
163178

164179
async function getCars {
165-
const query = geo.collection('cars').within(...)
180+
const query = geo.query('cars').within(...)
166181
const cars = await get(query)
167182
}
168183
```
169184

170-
## :zap: Tips
185+
## Tips
171186

172-
### Scale to Massive Collections
187+
### Compound Queries
173188

174-
It's possibe to build Firestore collections with billions of documents. One of the main motivations of this project was to make geoqueries possible on a queried subset of data. You can make a regular Firestore query on collection by passing a callback as the second argument, then all geoqueries will scoped these contstraints.
189+
The only well-supported type of compound query is `where`. A geoquery combines multiple smaller queries into a unified radius, so `limit` and pagination operators will not provide predictable results - a better approach is to search a smaller radius and do your sorting client-side.
175190

176-
Note: This query requires a composite index, which you will be prompted to create with an error from Firestore on the first request.
177191

178192
Example:
179193

180194
```ts
181-
const users = geo.collection('users', ref =>
182-
ref.where('status', '==', 'online')
183-
);
195+
// Make a query like you normally would
196+
const query = firestore().collection('users').where('status', '==', 'online');
197+
198+
const users = geo.query(query)
184199

185200
const nearbyOnlineUsers = users.within(center, radius, field);
186201
```
187202

203+
Note: This query requires a composite index, which you will be prompted to create with an error from Firestore on the first request.
204+
205+
188206
### Usage with RxJS < 6.2, or Ionic v3
189207

190208
This package requires RxJS 6.2, but you can still use it with older versions without blowing up your app by installing rxjs-compat.
@@ -195,24 +213,11 @@ Example:
195213
npm i rxjs@latest rxjs-compat
196214
```
197215

198-
### Seeing this error: `DocumentReference.set() called with invalid data`
199-
200-
Firestore writes cannot use custom classes, so make sure to call the `data` getter on the point.
201-
202-
```ts
203-
const point = geo.point(40, -50);
204-
// This is an ERROR
205-
ref.add({ location: point });
206-
207-
// This is GOOD
208-
ref.add({ location: point.data });
209-
```
210-
211216
### Make Dynamic Queries the RxJS Way
212217

213218
```ts
214219
const radius = new BehaviorSubject(1);
215-
const cities = geo.collection('cities');
220+
const cities = geo.query('cities');
216221

217222
const points = this.radius.pipe(
218223
switchMap(rad => {
@@ -226,4 +231,4 @@ radius.next(23);
226231

227232
### Always Order by `[Latitude, Longitude]`
228233

229-
The GeoJSON spec formats coords as `[Longitude, Latitude]` to represent an X/Y plane. However, the Firebase GeoPoint uses `[Latitude, Longitude]`. For consistency, this libary will always require you to use the latter format.
234+
The GeoJSON spec formats coords as `[Longitude, Latitude]` to represent an X/Y plane. However, the Firebase GeoPoint uses `[Latitude, Longitude]`. For consistency, this library always requires to use the latter Firebase-style format.

integration/.editorconfig renamed to example/.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Editor configuration, see http://editorconfig.org
1+
# Editor configuration, see https://editorconfig.org
22
root = true
33

44
[*]
File renamed without changes.

integration/.gitignore renamed to example/.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@
44
/dist
55
/tmp
66
/out-tsc
7+
# Only exists if Bazel was run
8+
/bazel-out
79

810
# dependencies
911
/node_modules
1012

13+
# profiling files
14+
chrome-profiler-events*.json
15+
speed-measure-plugin*.json
16+
1117
# IDEs and editors
1218
/.idea
1319
.project
@@ -23,6 +29,7 @@
2329
!.vscode/tasks.json
2430
!.vscode/launch.json
2531
!.vscode/extensions.json
32+
.history/*
2633

2734
# misc
2835
/.sass-cache

integration/README.md renamed to example/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# Integration
1+
# Example
22

3-
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.8.
3+
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.3.17.
44

55
## Development server
66

0 commit comments

Comments
 (0)