Algolia sorting by geolocation in Swift - swift

I have a problem with Algolia geolocation sorting in iOS App.
I need to display all documents in a range of 100 km from the user location.
I have multiple documents in my Indice. The document looks like this:
"document": {
"price": 5000,
"unit": "cały projekt",
"_geoloc": {
"lat": 54.5,
"lng": 18.55
},
"title": "Test ogloszenia",
"range": 0,
"activeFrom": {
"_seconds": 1597042800,
"_nanoseconds": 0
}
}
In my Algolia Ranking and Sorting, I have default set GEO.
My Swift code for sorting locations looks like below:
func getAnnouncesLocation(location: CLLocationCoordinate2D, completion: #escaping ([Announcement]) -> ()) {
var announcementsArray = [Announcement]()
let query = Query(query: "")
query.aroundLatLng = LatLng(lat: location.latitude, lng: location.longitude)
query.aroundRadius = .explicit(100000) // 100 km
collectionIndex = searchClient.index(withName: "products_geolocation")
collectionIndex.search(query) { (content, error) in
guard let content = content else {
if let error = error {
print(error.localizedDescription)
}
return
}
print("HITS \(content)")
}
}
The code doesn't return any error but the content is empty.
Another sorting like by price works perfectly. The only problem is with geolocation.
If that can help to set sorting by the price I need to add a sort-by attribute like this: document.price in Dashboard.
I am saving data to Algolia from my server in Node.js and there I am creating _geoloc value.
The latitude and longitude are hardcoded for testing so there isn’t a problem with async.
Thank you for any kind of help.
Regards
Matt

I found a bug in my project.
While sending the document to Algolia I send like this:
const record = {
objectID: doc.id,
document: document
};
where document contained all properties and also _geoloc property. After some investigation, I separated _geoloc from the document I sent it like below:
const record = {
objectID: doc.id,
document: document,
_geoloc: coordinates
};
Now everything works fine.
Cheers

Related

`find_one` does not give ObjectId in proper format

As the title states, here is the following code.
let users_coll = db
.database("foo")
.collection::<bson::oid::ObjectId>("users");
let user_id = users_coll
.find_one(
doc! { "email": &account.email },
mongodb::options::FindOneOptions::builder()
.projection(doc! { "_id": 1i32 })
.build(),
)
.await?
.unwrap();
But it fails at ? operator with the following mongodb::error::Error,
Error { kind: BsonDeserialization(DeserializationError { message: "expected map containing extended-JSON formatted ObjectId, instead found { \"_id\": ObjectId(\"62af199df4a16d3ea6056536\") }" }), labels: {}, wire_version: None, source: None }
And it is right. Given ObjectId should be in this format,
{
"_id": {
"$oid": "62af199df4a16d3ea6056536"
}
}
But I do not know how to handle this. Any help is appreciated.
Have a good day!
Your users collection isn't a collection of ObjectIds, it's actually a collection of documents which each contain an ObjectId. To let Rust know what to do with those, you should create a struct which represents the document, or at least the parts which you care about getting back from your query, and tell your collection to de-serialize into that struct:
use mongodb::bson::oid::ObjectId;
use serde::{Serialize, Deserialize};
#[derive(Debug, Default, Serialize, Deserialize)]
struct User {
_id: ObjectId,
}
#[tokio::main]
async fn main() {
let users_coll = db
.database("foo")
.collection::<User>("users");
let user_id: ObjectId = users_coll
.find_one(
doc! { "email": &account.email },
mongodb::options::FindOneOptions::builder()
.projection(doc! { "_id": 1i32 })
.build(),
)
.await?
.unwrap()
._id;
}
By default, the BSON fields have to match the struct fields exactly (_id in this case), but I'm pretty sure serde has a way to change that if you don't like the leading underscore.

Instead of running a snapshot for all users, how do you set up multiple queries to limit the number of users sent to the device?

What I have: A snapshot of all users with a bunch of if statements that eventually returns an array of users that get displayed.
What I need: The array of end users to be used in a .query in the line preceding the snapshot.
Why do I need this: This line is so that the entire database of users is not run on the client.
More specifically, what do I need to query for: A) Users who have a child "caption"(timestamp) with a timestamp that is in today, AND, B) who are 3000 miles from the current user.
JSON of DB
"people" : {
"02PdiNpmW3MMyJt3qPuRyTpHLaw2" : {
"Coordinates" : {
"latitude" : -25.809620667034363,
"longitude" : 28.321706241781342
},
"PhotoPosts" : "https://firebasestorage.googleapis.com/v0/b/daylike-2f938.appspot.com/o/images%2F02PdiNpmW3MMyJt3qPuRyTpHLaw2%2FPhotoPosts?alt=media&token=24fee778-bcda-44e3-aa26-d7c2f8509740",
"caption" : 1602596281762, /// this is timestamp
"postID" : "02PdiNpmW3MMyJt3qPuRyTpHLaw2"
},
"e1" : “cvvvv666",
"e2" : "aol.com",
" "postID" : "0RnqWV7Gd9Z0bUW9nUvizMQOjK73",
"users" : "cvvvv666#aol.com"
},
.
var dict = CLLocation()
...
dict = CLLocation(latitude: lat, longitude: lon)
...
let thisUsersUid = Auth.auth().currentUser?.uid
//this line below is where the refArtists2 query should go. in other words send all users to device that meet the 2 if statements, which is represented by self.people.append(peopl)//
let refArtists2 = Database.database().reference().child("people").queryOrdered(byChild: "caption").queryEqual(toValue: ANY Timestamp in today).queryOrdered(byChild:Coordinates). queryEqual(toValue:ThoseCoordinates which make the distance to current user less than 3000 miles)
refArtists2.observe(DataEventType.value, with: { snapshot in
if snapshot.childrenCount>0{
self.people.removeAll()
for people in snapshot.children.allObjects as! [DataSnapshot] {
if people.key != thisUsersUid {
let peopleObject = people.value as? [String: AnyObject]
let peopleCoordinates = peopleObject?["Coordinates"] as? String
let peoplecaption = peopleObject?["caption"] as? Int //is timestamp
let peoplepostID = peopleObject?["postID"] as? String
let coordSnap = people.childSnapshot(forPath: "Coordinates")
guard let lat = coordSnap.childSnapshot(forPath: "latitude").value as? CLLocationDegrees else { return }
guard let lon = coordSnap.childSnapshot(forPath: "longitude").value as? CLLocationDegrees else { return }
let locCoord = CLLocation(latitude: lat, longitude: lon)
let coordSnap12 = people.childSnapshot(forPath: "caption").value as? Int ?? 0
let date = Date(timeIntervalSince1970: TimeInterval(coordSnap12)/1000.0)
//let secondsInDay = 86400
**if Calendar.current.isDateInToday(date)** {
let distance = locCoord.distance(from: self.dict)
print(distance, "distancexy")
**if distance/1609.344 < 3000**{
let peopl = Userx(Coordinates: peopleCoordinates, distance:distance, caption: peoplecaption, postID: peoplepostID)
self.people.append(peopl)
let d = people.key as! String
self.printPersonInfo(uid:d) ///////This is used to reload the data
} else {
print ("w")
}
} else {
print ("alphaaa")
}
}
print("aaaaaaaa", self.people.map {$0.distance})
}
self.people.sort { ($0.distance ?? 0) < ($1.distance ?? 0) } ////////This sorting with distance is used with returning the cell. people is used as uid array to return the cell.
}
})
} else {
print("no")
}
})
Ancillary caveat: the self.people.sort { ($0.distance ?? 0) < ($1.distance ?? 0) }sorting is important, so the queries should not impede that. I am a bit concerned with using queryOrdered in that it orders the array of users in the wrong order. If it does, a C) query should be: The order of the users must be with the closest users to the logged in user first. The furthest from the logged in user must go last in the array.
Another way of asking this would be: Instead of running a snapshot of all users, how do you query the snapshot's 'end result sort' when making the snapshot?
The timestamp is seconds since 1970
My attempt at the date query below. I took the code and tried to put the code that gets the date before the actual query(currently the code that gets the date is after the snapshot of all users).
var ppp: String! ////this should be the uid of all users in db
let people = Database.database().reference().child("people").child(self.ppp).child("captions")
people.observe(DataEventType.value, with: { snapshot in
let captionss = snapshot.value as? Int ?? 0
let date = Date(timeIntervalSince1970: TimeInterval(captionss)/1000.0)
let query1 = Database.database().reference().child("people").queryOrdered(byChild: "caption").where?(isDateInToday(date))
Edit: This answer is in Firestore, not Realtime Database. However, the concepts are the same.
The question is several questions in one; asking about distance, compound queries and how to query Firebase in general. I will post this answer to address the second two and distance queries are addressed in the comment to the question.
Once the query pattern is understood, they become easier and most importantly; it becomes more obvious that how your data is structured depends on what queries you want to run against that data.
Suppose we have a users collection with user documents - each documentId is the users uid
users
uid_0
name: "Leroy"
and then we have the posts for the users - each post contains some text, a timestamp of the post, the uid of the user that posted it, what the topic is and a url of a picture that appears in the post. Notice I am storing posts in a separate collection; why read in a bunch of user data when we only want to know about their post.
posts
post_id
postText: "pretty flowers"
postDate: "20201103"
postUrl: "www....."
postUid: "uid_0"
postTopic: "flowers"
Let suppose we want to get posts from today that are about flowers, and then also get the posters name and output who posted the message and what they said.
To do this we will need a compound query and then a subquery to retrieve the posters name as well.
func getTodaysPostsAboutFlowers() {
let postsCollection = self.db.collection("posts")
let query = postsCollection.whereField("postDate", isEqualTo: "20201103").whereField("postTopic", isEqualTo: "flowers")
query.getDocuments(completion: { snapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
guard let docs = snapshot?.documents else { return }
for doc in docs {
let postText = doc.get("postText") as? String ?? "No text"
guard let postersUid = doc.get("postUid") as? String else { return }
self.outputPostTextAndUserName(withText: postText, andUid: postersUid)
}
})
}
The above performs a compound query on both the postDate field as the postTopic field.
The above then calls another function to retrieve the users name and output both the name and what they said
func outputPostTextAndUserName(withText: String, andUid: String) {
let usersCollection = self.db.collection("users")
let theUserDoc = usersCollection.document(andUid)
theUserDoc.getDocument(completion: { documentSnapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
if let doc = documentSnapshot {
let postersName = doc.get("name") as? String ?? "No Name"
print("\(postersName) posted: \(withText)")
}
})
}
and the output
Leroy posted: pretty flowers
As you can see, there's no need to load all of the users, no need to iterate over results etc. Even if you have a billion users, this will only return a subset of that data which is a best practice when working with huge data sets; only get the data you're interested in.
Edit. The OP is asking about querying for nodes containing today. The simple solution is to have one child node containing a timestamp which would contains specific date data and then another child node just containing today data in YYYYMMDD format.
people
uid_x
timetamps: 9023490823498 //Date(timeIntervalSince1970:
todaystamp: "20201106" // yyyymmdd format
that makes querying for nodes that contain today very simple.

How to nest data in Firebase Realtime database (Swift)

My database in Firebase currently looks like this:
locations: {
person: ""
location: ""
}
How do I nest the data so that it looks something like this:
locations: {
person: "" {
location: ""
}
}
A structure where locations is the parent, person is the child, and location is the child of person.
I know the relationships, I just am not too sure of the syntax.
Here is the code I currently have, written in Swift:
let locRef = locationRef.childByAutoId()
let locItem = [
"location": getLocation(),
"person": senderId
]
locRef.setValue(locItem)
Thanks.
let locRef = locationRef.childByAutoId()
let locItem = [
senderId : [
"location": getLocationID()
]
]
SenderID will be the key for the person, so you can find the Person by its ID... And the ID is holding a nested location key
Do you only need the logic? Or do you need working code example?

How to sort JSON coming from Alamofire and return final JSON object (swiftyJSON)

I'm having trouble succinctly pulling data from an api, adding the users current location into the object and then sorting the data based on the calculated distance.
The stackoverflow questions don't quite answer the problem I'm facing. See here: How to sort posts read from JSON server file in Swift.
I'm currently loading api data from Alamofire and rendering that data with a UITableViewController.
override func viewDidLoad() {
super.viewDidLoad()
titleLabel.title = q.capitalizedString
Alamofire.request(.GET, "http://www.thesite.com/api/v1/things", parameters: ["q" : q])
.responseJSON { response in
let JSONObject = JSON(response.result.value!)
self.results = JSONObject["things"]
for (key, _) in self.results {
let intKey: Int = Int(key)!
var thisItem: JSON = self.results[intKey]
let geoLat = thisItem["place"][0]["location"]["geo"][1].double ?? 37.763299
let geoLong = thisItem["place"][0]["location"]["geo"][0].double ?? -122.419356
let destination = CLLocation(latitude: geoLat, longitude: geoLong)
let setLocation = CLLocation(latitude: self.currentLocation.latitude, longitude: self.currentLocation.longitude)
let distanceBetween: CLLocationDistance = destination.distanceFromLocation(setLocation)
thisItem["distance"].double = distanceBetween
self.results[intKey] = thisItem
}
self.tableView.reloadData()
}
}
I get the data from the api and successfully add the distance between the user's location and the destination of the place.
However, now I need to sort the JSON object (SwiftyJSON) from lowest to highest distance. This is where I'm stuck.
the data structure at the point the tableView (as JSON object) is reloaded is essentially:
results = [
{"title": "Chai", "distance": "1245.678575"},
{"title": "Espresso", "distance": "765845.678575"},
{"title": "Drip Coffee", "distance": "23445.678575"}
...
]
How would I be able to either: 1) convert the object to NSArray and sort; or 2) just sort the object? When would be the best place to do the distance add and sort - should I do it before converting to the JSON object.
Any help is appreciated! Thanks!
If results is a SwiftyJSON object, you can extract its array with .arrayValue.
let resultsArray = results.arrayValue
Then once you have a normal array of dictionaries, you can then sort the array with sort like this:
let sortedResults = resultsArray.sort { $0["distance"].doubleValue < $1["distance"].doubleValue }
I took your JSON snippet:
And tested my answer in a Playground with SwiftyJSON:
If you wanted to, you could also sort the SwiftyJSON object directly:
let sortedResults = results.sort { $0.0.1["distance"].doubleValue < $0.1.1["distance"].doubleValue }.map { $0.1 }
But I find it less readable as source code.
Swift 3 & Swift 4
let sortedResults = finalJsonArray.sorted { $0["count"].doubleValue < $1["count"].doubleValue }
Swift 3 + Swift 4
Shortest solution I've found is below:
< is sorting ascending,
> is sorting descending
It is especially very efficient for sorting (String,JSON) format (when you work with SwiftyJSON)
let sortedResults = json.sorted(by: < )

How can I scrape the member count of Facebook groups into a Google Spreadsheet?

I'm trying to get the number of members of some Facebook groups. I tried to play with the Facebook Graph API but it does not work:
function facebook(url) {
var jsondata = UrlFetchApp.fetch('http://graph.facebook.com/http://www.facebook.com/groups/449592401822610/?ref=br_rs');
var object = Utilities.jsonParse(jsondata.getContentText());
return object.shares;
}
Is it possible to do that?
Thanks for your help
[UPDATE]
Sometimes I don't have the ID of the group. I wrote this to solve the issue but it does not work...
function facebook(group) {
if (isNaN(group) == true) {
var jsondata1 = UrlFetchApp.fetch('https://graph.facebook.com/search?q='+group+'&type=group&access_token={my token}');
var object1 = Utilities.jsonParse(jsondata1.getContentText());
var id = object1.data.id;
var jsondata2 = UrlFetchApp.fetch('https://graph.facebook.com/v2.5/'+id+'/members?summary=true&access_token={my token}');
var object2 = Utilities.jsonParse(jsondata2.getContentText());
return object2.summary.total_count;
}
else {
var jsondata = UrlFetchApp.fetch('https://graph.facebook.com/v2.5/'+group+'/members?summary=true&access_token={my token}');
var object = Utilities.jsonParse(jsondata.getContentText());
return object.summary.total_count;
}
}
Any idea?
Using Graph API
https://graph.facebook.com/v2.5/449592401822610/members?summary=true&access_token={user-access_token}
you will get response like this if it is closed group
{
"data": [
],
"summary": {
"total_count": 4113
}
}
and if it is public group you will also receive members detail in data section
Good Luck
NOTE: this will only return count <5000 member. If your group is near 5000 or more than 5000 it will return 4897, 4756, or some other "random" number, but will never return more than 5000.
It looks like the URL in your code is wrong, I'm guessing it should be
http://graph.facebook.com/groups/449592401822610/?ref=br_rs