Skip to content

Commit c408b3f

Browse files
committed
fix: unsubscribe from inner geohashes
1 parent deefd34 commit c408b3f

File tree

6 files changed

+127
-32
lines changed

6 files changed

+127
-32
lines changed

README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,13 @@ const geo = require('geofirex').init(admin);
4848
With Typescript:
4949

5050
```ts
51-
// Init Firebase
52-
import * as firebase from 'firebase/app';
53-
firebase.initializeApp(yourConfig);
54-
55-
// Init GeoFireX
5651
import * as geofirex from 'geofirex';
5752
const geo = geofirex.init(firebase);
5853
```
5954

6055
### Write Geo Data
6156

62-
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.
57+
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
6358

6459
```ts
6560
const cities = geo.collection('cities');
@@ -142,7 +137,9 @@ Returns a GeoFirePoint allowing you to create geohashes, format data, and calcul
142137

143138
Example: `const point = geo.point(38, -119)`
144139

145-
#### Getters
140+
#### Get Data
141+
142+
A point can return data in a variety of formats.
146143

147144
- `point.hash()` Returns a geohash string at precision 9
148145
- `point.geoPoint()` Returns a Firestore GeoPoint
@@ -159,6 +156,11 @@ Example: `const point = geo.point(38, -119)`
159156

160157
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.
161158

159+
### Logging
160+
161+
162+
![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)
163+
162164
### `toGeoJSON` Operator
163165

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

example/src/app/basic-geoquery/basic-geoquery.component.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Component, OnInit } from '@angular/core';
2-
import { Observable, BehaviorSubject } from 'rxjs';
3-
import { switchMap } from 'rxjs/operators';
2+
import { Observable, BehaviorSubject, interval } from 'rxjs';
3+
import { switchMap, share, shareReplay, tap } from 'rxjs/operators';
44
import * as firebaseApp from 'firebase/app';
55
import * as geofirex from 'geofirex';
66

@@ -19,12 +19,19 @@ export class BasicGeoqueryComponent implements OnInit {
1919

2020
ngOnInit() {
2121
const center = this.geo.point(40.5, -80.0);
22-
const radius = 0.5;
2322
const field = 'pos';
2423

25-
const collection = this.geo.collection('users', ref =>
26-
ref.where('status', '==', 'single').where('online', '==', true)
24+
this.points = this.radius.pipe(
25+
switchMap(r => {
26+
console.log('new rad');
27+
return this.geo.collection('bearings').within(center, r, field, { log: true });
28+
}),
29+
shareReplay(1)
2730
);
31+
32+
// this.radius.pipe(
33+
// switchMap(v => interval(500).pipe(tap(console.log)) )
34+
// ).subscribe()
2835
}
2936

3037
update(v) {

integration/functions/firebase-debug.log

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,3 +383,52 @@
383383
[info] > [object Object]
384384
[debug] [2019-11-06T20:11:52.027Z] [runtime-status] Ephemeral server survived. {}
385385
[info] i functions: Finished "testFun" in ~1s
386+
[info] Shutting down...
387+
[debug] [2019-11-07T04:01:12.571Z] ----------------------------------------------------------------------
388+
[debug] [2019-11-07T04:01:12.573Z] Command: C:\Program Files\nodejs\node.exe C:\Program Files\nodejs\node_modules\firebase-tools\lib\bin\firebase.js serve --only functions
389+
[debug] [2019-11-07T04:01:12.573Z] CLI Version: 7.5.0
390+
[debug] [2019-11-07T04:01:12.573Z] Platform: win32
391+
[debug] [2019-11-07T04:01:12.573Z] Node Version: v10.16.0
392+
[debug] [2019-11-07T04:01:12.574Z] Time: Wed Nov 06 2019 21:01:12 GMT-0700 (Mountain Standard Time)
393+
[debug] [2019-11-07T04:01:12.574Z] ----------------------------------------------------------------------
394+
[debug]
395+
[debug] [2019-11-07T04:01:12.600Z] > command requires scopes: ["email","openid","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","https://www.googleapis.com/auth/cloud-platform"]
396+
[debug] [2019-11-07T04:01:12.600Z] > authorizing via signed-in user
397+
[debug] [2019-11-07T04:01:12.600Z] [iam] checking project geo-test-c92e4 for permissions ["firebase.projects.get"]
398+
[debug] [2019-11-07T04:01:12.601Z] > refreshing access token with scopes: ["email","https://www.googleapis.com/auth/cloud-platform","https://www.googleapis.com/auth/cloudplatformprojects.readonly","https://www.googleapis.com/auth/firebase","openid"]
399+
[debug] [2019-11-07T04:01:12.602Z] >>> HTTP REQUEST POST https://www.googleapis.com/oauth2/v3/token
400+
<request body omitted>
401+
[debug] [2019-11-07T04:01:12.847Z] <<< HTTP RESPONSE 200
402+
[debug] [2019-11-07T04:01:12.856Z] >>> HTTP REQUEST POST https://cloudresourcemanager.googleapis.com/v1/projects/geo-test-c92e4:testIamPermissions
403+
404+
[debug] [2019-11-07T04:01:13.276Z] <<< HTTP RESPONSE 200
405+
[debug] [2019-11-07T04:01:13.279Z] >>> HTTP REQUEST GET https://firebase.googleapis.com/v1beta1/projects/geo-test-c92e4
406+
407+
[debug] [2019-11-07T04:01:13.580Z] <<< HTTP RESPONSE 200
408+
[warn] ! Your functions directory does not specify a Node version.
409+
- Learn more at https://firebase.google.com/docs/functions/manage-functions#set_runtime_options
410+
[info] + functions: Emulator started at http://localhost:5000
411+
[info] i functions: Watching "C:\Users\jeffd23\apps\geofirex\integration\functions" for Cloud Functions...
412+
[debug] [2019-11-07T04:01:16.402Z] [runtime-status] Functions runtime initialized. {"cwd":"C:\\Users\\jeffd23\\apps\\geofirex\\integration\\functions","node_version":"10.16.0"}
413+
[debug] [2019-11-07T04:01:16.403Z] [runtime-status] Disabled runtime features: {"functions_config_helper":true,"network_filtering":true,"timeout":true,"memory_limiting":true,"admin_stubs":true} {}
414+
[debug] [2019-11-07T04:01:16.408Z] [runtime-status] Resolved module firebase-admin {"declared":true,"installed":true,"version":"8.7.0","resolution":"C:\\Users\\jeffd23\\apps\\geofirex\\integration\\functions\\node_modules\\firebase-admin\\lib\\index.js"}
415+
[debug] [2019-11-07T04:01:16.414Z] [runtime-status] Resolved module firebase-functions {"declared":true,"installed":true,"version":"3.3.0","resolution":"C:\\Users\\jeffd23\\apps\\geofirex\\integration\\functions\\node_modules\\firebase-functions\\lib\\index.js"}
416+
[debug] [2019-11-07T04:01:16.415Z] [runtime-status] Resolved module firebase-functions {"declared":true,"installed":true,"version":"3.3.0","resolution":"C:\\Users\\jeffd23\\apps\\geofirex\\integration\\functions\\node_modules\\firebase-functions\\lib\\index.js"}
417+
[info] > GeoFireClient {
418+
[info] > app:
419+
[info] > FirebaseNamespace {
420+
[info] > __esModule: true,
421+
[info] > credential:
422+
[info] > { cert: [Function: cert],
423+
[info] > refreshToken: [Function: refreshToken],
424+
[info] > applicationDefault: [Function: applicationDefault] },
425+
[info] > SDK_VERSION: '8.7.0',
426+
[info] > Promise: [Function: Promise],
427+
[info] > INTERNAL:
428+
[info] > FirebaseNamespaceInternals {
429+
[info] > firebase_: [Circular],
430+
[info] > serviceFactories: {},
431+
[info] > apps_: [Object],
432+
[info] > appHooks_: {} },
433+
[info] > default: [Circular] } }
434+
[info] + functions[testFun]: http function initialized (http://localhost:5000/geo-test-c92e4/us-central1/testFun).

integration/src/app/app.module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export const config = {
3535
}),
3636
AgmSnazzyInfoWindowModule,
3737
AngularFireModule.initializeApp(config),
38-
AngularFirestoreModule
38+
AngularFirestoreModule.enablePersistence()
3939
],
4040
providers: [],
4141
bootstrap: [AppComponent]

spec/main.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,8 +251,9 @@ describe('RxGeofire', () => {
251251
test('should be ordered by distance', async done => {
252252
const first = data[0].queryMetadata.distance;
253253
const last = data[data.length - 1].queryMetadata.distance;
254+
console.log(111111, data.length,last)
254255
expect(first).toBeCloseTo(0.2);
255-
expect(last).toBeCloseTo(5);
256+
expect(last).toBeGreaterThan(1);
256257
expect(first).toBeLessThan(last);
257258
done();
258259
});

src/collection.ts

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// import { firestore } from './interfaces';
22

3-
import { Observable, combineLatest } from 'rxjs';
4-
import { shareReplay, map, first } from 'rxjs/operators';
3+
import { Observable, combineLatest, Subject } from 'rxjs';
4+
import { shareReplay, map, first, tap, finalize, takeUntil } from 'rxjs/operators';
55
import { GeoFirePoint } from './point';
66
import { setPrecsion } from './util';
77
import { FeatureCollection, Geometry } from 'geojson';
@@ -13,9 +13,10 @@ import { FirebaseSDK } from './interfaces';
1313
export type QueryFn = (ref: fb.firestore.CollectionReference) => fb.firestore.Query;
1414

1515
export interface GeoQueryOptions {
16-
units: 'km'
16+
units?: 'km',
17+
log?: boolean
1718
}
18-
const defaultOpts: GeoQueryOptions = { units: 'km' };
19+
const defaultOpts: GeoQueryOptions = { units: 'km', log: false };
1920

2021
export interface QueryMetadata {
2122
bearing: number;
@@ -124,41 +125,70 @@ export class GeoFireCollectionRef<T = any> {
124125
center: GeoFirePoint,
125126
radius: number,
126127
field: string,
127-
opts = defaultOpts
128+
opts?: GeoQueryOptions
128129
): Observable<(GeoQueryDocument & T)[]> {
130+
opts = { ...defaultOpts, ...opts }
129131
const precision = setPrecsion(radius);
130132
const radiusBuffer = radius * 1.02; // buffer for edge distances
131133
const centerHash = center.hash().substr(0, precision);
132134
const area = neighbors(centerHash).concat(centerHash);
133135

136+
// Used to cancel the individual geohash subscriptions
137+
const complete = new Subject();
138+
139+
// Map geohash neighbors to individual queries
134140
const queries = area.map(hash => {
135141
const query = this.queryPoint(hash, field);
136-
return createStream(query).pipe(snapToData());
142+
return createStream(query).pipe(
143+
snapToData(),
144+
takeUntil(complete),
145+
);
137146
});
138147

148+
const tick = Date.now();
149+
150+
151+
139152
const combo = combineLatest(...queries).pipe(
140153
map(arr => {
141154
const reduced = arr.reduce((acc, cur) => acc.concat(cur));
142-
return reduced
155+
156+
const filtered = reduced
143157
.filter(val => {
144-
const lat = val[field].geopoint.latitude;
145-
const lng = val[field].geopoint.longitude;
146-
return center.distance(lat, lng) <= radiusBuffer;
158+
159+
const { latitude, longitude } = val[field].geopoint;
160+
return center.distance(latitude, longitude) <= radiusBuffer;
147161
})
148162

149-
.map(val => {
150-
const lat = val[field].geopoint.latitude;
151-
const lng = val[field].geopoint.longitude;
163+
if (opts.log) {
164+
console.group('GeoFireX Query');
165+
console.log('💡 Logs update on every change to the query');
166+
console.log(`🌐 Center ${center.coords()}. Radius ${radius}`)
167+
// const cached = reduced.reduce((a, c) => a + (c.fromCache ? 1 : 0), 0);
168+
console.log(`📍 Hits: ${reduced.length}.`) //Cached ${cached}
169+
console.log(`⌚ Elapsed time: ${Date.now() - tick}ms`);
170+
console.log(`🟢 Within Radius: ${filtered.length}`);
171+
console.groupEnd();
172+
}
173+
174+
return filtered.map(val => {
175+
176+
const { latitude, longitude } = val[field].geopoint;
177+
152178
const queryMetadata = {
153-
distance: center.distance(lat, lng),
154-
bearing: center.bearing(lat, lng)
179+
distance: center.distance(latitude, longitude),
180+
bearing: center.bearing(latitude, longitude),
155181
};
156182
return { ...val, queryMetadata } as (GeoQueryDocument & T);
157183
})
158184

159185
.sort((a, b) => a.queryMetadata.distance - b.queryMetadata.distance);
160186
}),
161-
shareReplay(1)
187+
shareReplay(1),
188+
finalize(() => {
189+
opts.log && console.log('✋ Query complete');
190+
complete.next(true)
191+
})
162192
);
163193

164194
return combo;
@@ -184,14 +214,16 @@ export class GeoFireCollectionRef<T = any> {
184214
// findFirst() {
185215
// return 'not implemented';
186216
// }
217+
187218
}
188219

220+
189221
function snapToData(id = 'id') {
190222
return map((querySnapshot: fb.firestore.QuerySnapshot) =>
191223
querySnapshot.docs.map(v => {
192224
return {
193225
...(id ? { [id]: v.id } : null),
194-
...v.data()
226+
...v.data(),
195227
};
196228
})
197229
);
@@ -202,9 +234,13 @@ internal, do not use
202234
*/
203235
function createStream(input): Observable<any> {
204236
return new Observable(observer => {
205-
const unsubscribe = input.onSnapshot((val) => observer.next(val), err => observer.error(err));
237+
const unsubscribe = input.onSnapshot(
238+
(val) => observer.next(val),
239+
err => observer.error(err),
240+
);
206241
return { unsubscribe };
207242
});
243+
// return bindCallback(input.onSnapshot.bind(input.onSnapshot))();
208244
}
209245
/**
210246
* RxJS operator that converts a collection to a GeoJSON FeatureCollection

0 commit comments

Comments
 (0)