Server-side geolocation distance calculation with Firebase/Firestore [duplicate] - firebase

GeoFire is tightly coupled to the Realtime Database, while geo-queries are a common functional dependency of many apps that are looking to migrate to Firestore. Is there any way to replicate the hashing/retrieval of locations in the Firestore environment?

Edit (Dec 17th, 2020): we have recently released a set of geo utility libraries and a guide to explain how to use them to implement simple geo queries on Firestore!
https://firebase.google.com/docs/firestore/solutions/geoqueries
While we still don't have native geo queries in the database, these Android, iOS, and Web libraries will help you use Geohashes to get geo querying functionality.
Edit (July 1st, 2019): When I originally wrote the answer below I was optimistic that native geo queries would come to Cloud Firestore soon, which clearly did not happen. It's still in the long-term plans, but for now the best option is to use a community-built library or make your own using either GeoHashes or the S2 Geometry library.
The GeoFire libraries for Realtime Database were built using GeoHashes and porting the logic of those libraries to Cloud Firestore should be relatively simple.
Sam from the Cloud Firestore team here. As SUPERCILEX said, Cloud Firestore has support for the GeoPoint data type already. We are working hard to bring native geo queries to the product.
Because native geo queries are coming, we will not be porting GeoFire to Cloud Firestore. Instead we will redirect that engineering effort to getting the native queries sooner.
If you need geo queries today and don't want to build your own library, stick with Realtime Database!

GREAT NEWS. There is now a library for both iOS and Android that replicates GeoFire for Firestore. The library is called GeoFirestore. It has full documentation and is well tested. I currently use it in my app and it works brilliantly. The code is very similar to that of GeoFire so it should only take a few minutes to learn.

A solution that comes to mind is to add on the Realtime Database just for geo-queries and synchronize the two databases with Cloud Functions, much like Google suggests with presence.
In my case it's not even necessary to synchronize much; I just keep a list of uids with their locations on the Realtime Database and do all geo-queries there.

A new project has been introduced since the original poster first ask this question. The project is called GEOFirestore.
With this library you can perform queries like query documents within a circle:
const geoQuery = geoFirestore.query({
center: new firebase.firestore.GeoPoint(10.38, 2.41),
radius: 10.5
});
You can install GeoFirestore via npm. You will have to install Firebase separately (because it is a peer dependency to GeoFirestore):
$ npm install geofirestore firebase --save

The Javascript solution for GeoQuery with Firestore is to use GeoFirestore as Nikhil Sridhar said. But is a quite difficult to use (or it was for me).
First of all you have to initialize a GeoFirestore reference.
var firebase = require('firebase-admin');
var GeoFirestore = require('geofirestore');
// Create a Firestore reference
const firestore = firebase.firestore();
// Create a GeoFirestore reference
const geofirestore = new GeoFirestore.GeoFirestore(firestore);
// Create a collection reference but using geofirestore collections
// this is where you save the geofirestore documents with its structure
const geocollection = geofirestore.collection('userPositions');
After you have your collection initialized, the first step is save a document with the specified structure
{
g: string;
l: GeoPoint;
d: DocumentData;
}
geofirestore.collection('userPositions').doc(id).set({ coordinates: new firebase.firestore.GeoPoint(0, 0)}).then(res => {
return res;
}).catch(err => {
console.log(err);
});
Only when you have your collection with geofirestore documents, you can query about them as the example said.
// Create a GeoQuery based on a location
const query = geocollection.near({ center: new firebase.firestore.GeoPoint(0, 0), radius: 1000 });
// Get query (as Promise)
query.get().then((value) => {
console.log(value.docs); // All docs returned by GeoQuery
});
Hope this steps help you!

Cloud Firestore natively supports geo points. See the supported data types. And you'll find the GeoPoint class which you can use to set data and also query it:
query.whereEqualTo("location", GeoPoint(lat, long))

Related

Is there a way to add new field to all of the documents in a firestore collection?

I have a collection that needs to be updated. There's a need to add new field and fill it out based on the existing field.
Let's say I have a collection called documents:
documents/{documentId}: {
existingField: ['foo', 'bar'],
myNewField ['foo', 'bar']
}
documents/{anotherDocumentId}: {
existingField: ['baz'],
myNewField ['baz']
}
// ... and so on
I already tried to fire up local cloud function from emulator that loops for each document and writes to production data based on the logic I need. The problem is that function can only live up to max of 30 seconds. What I need would be some kind of console tool that I can run as admin (using service-account) to quickly manage my needs.
How do you handle such cases?
Firebase does not provide a console or tool to do migrations.
You can write a program to run on your development machine that uses the one of the backend SDKs (like the Firebase Admin SDK) to query, iterate, and update the documents and let it run as long as you want.
There is nothing specific built into the API for this type of data migration. You'll have to update each document in turn, which typically involves also reading all documents (or at least their IDs).
While it is possible to do this on Cloud Functions, I find it easier to do it with a local Node.js script, as that doesn't have the runtime limits Cloud Functions imposes.

Accesssing Firebase Firestore from a script

I have a flutter Andriod app that can be used to store data in the firestore. Now I want to access that stored data from a Dart script to perform some operations on the data.
How can I access the firestore with a Dart script?
Updated the question: Looking only for dart package/library to access firebase firestore.
The Firebase docs provides good help with how to set up environment and a lot of examples.
It is possible with firedart.
import 'package:firedart/firedart.dart';
String pi = 'example-app';
void main(List<String> arguments) async {
Firestore.initialize(pi);
var map = await Firestore.instance.collection("users").get();
print(map);
}

Stripe firebase extension, listen to invoices collection in collection group

Regarding stripe extension for firebase: https://firebase.google.com/products/extensions/firestore-stripe-invoices
I haven't found anything related in the documentation yet, and source code didn't reveal much to me (at least to my understanding).
In the configuration of the extension it asks for a collection to listen for invoices. Is it possible to use a collection group instead? So instead of listening for invoices at invoices I want it to listen at users/{uid}/invoices
From the extension code,
let invoicesInFirestore = await admin
.firestore()
.collection(config.invoicesCollectionPath)
.where('stripeInvoiceId', '==', invoice.id)
.get();
it appears that, at the time of writing, it is not foreseen to handle multi subcollections.
I can see two solutions:
1. Adapt the extension code to create your own Cloud Functions
You copy the extension code and modify it in such a way it handles multi subcollections, in order to create your own Cloud Functions.
Note that the extension code declares the Cloud Function with export const sendInvoice = functions.handler.firestore.document.onCreate(...). As explained in the doc "The HandlerBuilder class facilitates the writing of functions by developers building Firebase Extensions... Do not use HandlerBuilder when writing normal functions for deployment via the Firebase CLI."
So you should adapt it as follows:
export const sendInvoice = functions.firestore
.document('users/{uid}/invoices')
.onCreate((snap, context) => {...}
However, note that by writing your own Cloud Function, you will loose potential future improvements to the extension.
2. Create a Cloud Function that copies/pastes the invoices from the subcollections
The idea is to have a Cloud Function listening to all the users/{uid}/invoices subcollections (see above) that copies the new doc and creates a copy in the "central" Extension collection. Nothing prevent you to add some extra fields in the copied doc, like the user's uid.
Note that, if you want to get the feedback from Stripe, you may need another Cloud Function (which listens to the "central" Extension collection) to copy/paste the results of the Stripe webhook calls to the original documents in the users/{uid}/invoices subcollection.
Personally I would go for this second approach.

Updating from Document DB SDK (2.7.0) to Cosmos SDK (3.12.0)

We are in a process of updating our service code to using Cosmos SDK 3.12.0 from DocumentDB SDK 2.7.0. Since a change will likely be huge, we would like to do it incrementally that will result in our service using both SDKs to access the same databases (one executable loading assemblies of both SDKs). Please let us know if that is supported or if you see any issues in doing so. Also, I couldn’t figure out how to do things in same ways with Cosmos SDK (e.g. specifying “enable cross partition query” in querying items – the query method in 2.7.0 takes FeedOptions as a parameter whereas the new one in 3.12.0 doesn’t). I found this wiki and some sample code but if you have more info/guidelines for converting from Document SDK to Cosmos SDK, please let me know.
Yes,you can use both DocumentDB sdk and cosmos sdk access the same databases.
In Cosmos SDK 3.12.0,there is no need to set EnableCrossPartitionQuery true.just do something like this is ok:
QueryDefinition queryDefinition = new QueryDefinition("select * from c");
FeedIterator<JObject> feedIterator = container.GetItemQueryIterator<JObject>(queryDefinition);
while (feedIterator.HasMoreResults)
{
foreach (var item in await feedIterator.ReadNextAsync())
{
{
Console.WriteLine(item.ToString());
}
}
}

Is there a way to use GeoFire with Firestore?

GeoFire is tightly coupled to the Realtime Database, while geo-queries are a common functional dependency of many apps that are looking to migrate to Firestore. Is there any way to replicate the hashing/retrieval of locations in the Firestore environment?
Edit (Dec 17th, 2020): we have recently released a set of geo utility libraries and a guide to explain how to use them to implement simple geo queries on Firestore!
https://firebase.google.com/docs/firestore/solutions/geoqueries
While we still don't have native geo queries in the database, these Android, iOS, and Web libraries will help you use Geohashes to get geo querying functionality.
Edit (July 1st, 2019): When I originally wrote the answer below I was optimistic that native geo queries would come to Cloud Firestore soon, which clearly did not happen. It's still in the long-term plans, but for now the best option is to use a community-built library or make your own using either GeoHashes or the S2 Geometry library.
The GeoFire libraries for Realtime Database were built using GeoHashes and porting the logic of those libraries to Cloud Firestore should be relatively simple.
Sam from the Cloud Firestore team here. As SUPERCILEX said, Cloud Firestore has support for the GeoPoint data type already. We are working hard to bring native geo queries to the product.
Because native geo queries are coming, we will not be porting GeoFire to Cloud Firestore. Instead we will redirect that engineering effort to getting the native queries sooner.
If you need geo queries today and don't want to build your own library, stick with Realtime Database!
GREAT NEWS. There is now a library for both iOS and Android that replicates GeoFire for Firestore. The library is called GeoFirestore. It has full documentation and is well tested. I currently use it in my app and it works brilliantly. The code is very similar to that of GeoFire so it should only take a few minutes to learn.
A solution that comes to mind is to add on the Realtime Database just for geo-queries and synchronize the two databases with Cloud Functions, much like Google suggests with presence.
In my case it's not even necessary to synchronize much; I just keep a list of uids with their locations on the Realtime Database and do all geo-queries there.
A new project has been introduced since the original poster first ask this question. The project is called GEOFirestore.
With this library you can perform queries like query documents within a circle:
const geoQuery = geoFirestore.query({
center: new firebase.firestore.GeoPoint(10.38, 2.41),
radius: 10.5
});
You can install GeoFirestore via npm. You will have to install Firebase separately (because it is a peer dependency to GeoFirestore):
$ npm install geofirestore firebase --save
The Javascript solution for GeoQuery with Firestore is to use GeoFirestore as Nikhil Sridhar said. But is a quite difficult to use (or it was for me).
First of all you have to initialize a GeoFirestore reference.
var firebase = require('firebase-admin');
var GeoFirestore = require('geofirestore');
// Create a Firestore reference
const firestore = firebase.firestore();
// Create a GeoFirestore reference
const geofirestore = new GeoFirestore.GeoFirestore(firestore);
// Create a collection reference but using geofirestore collections
// this is where you save the geofirestore documents with its structure
const geocollection = geofirestore.collection('userPositions');
After you have your collection initialized, the first step is save a document with the specified structure
{
g: string;
l: GeoPoint;
d: DocumentData;
}
geofirestore.collection('userPositions').doc(id).set({ coordinates: new firebase.firestore.GeoPoint(0, 0)}).then(res => {
return res;
}).catch(err => {
console.log(err);
});
Only when you have your collection with geofirestore documents, you can query about them as the example said.
// Create a GeoQuery based on a location
const query = geocollection.near({ center: new firebase.firestore.GeoPoint(0, 0), radius: 1000 });
// Get query (as Promise)
query.get().then((value) => {
console.log(value.docs); // All docs returned by GeoQuery
});
Hope this steps help you!
Cloud Firestore natively supports geo points. See the supported data types. And you'll find the GeoPoint class which you can use to set data and also query it:
query.whereEqualTo("location", GeoPoint(lat, long))

Resources