Ricardo
Ricardo

Reputation: 8291

Geofire - not found locations

How can I get the all locations that are near to a passed GeoLocation(double lat, double lng) using Geo Queries. I have the following code (and it happens nothing):

public void setCurrentLatLng(double lat, double lng){
    this.lat = lat;
    this.lng = lng;
    GeoLocation geoLocation = new GeoLocation(lat, lng);
    updateCurrenLocation(geoLocation);
    GeoQuery geoQuery = geoFire.queryAtLocation(geoLocation, 8f);
    geoQuery.addGeoQueryDataEventListener(new GeoQueryDataEventListener() {

        @Override
        public void onDataEntered(DataSnapshot dataSnapshot, GeoLocation location) {
            Log.d("geoQuery","onDataEntered "+dataSnapshot.toString());
            // ...
        }

        @Override
        public void onDataExited(DataSnapshot dataSnapshot) {
            Log.d("geoQuery","onDataExited "+dataSnapshot.toString());
            // ...
        }

        @Override
        public void onDataMoved(DataSnapshot dataSnapshot, GeoLocation location) {
            Log.d("geoQuery","onDataMoved "+dataSnapshot.toString());
            // ...
        }

        @Override
        public void onDataChanged(DataSnapshot dataSnapshot, GeoLocation location) {
            Log.d("geoQuery","onDataChanged "+dataSnapshot.toString());
            // ...
        }

        @Override
        public void onGeoQueryReady() {
            // ...
            Log.d("geoQuery","onGeoQueryReady");
        }

        @Override
        public void onGeoQueryError(DatabaseError error) {
            Log.d("geoQuery","onGeoQueryError");
            // ...
        }

    });
    this.setChanged();
    notifyObservers();
    this.clearChanged();
    Log.d("update","clearChanged");
}

And this is my firebase data: enter image description here

I think I'm open to modify the data struct if needed.

Logs

09-12 08:55:33.818 17710-17710/es.rchampa.weirdo D/geoQuery: lat=40.4430883 lng=-3.721805
09-12 08:55:33.982 17710-17710/es.rchampa.weirdo D/geoQuery: lat=40.4430883 lng=-3.721805
09-12 08:55:33.986 17710-17710/es.rchampa.weirdo D/geoQuery: onGeoQueryReady
09-12 08:55:34.025 17710-17710/es.rchampa.weirdo D/geoQuery: onGeoQueryReady

Gradle file

....
// Firebase
implementation 'com.google.firebase:firebase-database:16.0.1'
implementation 'com.google.firebase:firebase-storage:16.0.1'
implementation 'com.google.firebase:firebase-auth:16.0.3'
implementation 'com.google.firebase:firebase-crash:16.2.0'
implementation 'com.google.firebase:firebase-core:16.0.3'

// Firebase UI
implementation 'com.firebaseui:firebase-ui-database:1.2.0'

//Firebase GeoFire
implementation 'com.firebase:geofire-android:2.3.1'

// Google Play Services
implementation 'com.google.android.gms:play-services-auth:16.0.0'
implementation 'com.google.android.gms:play-services-maps:15.0.1'
implementation 'com.google.android.gms:play-services-location:15.0.1'
....

UPDATE

I can grant access to my private repo if you wish.

Upvotes: 7

Views: 1374

Answers (3)

Martin Zeitler
Martin Zeitler

Reputation: 76759

you pass value 8f (float) as the radius there, while the the radius should rather be 8.0d or Double.valueOf(8.0), where MAX_SUPPORTED_RADIUS equals 8587 kilometers.

while the actual problem is, that GeoFire already would need to know of .child("location"), but it is not possible to represent that with a Reference; only DataSnapshot has getChildren().

the bottom line is:

you'd have to create a separate locations Reference, in order to avoid the nesting. nevertheless you still can use the related uid key for these nodes (or at least add it as a child-node), in order to be able to look up within the users Reference. it's a 1:1 relation, in between two References.

so here's a working Java example, just because ...

we're assuming the following structure (as described above):

{
  "locations" : {
    "CR88aa9gnDfJYYGq5ZTMwwC38C12" : {
      ".priority" : "9q8yywdgue",
      "g" : "9q8yywdgue",
      "l" : [ 37.7853889, -122.4056973 ]
    }
  },
  "users" : {
    "CR88aa9gnDfJYYGq5ZTMwwC38C12" : {
      "displayName" : "user 01",
      ...
    }
  }
}

the database rules should have .indexOn for locations field g set:

{
  "rules": {
    ...
    "locations": {
      ".indexOn": "g"
    }
  }
}

the dependencies in the module's build.gradle:

dependencies {
    ...
    implementation "com.firebase:geofire-android:2.3.1"
}

and this demonstrates, how to obtain a user's snapshot by a GeoQuery result;

notice the GeoQueryEventListener instead of the GeoQueryDataEventListener:

public class GeofireActivity extends AppCompatActivity {

    private static final String LOG_TAG = GeofireActivity.class.getSimpleName();

    private DatabaseReference refBase     = null;
    private DatabaseReference refLocation = null;
    private DatabaseReference refUser     = null;

    private GeoFire geoFire = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.fragment_geofire);
        this.setReferences();
    }

    private void setReferences() {
        this.refBase = FirebaseDatabase.getInstance().getReference();
        this.refUser = refBase.child("users");
        this.refLocation = refBase.child("locations");
        this.geoFire = new GeoFire(this.refLocation);
    }

    private void searchNearby(double latitude, double longitude, double radius) {
        this.searchNearby(new GeoLocation(latitude, longitude), radius);
    }

    private void searchNearby(GeoLocation location, double radius) {

        GeoQuery geoQuery = this.geoFire.queryAtLocation(location, radius);
        geoQuery.addGeoQueryEventListener(new GeoQueryEventListener() {

            @Override
            public void onKeyEntered(String key, GeoLocation location) {

                String loc = String.valueOf(location.latitude) + ", " + String.valueOf(location.longitude);
                Log.d(LOG_TAG, "onKeyEntered: " + key + " @ " + loc);

                /* once the key is known, one can lookup the associated record */
                refUser.child(key).addListenerForSingleValueEvent(new ValueEventListener() {

                    @Override
                    public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
                        Log.d(LOG_TAG, "onDataChange: " + dataSnapshot.toString());
                    }

                    @Override
                    public void onCancelled(@NonNull DatabaseError firebaseError) {
                        Log.e(LOG_TAG, "onCancelled: " + firebaseError.getMessage());
                    }
                });
            }

            @Override
            public void onKeyExited(String key) {
                Log.d(LOG_TAG, "onKeyExited: " + key);
            }

            @Override
            public void onKeyMoved(String key, GeoLocation location) {
                Log.d(LOG_TAG, "onKeyMoved: " + key);
            }

            @Override
            public void onGeoQueryReady() {
                Log.d(LOG_TAG, "onGeoQueryReady");
            }

            @Override
            public void onGeoQueryError(DatabaseError error) {
                Log.e(LOG_TAG, "onGeoQueryError" + error.getMessage());
            }
        });
    }
}

in order to maintain the integrity, one would need to remove the associated location record, when a user record is being removed - else it would result in keys, which cannot be looked up anymore.

Upvotes: 3

MichaelSolati
MichaelSolati

Reputation: 2872

As it stands right now geofire sort of serves as an index to make geoqueries on, and provides the key of the document you want (which would be stored in a separate "collection").

You should be using geofire and a separate "collection" (call it usersLocations)

DatabaseReference ref = FirebaseDatabase.getInstance().getReference("usersLocations");
GeoFire geoFire = new GeoFire(ref);

Now you can use it as an index for your users, and can add items to it like so.

geoFire.setLocation('QymlMpC0Zc', new GeoLocation(40.2334983, -3.7185183));

Your Firebase RTDB will look like this now:

{
   'users': {
        'QymlMpC0Zc': {
            // All your data here
        }
    },
   'usersLocations': {
        'QymlMpC0Zc': {
            'g': 'ezjkgkk305',
            'l': {
                '0': 40.2334983,
                '1': -3.7185183
            }
        }
    }
}

So finally when you do a query on your geoFire you'll end up being firing whatever listeners you have.

As a small note... I am not a Java developer, but I do use/know geofire in general. Hopefully my bits of advice/thoughts will be helpful.

Upvotes: 2

Ashvin solanki
Ashvin solanki

Reputation: 4809

The Problem is When You Passed Radius According to Issue https://github.com/firebase/geofire-java/issues/72

    double radius = 8589; // Fails
//  double radius = 8587.8; //Passes

try to pass value like this this may helps

//GeoQuery geoQuery = geoFire.queryAtLocation(geoLocation, 8f);
GeoQuery geoQuery = geoFire.queryAtLocation(geoLocation, radius);

passing value 8f (float) as the radius, while the the radius should rather be 8.0d or Double.valueOf(8.0), where MAX_SUPPORTED_RADIUS equals 8587 kilometers.

Upvotes: 2

Related Questions