How to Get User Nearby My Location in Geofire,Firebase

Can't get near Users by my location with Firebase (GeoFire) and Swift

This answer depends on how you're user data is stored but if you're storing users with their documentId as the users uid then just

let uid = Auth.auth().currentUser!.uid //should safe unwrap that optional

then

geoFire.setLocation(CLLocation(..., forKey: uid)

however, if you want to get it as you're doing in the question

let uid = Auth.auth().currentUser!.uid
let usersCollection = Firestore.firestore().collection("users")
.whereField("uid", isEqualTo: uid)

usersCollection.getDocument(completion: { documentSnapshot, error in
if let err = error {
print(err.localizedDescription)
return
}

guard let docs = documentSnapshot?.documents else { return }

let thisUserUid = docs[0].documentID
print(thisUserUid)
})

But again, that's a bit redundant as it wound indicate storing the uid in both the documentId as well as a child field, which is unnecessary:

users
uid_0 //the documentId
name: "Users name"
uid: "uid_0" //not needed

The problem appers to be actually getting the center point - e.g. if it's not stored, then when read, it will be empty and you'll get that error.

So you have to store it to start with

geoFire.setLocation(CLLocation(latitude: 37.7853889, longitude: -122.4056973), forKey: uid) { (error) in
if (error != nil) {
print("An error occured: \(error)")
} else {
print("Saved location successfully!")
}
}

Once you successfully stored a center point (your location), and then retrieved it, then the actual query should be

let center = CLLocation(userLocation)
geoFire.queryAtLocation(center, withRadius: 20);

Oh, and a very important thing is the setLocation is asynchronous; it takes time for the server to store the data. You should really be working with the data within the closure

geoFire.setLocation(CLLocation(latitude: 37.7853889, longitude: -122.4056973), forKey: uid) { (error) in
// data is now valid so perform your query
}

How can i get user nearby my location in geofire,Firebase

Keep your GeoFire Locations separate from everything with a key to reference the other additional data e.g. user info or post info. As mixing static and dynamic data would not be the most efficient way of storing the data.

See my data structure and query here:

Retrieving Keys From GeoFire within Radius in Swift

How can I retrieve nearby bakeries to users using firebase

This is how you initialize GeoFire:

firebaseDatabase=FirebaseDatabase.getInstance().getReference("Users");
GeoFire geoFire=new GeoFire(firebaseDatabase.child("latandlong"));

This means that in your database you're expected to have a node /Users/latandlong, under which you have a node with the geohash and lat/lon for each key you want to track. So:

Users: {
latandlong: {
"IEvLcwll....SQkbp2": {
g: "..."
l: { 0: ..., 1: ... }
}
}
}

This structure is different from what's in the screenshot you shared, as you are missing the key level: "IEvLcwll....SQkbp2". This explains why any geoquery you fire on geoFire won't find any keys.

I highly recommend reading the GeoFire documentation on:

  1. How to initialize the GeoFire object, which you'll typically want to point to a top-level node.
  2. Then set the location data for a specific key, which will then become a child node under the top-level node you set in the previous step.

Only once you've done these two steps correctly, will you be able to execute a geoquery against the data.

Find nearby users in Firebase Realtime DB

The fastest and best way I found was using GeoFIre.
In your onCreate() method add the following lines

DatabaseReference ref = FirebaseDatabase.getInstance().getReference("path/to/geofire");
GeoFire geoFire = new GeoFire(ref);

Provided we have our location, then we can simply use GeoQueries in a method in order to get the result we want.

// creates a new query around [latitude, longitude] with a radius of 10.00 kilometers
GeoQuery geoQuery = geoFire.queryAtLocation(new GeoLocation(Double.parseDouble(latitude), Double.parseDouble(longitude)), 10.00);
geoQuery.addGeoQueryEventListener(new GeoQueryEventListener() {
@Override
public void onKeyEntered(String key, GeoLocation location) {
Log.d("KEY", "KEY IS: " + key);
if (!key.equals(currentUserID) && !nearbyList.contains(key)) {
Log.d("KEY FOUND", "Key " + key + " entered the search area at" + location.latitude + "," + location.longitude);

usersRef.child(key).addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
/* Get your data from Firebase here */
}

@Override
public void onCancelled(@NonNull DatabaseError error) {

}
});
}
}
}

@Override
public void onKeyExited(String key) {
/* When someone exits the radius */
}

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

}

@Override
public void onGeoQueryReady() {

}

@Override
public void onGeoQueryError(DatabaseError error) {

}
});

Firebase cloud functions find nearby locations

Using the geofire library you could do something like this...

exports.cloudFuncion = functions.https.onRequest((request, response) => {
// logic to parse out coordinates
const results = [];
const geofireQuery = new GeoFire(admin.database().ref('geofireDatabase')).query({
center: [coordinates.lat, coordinates.lng],
radius: 15 // Whatever radius you want in meters
})
.on('key_entered', (key, coords, distance) => {
// Geofire only provides an index to query.
// We'll need to fetch the original object as well
admin.database().ref('regularDatabase/' + key).on('value', (snapshot) => {
let result = snapshot.val();
// Attach the distance so we can sort it later
result['distance'] = distance;
results.push(result);
});
});

// Depending on how many locations you have this could fire for a while.
// We'll set a timeout of 3 seconds to force a quick response
setTimeout(() => {
geofireQuery.cancel(); // Cancel the query
if (results.length === 0) {
response('Nothing nearby found...');
} else {
results.sort((a, b) => a.distance - b.distance); // Sort the query by distance
response(result);
}
}, 3000);
});

If you're not sure how to use geofire though I'd recommend looking at this post I made which will explain a lot of how geofire works and how to use it/

Swift Firebase -How to get individual user's location from GeoFire

Inside the Database the GeoFire ref of each userId location has a "g" child and a "l" child:

@geoLocations
|
@--abc123xyz456 // userId
|
@--g: "dr72xyz25abc" // geoFire id for this user's location in geoFire
|
@--l
|--0: 40.870431300779900 // latitude
|--1: -73.090007211987188 // longitude

Here's a picture of the actual database layout

Sample Image

I have no idea what "g" stands for but I assume "l" stands for location because it's of type CLLocation as is stated in the arguments of .observe(.keyEntered, with: { (key: String!, location: CLLocation!).

Inside the database the 0 key and 1 are held as snapshot.value of array of either CLLocationDegrees or Double.

To get the latitude and longitude I used let arr = snapshot.value as? [CLLocationDegrees] but let arr = snapshot.value as? [Double] also worked.

Create a ref that has a child name of whatever the name of your geoLocations ref is > then add a child of the the userId > then add a child of "l" for the locations child.

Run observeSingleEvent(of: .value and in the callback cast snapshot.value as an an array of [CLLocationDegrees]

// *** if using CLLocationDegrees be to import CoreLocation ***
import CoreLocation

let geoLocationsRef = Database.database().reference()
.child("geoLocations") // name of my geoRef in Firebase
.child("abc123xyz456") // the userId I'm observing
.child("l") // the "l" is the child to observe

geoLocationsRef.observeSingleEvent(of: .value, with: { (snapshot) in

if !snapshot.exists() { return }

guard let arr = snapshot.value as? [CLLocationDegrees] else { return }

if arr.count > 1 {

let latitude = arr[0]
print(latitude)

let longitude = arr[1]
print(longitude)

// do whatever with the latitude and longitude
}
})

Here is the answer to my question with using dispatchGroup():

func queryLocationOfSubsetOfUsersInRadius() {

let dispatchGroup = DispatchGroup()

for user in subsetOfUsersInRadius {

dispatchGroup.enter()

let userId = user.userId

let geoLocationsRef = Database.database().reference()
.child("geoLocations")
.child(userId)
.child("l")

geoLocationsRef.observeSingleEvent(of: .value, with: { (snapshot) in

// this user may have deleted their location
if !snapshot.exists() {
dispatchGroup.leave()
return
}

guard let arr = snapshot.value as? [CLLocationDegrees] else {
dispatchGroup.leave()
return
}

if arr.count > 1 {

let latitude = arr[0]
print(latitude)

let longitude = arr[1]
print(longitude)

// do whatever with the latitude and longitude
}

dispatchGroup.leave()
})
}

dispatchGroup.notify(queue: .global(qos: .background)) {

// now animate the annotation from the user's inital old location (if they moved) on the mapView to their new location on the mapView. It's supposed to look like Uber's cars moving. Happens on main thread
}
}


Related Topics



Leave a reply



Submit