Reputation: 8291
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");
}
I think I'm open to modify the data struct if needed.
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'
....
I can grant access to my private repo if you wish.
Upvotes: 7
Views: 1374
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 relateduid
key for these nodes (or at least add it as a child-node), in order to be able to look up within the usersReference
. it's a1:1
relation, in between twoReference
s.
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
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
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