geofirex

Realtime Firestore GeoQueries with RxJS

Downloads in past

Stats

StarsIssuesVersionUpdatedCreatedSize
geofirex
4731100.1.05 years ago6 years agoMinified + gzip package size for geofirex in KB

Readme

GeoFireX
Realtime Geolocation with Firestore & RxJS. Query geographic points within a radius on the web or Node.js.

: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
and a 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 }); ```

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

:notebook: API

query(ref: CollectionReference | Query | string)

Creates reference to a Firestore collection or query that can be used to make geo-queries. Example: ```ts const geoQuery = geo.query('cities'); // OR make a geoquery on top of a firestore query const query = firestore().collection('cities').where('name', '==', 'Phoenix'); const geoQuery = geo.query(query); ```

Perform Geo-Queries

```js geoQuery.within(center: FirePoint, radius: number, field: string)
.subscribe((hits) => console.log((hits)))
``` 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. Returns: Observable<T[]>

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 ref.within(center, radius, field, { log: true }) ``` Logging GeoQueries

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. Very useful for tools like MapBox that can use GeoJSON to update a realtime data source. ```ts 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 query = firestore().collection('users').where('status', '==', 'online'); const users = geo.query(query) const nearbyOnlineUsers = 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.