GeoFire query on User location
For GeoFire you must keep a segment in the tree that contains just the geolocation and then you have another segment in the tree that contains the other information about the items.
user_locations
uid1:
g: xxxx
l:
0: xxx
1: xxx
uid2:
g: xxxx
l:
0: xxx
1: xxx
user_profiles:
uid1:
name: "giacavicchioli"
uid2:
name: "Frank van Puffelen"
Also see: Query firebase data linked to GeoFire
Query Location by Radius with GeoFire
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 eventPlaces)
var firebaseRef = firebase.database().ref('eventPlaces');
var geoFire = new GeoFire(firebaseRef);
Now you can use it as an index for your events, and can add items to it like so.
geoFire.set('-K_Pp-3RBJ58VkHGsL5P', [40.607765, -73.758949]);
Your Firebase RTDB will look like this now:
{
'events': {
'-K_Pp-3RBJ58VkHGsL5P': {
// All your data here
}
},
'eventPlaces': {
'-K_Pp-3RBJ58VkHGsL5P': {
'g': 'dr5x186m7u',
'l': [40.607765, -73.758949]
}
}
}
So finally when you do a query on your geoFire
:
geoFire.query({
center: [40.607765, -73.758949],
radius: 10
}).on('key_entered', (key, location, distance) => {
console.log(key + ' entered query at ' + location + ' (' + distance + ' km from center)');
});
You'll end up being returned the key of the doc, for which you can do a normal Firebase query for that individual doc.
Retrieving list of users only within 3 miles using geofire
I solved it with the Help of answers from Frank Van Puffelen. Here's what I did
public void onKeyEntered(final String key, GeoLocation location) {
firebaseDatabase.child(key).addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
Users my=dataSnapshot.getValue(Users.class);
list.add(my);
adapter=new MyAdapter(MainActivity.this,list);
recycler.setAdapter(adapter);
}
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
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
}
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
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
}
}
how can i count the available user in specific loaction radius with geofire query?
The onDataEntered
method is called for each node that is within the range. Once onDataEntered
has been called for all nodes that are initially in range, the onGeoQueryReady
method is called. That means that you can increment a counter in onDataEntered
and then show it in onGeoQueryReady
.
long count = 0;
geoQuery.addGeoQueryDataEventListener(new GeoQueryDataEventListener() {
@Override
public void onDataEntered(DataSnapshot dataSnapshot, GeoLocation location) {
count++;
}
@Override
public void onDataExited(DataSnapshot dataSnapshot) { }
@Override
public void onDataMoved(DataSnapshot dataSnapshot, GeoLocation location) { }
@Override
public void onDataChanged(DataSnapshot dataSnapshot, GeoLocation location) { }
@Override
public void onGeoQueryReady() {
ads5_userCounter.setText(""+count);
}
@Override
public void onGeoQueryError(DatabaseError error) {
throw databaseError.toException(); // don't ignore errors
}
});
Related Topics
iOS 11 - Unable to Change Navigation Bar Height
Declaring Global Variables in Swift
Document or Cache Path Changes on Every Launch in iOS 8
iOS Uiwebview Crash in "Webthread"
Auto-Implement Swift Protocol Methods in Xcode
How to Add a Custom Separator to Uitableviewcell
How to Switch to Speaker Output When Bluetooth Headsets Are Connected
How to Monitor Battery Level and State Changes Using Swift
How to Change the Height of Uitextfield in Uialertcontroller in Swift
How to Take Uiimage of Avcapturevideopreviewlayer Instead of Avcapturephotooutput Capture
Could Not Cast Value of Type 'Uiview' (0X112484Eb0) to 'Skview' (0X111646718)
Pass Uicollectionview Touch Event to Its Parent Uitableviewcell
How to Default Uilabel Font and Size Using Swift
Uiactivityviewcontroller Completion Handler Returns Success When Tweet Has Failed