Skip to content

Commit b8b77c2

Browse files
committed
update: typings, general fixes
1 parent 0ea20f9 commit b8b77c2

File tree

3 files changed

+76
-168
lines changed

3 files changed

+76
-168
lines changed

README.md

+75-28
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,29 @@
1-
# GeoFireX
2-
3-
Realtime Geolocation with Firestore & RxJS
4-
5-
[Live Demo](https://geo-test-c92e4.firebaseapp.com)
6-
71
<p align="center">
82

93
<a href="https://slackin-pbfjhfxnsa.now.sh"><img src="https://slackin-pbfjhfxnsa.now.sh/badge.svg"></a>
104

11-
<!-- <a href="https://circleci.com/gh/codediodeio/angular-firestarter"><img src="https://circleci.com/gh/codediodeio/angular-firestarter.svg?style=svg"></a> -->
5+
<a href="https://circleci.com/gh/codediodeio/geofirex"><img src="https://circleci.com/gh/codediodeio/geofirex.svg?style=svg"></a>
126

137
</p>
148

9+
# GeoFireX
10+
11+
Realtime Geolocation with Firestore & RxJS
12+
13+
:point_right: [Live Demo](https://geo-test-c92e4.firebaseapp.com)
14+
1515
## :checkered_flag: QuickStart
1616

1717
```shell
18-
npm install firebase geofirex
18+
npm install geofirex
19+
20+
# peer dependencies
21+
npm install rxjs firebase
1922
```
2023

21-
#### Initialize
24+
### Initialize
2225

23-
The library is a lightweight client for the Firebase SDK that provides tools for handling geolocation data in Firestore.
26+
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.
2427

2528
```ts
2629
// Init Firebase
@@ -32,9 +35,9 @@ import * as geofirex from 'geofirex';
3235
const geo = geofirex.init(firebase);
3336
```
3437

35-
#### Write Geo Data
38+
### Write Geo Data
3639

37-
First, you'll need to add some geolocation data in your database. A `collection` creates a reference to Firestore (just like the SDK), but with some extra geoquery features. The `point` method returns a class that helps you create geolocation data.
40+
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.
3841

3942
```ts
4043
const cities = geo.collection('cities');
@@ -44,9 +47,13 @@ const point = geo.point(40, -119);
4447
cities.add({ name: 'Phoenix', position: point.data });
4548
```
4649

47-
#### Query Geo Data
50+
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.
51+
52+
![](https://firebasestorage.googleapis.com/v0/b/geo-test-c92e4.appspot.com/o/point1.png?alt=media&token=0c833700-3dbd-476a-99a9-41c1143dbe97)
4853

49-
Now let's make a query Firestore for _cities.position within 100km radius of a centerpoint_.
54+
### Query Geo Data
55+
56+
Now let's query Firestore for _cities.position within 100km radius of a centerpoint_.
5057

5158
```ts
5259
const center = geo.point(40.1, -119.1);
@@ -56,13 +63,17 @@ const field = 'position';
5663
const query = cities.within(center, radius, field);
5764
```
5865

59-
The query returns a realtime Observable of the document data + some additional metadata.
66+
The query returns a realtime Observable of the document data, plus some useful metadata like _distance_ and _bearing_ from the query centerpoint.
6067

6168
```ts
6269
query.subscribe(console.log);
6370
// [{ ...documentData, queryMetadata: { distance: 1.23232, bearing: 230.23 } }]
6471
```
6572

73+
You now have a realtime stream of data to visualize on a map.
74+
75+
![](https://firebasestorage.googleapis.com/v0/b/geo-test-c92e4.appspot.com/o/geoquery-fire2.gif?alt=media&token=487abd17-90a3-4589-a82d-81d172ddeb25)
76+
6677
## :notebook: API
6778

6879
### `collection(path: string, query? QueryFn)`
@@ -71,8 +82,8 @@ Creates reference to a Firestore collection that can be used to make geo-queries
7182

7283
Example:
7384

74-
```
75-
const collection = geo.collection('cities', ref => ref.where('zip', '==', 90201) )
85+
```ts
86+
const collection = geo.collection('cities');
7687
```
7788

7889
#### Performing Geo-Queries
@@ -122,8 +133,55 @@ Example: `const point = geo.point(38, -119)`
122133
- `point.distance(latitude, longitude)` Haversine distance to a point
123134
- `point.bearing(latitude, longitude)` Haversine bearing to a point
124135

136+
## :pizza: Additional Features
137+
138+
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.
139+
140+
### `toGeoJSON` Operator
141+
142+
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.
143+
144+
```ts
145+
const query = geo.collection('cars').within(...)
146+
147+
query.pipe( getGeoJSON() )
148+
149+
// Emits a single object typed as a FeatureCollection<Geometry>
150+
{
151+
"type": "FeatureCollection",
152+
"features": [...]
153+
}
154+
```
155+
156+
#### Promises with `get`
157+
158+
Don't need a realtime stream? Convert any query observable to a promise by wrapping it with `get`.
159+
160+
```ts
161+
import { get } from 'geofirex';
162+
163+
async function getCars {
164+
const query = geo.collection('cars').within(...)
165+
const cars = await get(query)
166+
}
167+
```
168+
125169
## :zap: Tips
126170

171+
### Scale to Massive Collections
172+
173+
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.
174+
175+
Example:
176+
177+
```ts
178+
const users = geo.collection('users', ref =>
179+
ref.where('status', '==', 'online').limit(1000)
180+
);
181+
182+
const nearbyOnlineUsers = users.within(center, radius, field);
183+
```
184+
127185
### Seeing this error: `DocumentReference.set() called with invalid data`
128186

129187
Firestore writes cannot use custom classes, so make sure to call the `data` getter on the point.
@@ -153,17 +211,6 @@ const points = this.radius.pipe(
153211
radius.next(23);
154212
```
155213

156-
### Don't need a realtime stream? Use a Promise with async/await
157-
158-
```ts
159-
import { get } from 'geofirex';
160-
161-
async function getCars {
162-
const query = geo.collection('cars').within(...)
163-
const cars = await get(query)
164-
}
165-
```
166-
167214
### Always Order by `[Latitude, Longitude]`
168215

169216
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.

rollup.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const globals = {
1010
rxjs: 'rxjs',
1111
'rxjs/operators': 'rxjs.operators',
1212
}
13-
const external = ['firebase/app', 'rxjs']
13+
const external = ['firebase/app', 'rxjs', 'rxjs/operators']
1414

1515
export default {
1616
input: './src/index.ts',

src/util.ts

-139
Original file line numberDiff line numberDiff line change
@@ -384,142 +384,3 @@ export const neighbors = function(hash_string) {
384384

385385
return neighborHashList;
386386
};
387-
388-
/**
389-
* Neighbors Integer
390-
*
391-
* Returns all neighbors' hash integers clockwise from north around to northwest
392-
* 7 0 1
393-
* 6 x 2
394-
* 5 4 3
395-
* @param {Number} hash_int
396-
* @param {Number} bitDepth
397-
* @returns {encode_int'd neighborHashIntList|Array}
398-
*/
399-
export const neighbors_int = function(hash_int, bitDepth) {
400-
bitDepth = bitDepth || 52;
401-
402-
var lonlat = decode_int(hash_int, bitDepth);
403-
var lat = lonlat.latitude;
404-
var lon = lonlat.longitude;
405-
var latErr = lonlat.error.latitude * 2;
406-
var lonErr = lonlat.error.longitude * 2;
407-
408-
var neighbor_lat, neighbor_lon;
409-
410-
var neighborHashIntList = [
411-
encodeNeighbor_int(1, 0),
412-
encodeNeighbor_int(1, 1),
413-
encodeNeighbor_int(0, 1),
414-
encodeNeighbor_int(-1, 1),
415-
encodeNeighbor_int(-1, 0),
416-
encodeNeighbor_int(-1, -1),
417-
encodeNeighbor_int(0, -1),
418-
encodeNeighbor_int(1, -1)
419-
];
420-
421-
function encodeNeighbor_int(neighborLatDir, neighborLonDir) {
422-
neighbor_lat = lat + neighborLatDir * latErr;
423-
neighbor_lon = lon + neighborLonDir * lonErr;
424-
return encode_int(neighbor_lat, neighbor_lon, bitDepth);
425-
}
426-
427-
return neighborHashIntList;
428-
};
429-
430-
/**
431-
* Bounding Boxes
432-
*
433-
* Return all the hashString between minLat, minLon, maxLat, maxLon in numberOfChars
434-
* @param {Number} minLat
435-
* @param {Number} minLon
436-
* @param {Number} maxLat
437-
* @param {Number} maxLon
438-
* @param {Number} numberOfChars
439-
* @returns {bboxes.hashList|Array}
440-
*/
441-
export const bboxes = function(minLat, minLon, maxLat, maxLon, numberOfChars) {
442-
numberOfChars = numberOfChars || 9;
443-
444-
var hashSouthWest = encode(minLat, minLon, numberOfChars);
445-
var hashNorthEast = encode(maxLat, maxLon, numberOfChars);
446-
447-
var latLon = decode(hashSouthWest);
448-
449-
var perLat = latLon.error.latitude * 2;
450-
var perLon = latLon.error.longitude * 2;
451-
452-
var boxSouthWest = decode_bbox(hashSouthWest);
453-
var boxNorthEast = decode_bbox(hashNorthEast);
454-
455-
var latStep = Math.round((boxNorthEast[0] - boxSouthWest[0]) / perLat);
456-
var lonStep = Math.round((boxNorthEast[1] - boxSouthWest[1]) / perLon);
457-
458-
var hashList = [];
459-
460-
for (var lat = 0; lat <= latStep; lat++) {
461-
for (var lon = 0; lon <= lonStep; lon++) {
462-
hashList.push(neighbor(hashSouthWest, [lat, lon]));
463-
}
464-
}
465-
466-
return hashList;
467-
};
468-
469-
/**
470-
* Bounding Boxes Integer
471-
*
472-
* Return all the hash integers between minLat, minLon, maxLat, maxLon in bitDepth
473-
* @param {Number} minLat
474-
* @param {Number} minLon
475-
* @param {Number} maxLat
476-
* @param {Number} maxLon
477-
* @param {Number} bitDepth
478-
* @returns {bboxes_int.hashList|Array}
479-
*/
480-
export const bboxes_int = function(minLat, minLon, maxLat, maxLon, bitDepth) {
481-
bitDepth = bitDepth || 52;
482-
483-
var hashSouthWest = encode_int(minLat, minLon, bitDepth);
484-
var hashNorthEast = encode_int(maxLat, maxLon, bitDepth);
485-
486-
var latlon = decode_int(hashSouthWest, bitDepth);
487-
488-
var perLat = latlon.error.latitude * 2;
489-
var perLon = latlon.error.longitude * 2;
490-
491-
var boxSouthWest = decode_bbox_int(hashSouthWest, bitDepth);
492-
var boxNorthEast = decode_bbox_int(hashNorthEast, bitDepth);
493-
494-
var latStep = Math.round((boxNorthEast[0] - boxSouthWest[0]) / perLat);
495-
var lonStep = Math.round((boxNorthEast[1] - boxSouthWest[1]) / perLon);
496-
497-
var hashList = [];
498-
499-
for (var lat = 0; lat <= latStep; lat++) {
500-
for (var lon = 0; lon <= lonStep; lon++) {
501-
hashList.push(neighbor_int(hashSouthWest, [lat, lon], bitDepth));
502-
}
503-
}
504-
505-
return hashList;
506-
};
507-
508-
// var geohash = {
509-
// 'ENCODE_AUTO': ENCODE_AUTO,
510-
// 'encode': encode,
511-
// 'encode_uint64': encode_int, // keeping for backwards compatibility, will deprecate
512-
// 'encode_int': encode_int,
513-
// 'decode': decode,
514-
// 'decode_int': decode_int,
515-
// 'decode_uint64': decode_int, // keeping for backwards compatibility, will deprecate
516-
// 'decode_bbox': decode_bbox,
517-
// 'decode_bbox_uint64': decode_bbox_int, // keeping for backwards compatibility, will deprecate
518-
// 'decode_bbox_int': decode_bbox_int,
519-
// 'neighbor': neighbor,
520-
// 'neighbor_int': neighbor_int,
521-
// 'neighbors': neighbors,
522-
// 'neighbors_int': neighbors_int,
523-
// 'bboxes': bboxes,
524-
// 'bboxes_int': bboxes_int
525-
// };

0 commit comments

Comments
 (0)