Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/codediodeio/geofirex
:globe_with_meridians: :round_pushpin: Geolocation Queries with Firestore & RxJS
https://github.com/codediodeio/geofirex
firebase firestore geojson geolocation rxjs
Last synced: 22 days ago
JSON representation
:globe_with_meridians: :round_pushpin: Geolocation Queries with Firestore & RxJS
- Host: GitHub
- URL: https://github.com/codediodeio/geofirex
- Owner: codediodeio
- Created: 2018-07-08T23:56:50.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2023-01-09T12:00:32.000Z (almost 2 years ago)
- Last Synced: 2024-04-14T10:08:10.054Z (7 months ago)
- Topics: firebase, firestore, geojson, geolocation, rxjs
- Language: TypeScript
- Homepage: https://geo-test-c92e4.firebaseapp.com/
- Size: 2.3 MB
- Stars: 472
- Watchers: 20
- Forks: 107
- Open Issues: 110
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# GeoFireX
Realtime Geolocation with Firestore & RxJS. Query geographic points within a radius on the web or Node.js.
- :point_right: [Live Demo](https://geo-test-c92e4.firebaseapp.com)
- :tv: [Video Tutorial](https://angularfirebase.com/lessons/geolocation-query-in-firestore-realtime/)## :zap: QuickStart
```shell
npm install geofirex rxjs firebase
```### Initialize
The library is a lightweight extension for the Firebase Web and Admin JavaScript SDKs to provide tools for wrangling geolocation data in Firestore.
Web:
```js
// Init Firebase
import firebase from 'firebase/app';
firebase.initializeApp(yourConfig);// Init GeoFireX
import geofirex from 'geofirex';
const geo = geofirex.init(firebase);
```Node.js with the Firebase Admin SDK:
```js
const admin = require('firebase-admin');
admin.initializeApp();const geo = require('geofirex').init(admin);
```With Typescript:
```ts
import * as geofirex from 'geofirex';
const geo = geofirex.init(firebase);
```### Write Geolocation Data
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.
```ts
const cities = firestore().collection('cities');const position = geo.point(40, -119);
cities.add({ name: 'Phoenix', position });
```![](https://firebasestorage.googleapis.com/v0/b/geo-test-c92e4.appspot.com/o/point1.png?alt=media&token=0c833700-3dbd-476a-99a9-41c1143dbe97)
### Query Geo Data
Query Firestore for _cities.position within 100km radius of a centerpoint_.
```ts
const center = geo.point(40.1, -119.1);
const radius = 100;
const field = 'position';const query = geo.query(cities).within(center, radius, field);
```Each hit returns a realtime Observable of the document data, plus some useful `hitMetadata` like _distance_ and _bearing_ from the query centerpoint.
```ts
query.subscribe(console.log);
// [{ ...documentData, hitMetadata: { distance: 1.23232, bearing: 230.23 } }]
```You now have a realtime stream of data to visualize on a map.
![](https://firebasestorage.googleapis.com/v0/b/geo-test-c92e4.appspot.com/o/geoquery-fire2.gif?alt=media&token=487abd17-90a3-4589-a82d-81d172ddeb25)
## :notebook: API
### `query(ref: CollectionReference | Query | string): GeoFireQuery`
Creates reference to a Firestore collection or query that can be used to make geo-queries.
Example:
```ts
const geoRef = geo.query('cities');// OR make a geoquery on top of a firestore query
const firestoreRef = firestore().collection('cities').where('name', '==', 'Phoenix');
const geoRef = geo.query(firestoreRef);
```#### `within(center: FirePoint, radius: number, field: string): Observable`
```js
const query = geoRef.within(center: FirePoint, radius: number, field: string)
query.subscribe(hits => console.log(hits))// OR fetch as a promise
import { get } from 'geofirex';
const hits = await get(query);
```Query the parent Firestore collection by geographic distance. It will return documents that exist within X kilometers of the centerpoint.
Each doc also contains returns _distance_ and _bearing_ calculated on the query on the `hitMetadata` property.
### `point(latitude: number, longitude: number): FirePoint`
Returns an object with the required geohash format to save to Firestore.
Example: `const point = geo.point(38, -119)`
A point is a plain JS object with two properties.
- `point.geohash` Returns a geohash string at precision 9
- `point.geopoint` Returns a Firestore GeoPoint## Additional Features
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.
### Logging
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.
```js
query.within(center, radius, field, { log: true })
```![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)
### Geo Calculations
Convenience methods for calculating distance and bearing.
- `geo.distance(geo.point(38, -118), geo.point(40, -115))` Haversine distance
- `geo.bearing(to, from)` Haversine bearing### `toGeoJSON` Operator
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.
```ts
import { toGeoJSON } from 'geofirex';const query = geo.query('cars').within(...)
query.pipe( toGeoJSON() )
// Emits a single object typed as a FeatureCollection
{
"type": "FeatureCollection",
"features": [...]
}
```#### Promises with `get`
Don't need a realtime stream? Convert any query observable to a promise by wrapping it with `get`.
```ts
import { get } from 'geofirex';async function getCars {
const query = geo.query('cars').within(...)
const cars = await get(query)
}
```## Tips
### Compound Queries
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.
Example:
```ts
// Make a query like you normally would
const users = firestore().collection('users').where('status', '==', 'online');const nearbyOnlineUsers = geo.query(users).within(center, radius, field);
```Note: This query requires a composite index, which you will be prompted to create with an error from Firestore on the first request.
### Usage with RxJS < 6.2, or Ionic v3
This package requires RxJS 6.2, but you can still use it with older versions without blowing up your app by installing rxjs-compat.
Example:
```shell
npm i rxjs@latest rxjs-compat
```### Make Dynamic Queries the RxJS Way
```ts
const radius = new BehaviorSubject(1);
const cities = geo.query('cities');const points = this.radius.pipe(
switchMap(rad => {
return cities.within(center, rad, 'point');
})
);// Now update your query
radius.next(23);
```### Always Order by `[Latitude, Longitude]`
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.