GeoFire Firebase Swift in realtime - swift

Please how can make it in real time ?
like adding addSnapshotListener.
This code is only work when it called, I wanna if you change in FireBase, the reflect will be on the frontend.
reference >>> https://firebase.google.com/docs/firestore/solutions/geoqueries
// Find cities within 50km of London
let center = CLLocationCoordinate2D(latitude: 51.5074, longitude: 0.1278)
let radiusInM: Double = 50 * 1000
// Each item in 'bounds' represents a startAt/endAt pair. We have to issue
// a separate query for each pair. There can be up to 9 pairs of bounds
// depending on overlap, but in most cases there are 4.
let queryBounds = GFUtils.queryBounds(forLocation: center,
withRadius: radiusInM)
let queries = queryBounds.map { bound -> Query in
return db.collection("cities")
.order(by: "geohash")
.start(at: [bound.startValue])
.end(at: [bound.endValue])
}
var matchingDocs = [QueryDocumentSnapshot]()
// Collect all the query results together into a single list
func getDocumentsCompletion(snapshot: QuerySnapshot?, error: Error?) -> () {
guard let documents = snapshot?.documents else {
print("Unable to fetch snapshot data. \(String(describing: error))")
return
}
for document in documents {
let lat = document.data()["lat"] as? Double ?? 0
let lng = document.data()["lng"] as? Double ?? 0
let coordinates = CLLocation(latitude: lat, longitude: lng)
let centerPoint = CLLocation(latitude: center.latitude, longitude: center.longitude)
// We have to filter out a few false positives due to GeoHash accuracy, but
// most will match
let distance = GFUtils.distance(from: centerPoint, to: coordinates)
if distance <= radiusInM {
matchingDocs.append(document)
}
}
}
// After all callbacks have executed, matchingDocs contains the result. Note that this
// sample does not demonstrate how to wait on all callbacks to complete.
for query in queries {
query.getDocuments(completion: getDocumentsCompletion)
}
Thanks

If you want to listen for realtime updates, you can use .addSnapshotListener instead of getDocuments. Since this is not directly related to the geoquery, the solution documents the simpler approach - but the code for geoquerying would work exactly the same.

Related

Firebase GeoFire closure

I'm trying Firebase GeoFire and closures always trip me up. Firebase provides this sample code snippet in their documentation
// After all callbacks have executed, matchingDocs contains the result. Note that this
// sample does not demonstrate how to wait on all callbacks to complete.
for query in queries {
query.getDocuments(completion: getDocumentsCompletion)
}
Unfortunately it doesn't demonstrate just what I need...
I want this to fire on pre set interval so I have a scheduled it like this:
locationTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(getLocations), userInfo: nil, repeats: true)
Then my code is pretty much as per the sample:
var matchingDocs = [QueryDocumentSnapshot]()
#objc func getLocations() {
let center = CLLocationCoordinate2D(latitude: lat, longitude: lon)
let radiusInM: Double = 10000
// Each item in 'bounds' represents a startAt/endAt pair. We have to issue
// a separate query for each pair. There can be up to 9 pairs of bounds
// depending on overlap, but in most cases there are 4.
let queryBounds = GFUtils.queryBounds(forLocation: center,
withRadius: radiusInM)
let queries = queryBounds.map { bound -> Query in
return db.collection("geopoints")
.order(by: "geohash")
}
// Collect all the query results together into a single list
func getDocumentsCompletion(snapshot: QuerySnapshot?, error: Error?) -> () {
guard let documents = snapshot?.documents else {
print("Unable to fetch snapshot data. \(String(describing: error))")
return
}
for document in documents {
let lat = document.data()["lat"] as? Double ?? 0
let lng = document.data()["lng"] as? Double ?? 0
let coordinates = CLLocation(latitude: lat, longitude: lng)
let centerPoint = CLLocation(latitude: center.latitude, longitude: center.longitude)
// We have to filter out a few false positives due to GeoHash accuracy, but most will match
let distance = GFUtils.distance(from: centerPoint, to: coordinates)
if distance <= radiusInM {
matchingDocs.append(document)
}
}
}
// After all callbacks have executed, matchingDocs contains the result. Note that this
// sample does not demonstrate how to wait on all callbacks to complete.
for query in queries {
query.getDocuments(completion: getDocumentsCompletion)
}
}
}
What is the proper way to wait for the callbacks to complete, and continue executing the program outside the getLocations() function with matchingDocs containing all the geopoints? Thanks for any recommendations.
This is my solution. Function allDone() with the completion will be called only after all queries come back.
// button press to read data from Firebase
#IBAction func getData(_ sender: Any) {
getLocations(completion: {
// code here to process all query results
})
}
func getLocations(completion: #escaping () -> Void) {
let center = CLLocationCoordinate2D(latitude: lat, longitude: lon)
let radiusInM: Double = 10000
var queriesFinished = 0
// Each item in 'bounds' represents a startAt/endAt pair. We have to issue
// a separate query for each pair. There can be up to 9 pairs of bounds
// depending on overlap, but in most cases there are 4.
let queryBounds = GFUtils.queryBounds(forLocation: center,
withRadius: radiusInM)
let queries = queryBounds.map { bound -> Query in
return db.collection("geopoints")
.order(by: "geohash")
}
let numOfQueries = queries.count
// Collect all the query results together into a single list
func getDocumentsCompletion(snapshot: QuerySnapshot?, error: Error?) -> () {
guard let documents = snapshot?.documents else {
print("Unable to fetch snapshot data. \(String(describing: error))")
return
}
for document in documents {
let lat = document.data()["lat"] as? Double ?? 0
let lng = document.data()["lng"] as? Double ?? 0
let coordinates = CLLocation(latitude: lat, longitude: lng)
let centerPoint = CLLocation(latitude: center.latitude, longitude: center.longitude)
// We have to filter out a few false positives due to GeoHash accuracy, but most will match
let distance = GFUtils.distance(from: centerPoint, to: coordinates)
if distance <= radiusInM {
matchingDocs.append(document)
}
}
queriesFinished += 1
allDone()
}
// After all callbacks have executed, matchingDocs contains the result. Note that this
// sample does not demonstrate how to wait on all callbacks to complete.
for query in queries {
query.getDocuments(completion:getDocumentsCompletion)
}
func allDone() {
if queriesFinished == numOfQueries {
completion()
}
}
}
I was reading that async/await may be a better way but did not get it to work.

How to get Documents out of an geo query?

I used this function for an geo query. But I don't known how to add the document from the query to an array. So I can display some Map Annotations with infos from an Firestore document. How should I change it?
func geoQuery() {
// [START fs_geo_query_hashes]
// Find cities within 50km of London
let center = CLLocationCoordinate2D(latitude: 51.5074, longitude: 0.1278)
let radiusInKilometers: Double = 50
// Each item in 'bounds' represents a startAt/endAt pair. We have to issue
// a separate query for each pair. There can be up to 9 pairs of bounds
// depending on overlap, but in most cases there are 4.
let queryBounds = GFUtils.queryBounds(forLocation: center,
withRadius: radiusInKilometers)
let queries = queryBounds.compactMap { (any) -> Query? in
guard let bound = any as? GFGeoQueryBounds else { return nil }
return db.collection("cities")
.order(by: "geohash")
.start(at: [bound.startValue])
.end(at: [bound.endValue])
}
var matchingDocs = [QueryDocumentSnapshot]()
// Collect all the query results together into a single list
func getDocumentsCompletion(snapshot: QuerySnapshot?, error: Error?) -> () {
guard let documents = snapshot?.documents else {
print("Unable to fetch snapshot data. \(String(describing: error))")
return
}
for document in documents {
let lat = document.data()["lat"] as? Double ?? 0
let lng = document.data()["lng"] as? Double ?? 0
let coordinates = CLLocation(latitude: lat, longitude: lng)
let centerPoint = CLLocation(latitude: center.latitude, longitude: center.longitude)
// We have to filter out a few false positives due to GeoHash accuracy, but
// most will match
let distance = GFUtils.distance(from: centerPoint, to: coordinates)
if distance <= radiusInKilometers {
matchingDocs.append(document)
}
}
}
// After all callbacks have executed, matchingDocs contains the result. Note that this
// sample does not demonstrate how to wait on all callbacks to complete.
for query in queries {
query.getDocuments(completion: getDocumentsCompletion)
}
// [END fs_geo_query_hashes]
}
https://firebase.google.com/docs/firestore/solutions/geoqueries?hl=en#swift_2 This is the Firebase documentary.
I don't know how your documents are structured or how your map is configured to display data (annotations versus regions, for example), but the general fix for your problem is to coordinate the loop of queries in your function and give them a completion handler. And to do that, we can use a Dispatch Group. In the completion handler of this group, you have an array of document snapshots which you need to loop through to get the data (from each document), construct the Pin, and add it to the map. There are a number of other steps involved here that I can't help you with since I don't know how your documents and map are configured but this will help you. That said, you could reduce this code a bit and make it more efficient but let's just go with the Firebase sample code you're using and get it working first.
struct Pin: Identifiable {
let id = UUID().uuidString
var location: MKCoordinateRegion
var name: String
var img: String
}
func geoQuery() {
// [START fs_geo_query_hashes]
// Find cities within 50km of London
let center = CLLocationCoordinate2D(latitude: 51.5074, longitude: 0.1278)
let radiusInKilometers: Double = 50
// Each item in 'bounds' represents a startAt/endAt pair. We have to issue
// a separate query for each pair. There can be up to 9 pairs of bounds
// depending on overlap, but in most cases there are 4.
let queryBounds = GFUtils.queryBounds(forLocation: center,
withRadius: radiusInKilometers)
let queries = queryBounds.compactMap { (Any) -> Query? in
guard let bound = Any as? GFGeoQueryBounds else { return nil }
return db.collection("cities")
.order(by: "geohash")
.start(at: [bound.startValue])
.end(at: [bound.endValue])
}
// Create a dispatch group outside of the query loop since each iteration of the loop
// performs an asynchronous task.
let dispatch = DispatchGroup()
var matchingDocs = [QueryDocumentSnapshot]()
// Collect all the query results together into a single list
func getDocumentsCompletion(snapshot: QuerySnapshot?, error: Error?) -> () {
guard let documents = snapshot?.documents else {
print("Unable to fetch snapshot data. \(String(describing: error))")
dispatch.leave() // leave the dispatch group when we exit this completion
return
}
for document in documents {
let lat = document.data()["lat"] as? Double ?? 0
let lng = document.data()["lng"] as? Double ?? 0
let name = document.data()["names"] as? String ?? "no name"
let coordinates = CLLocation(latitude: lat, longitude: lng)
let centerPoint = CLLocation(latitude: center.latitude, longitude: center.longitude)
// We have to filter out a few false positives due to GeoHash accuracy, but
// most will match
let distance = GFUtils.distance(from: centerPoint, to: coordinates)
if distance <= radiusInKilometers {
matchingDocs.append(document)
}
}
dispatch.leave() // leave the dispatch group when we exit this completion
}
// After all callbacks have executed, matchingDocs contains the result. Note that this
// sample does not demonstrate how to wait on all callbacks to complete.
for query in queries {
dispatch.enter() // enter the dispatch group on each iteration
query.getDocuments(completion: getDocumentsCompletion)
}
// [END fs_geo_query_hashes]
// This is the completion handler of the dispatch group. When all of the leave()
// calls equal the number of enter() calls, this notify function is called.
dispatch.notify(queue: .main) {
for doc in matchingDocs {
let lat = doc.data()["lat"] as? Double ?? 0
let lng = doc.data()["lng"] as? Double ?? 0
let name = doc.data()["names"] as? String ?? "no name"
let coordinates = CLLocation(latitude: lat, longitude: lng)
let region = MKCoordinateRegion(center: <#T##CLLocationCoordinate2D#>, latitudinalMeters: <#T##CLLocationDistance#>, longitudinalMeters: <#T##CLLocationDistance#>)
let pin = Pin(location: region, name: name, img: "someImg")
// Add pin to array and then to map or just add pin directly to map here.
}
}
}
Map(coordinateRegion: $region, interactionModes: .all, showsUserLocation: true, annotationItems: pvm.allPins) { pin in
MapAnnotation(coordinate: pin.location.coordinate) {
Image(pin.img)
}
}

How to get query firestore with geofirestore and get documents in swift

I tried the code below to fetch the location data filtered by nearby certain location.
but I don't know how to get documents.
FirebaseApp.configure()
let db = Firestore.firestore()
let geoFirestoreRef = db.collection("spot")
let geoFirestore = GeoFirestore(collectionRef: geoFirestoreRef)
// Query using CLLocation
let center = CLLocation(latitude: 35.681236, longitude: 139.767125)
// Query locations at [37.7832889, -122.4056973] with a radius of 100km
var circleQuery = geoFirestore.query(withCenter: center, radius: 100.0)
let _ = circleQuery.observe(.documentEntered, with: { (key, location) in
geoFirestoreRef.document(key!).getDocument { (document, error) in
if let document = document, document.exists {
print(document)
} else {
print("Document does not exist.")
}
}
print("The document with documentID '\(key)' entered the search area and is at location '\(location)'")
})
circleQuery
This variable just return GFSCircleQuery object.
I don't know how to fetch the actual documents filtered by query.
When you have a query in Firebase, you can call .getDocuments or similar. Firebase has really detailed documentation online that you should probably start with. https://firebase.google.com/docs

Reading a coordinate from the cloud firestore continously and updating the point on the map as the coordinate changes - Swift, apple mapkit

Problem statement:
I am currently working on an app that will track all the buses in my college. Each bus is equipped with a GPS module that sends updated coordinate along with heading to the cloud fireStore every 5 seconds. On the application side I want to read that updated coordinate whenever it changes.
Cloud firestore:
busCurrentLoc (shown in the image) has different bus numbers in it and each bus number has their respective longitude and latitude along with heading.
Application side:
The user is provided a screen where he can select which bus he wants to track. When he selects the bus number he is shown the Map screen where the bus's real time location is updated every 5 seconds. I have already done the selection process. The Real-Time tracking is the issue.
I have included necessary comments about each and every variable in the code below. The problem i am facing is updating the currentLocation variable whenever there is a change in the cloud firestore.
Code:
//The mapview outlet
#IBOutlet var mapView: MKMapView!
//The variable which stores the current coordinates of the bus from cloud firebase
var currentLocation = [String: CLLocationCoordinate2D]()
//This variable is assigned from the previous ViewController during segue.
var selectedNum: String
let db = Firestore.firestore()
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
//Function to get the coordinate from cloud firestore
getCoordinates()
}
//MARK: - Get coordinates
/**
Function to get coordinates from the Cloud firestore and store it in the global "currentLocation" variable.
*/
func getCoordinates() {
db.collection("busCurrentLoc").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
if document.documentID == self.selectedNumber {
let latitude = document.data()["latitude"]
let longitude = document.data()["longitude"]
let coord: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: latitude as! CLLocationDegrees, longitude: longitude as! CLLocationDegrees)
self.currentLocation = ["Location": coord]
// prints : ["Location": __C.CLLocationCoordinate2D(latitude: 12.89307834, longitude: 80.1411217)]
print(self.currentLocation)
self.DrawBus()
}
}
}
}
//This function should draw a bus/annotation on the currentLocation.
DrawBus(at: currentLocation)
}
//MARK: - Draw bus at currentLocation
/**
Function to Draw a bus at currentLocation.
- Parameters:
currentLocation
*/
func drawBus(coordinate at: [String: CLLocationCoordinate2D]()) {
let annotations = MKPointAnnotation()
annotations.title = "Bus Location"
annotations.coordinate = coordinate["Location"] ?? CLLocationCoordinate2D(latitude: 0, longitude: 0)
mapView.addAnnotation(annotations)
}
The annotation is added to the screen and it is displayed. But when i try changing the coordinate in the Cloud firestore the coordinate doesn't change or the annotation is not getting updated. Kindly help me solve this issue.
I managed to solve this issue by using addSnapshotListener instead of getDocument
db.collection("busCurrentLoc").addSnapshotListener{ (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
if document.documentID == self.selectedNumber {
let latitude = document.data()["latitude"]
let longitude = document.data()["longitude"]
self.currentHeading = document.data()["heading"] as! CGFloat
let coord: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: latitude as! CLLocationDegrees, longitude: longitude as! CLLocationDegrees)
self.currentLocation = ["Location": coord]
print(self.currentLocation)
self.DrawBus()
}
}
}
}

Calculate each distance from user to several points by route in Swift

I'm a newbie in swift and I'm trying to calculate the distance, on route, from userLocation to several Point of interest.
I don’t want, in this part of the app, “draw” the route on a map, but I only want to calculate the distance on route instead the distance between two coordinate and then show this distance as information inside the callout on the map.
The coordinate of the POI (latitude and longitude) are contained in a database.
I have read some threads about this argument here on Stack Overflow:
Measuring distance in meters from a drawn route in MKMapView
MKMapView get distance between coordinates on customized route
and other tutorials:
http://www.technetexperts.com/mobile/draw-route-between-2-points-on-map-with-ios7-mapkit-api/
https://videos.raywenderlich.com/courses/mapkit-and-core-location/lessons/9
then i wrote this code:
for item in items {
if item.latitudine != "" && item.longitudine != "" {
// Point Of Interest coordinate
let latitude = Double(item.latitude!)
let longitude = Double(item.longitude!)
let itemLocation = CLLocation(latitude: latitude!, longitude: longitude!)
let itemLocationPlacemark = MKPlacemark(coordinate: itemLocation.coordinate, addressDictionary: nil)
// user coordinate
let userLocation = CLLocation(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude)
let userLocationPlacemark = MKPlacemark(coordinate: userLocation.coordinate, addressDictionary: nil)
// create Request object
let request = MKDirectionsRequest()
request.source = MKMapItem(placemark: userLocationPlacemark)
request.destination = MKMapItem(placemark: itemLocationPlacemark)
request.requestsAlternateRoutes = false
request.transportType = .automobile
let directions = MKDirections(request: request)
directions.calculate {
[weak self] (response, error) in
if error == nil {
for route in (response?.routes)! {
let distance = (route.distance)/1000
print(distance)
}
}
}
}
}
The problem is when I execute the code from the line directions.calculate.
The program run the line but then don’t execute the rest, don’t execute the control if error == nil and the instructions in the closure.
So now I wonder if my idea is wrong and, if not, how can obtain my goal.
(Posted solution on behalf of the OP).
Reading other threads I understand that the problem was the closure inside che for loop. So after several attempts I found the solution that work for me. I write it here so that can be useful to someone else:
var counter: Int!
...
for item in itemPin {
if item.latitude != "" && item.longitude != "" {
....
....
let directions = MKDirections(request: request)
directions.calculate {
(response, error) in
print("error \(error)")
if error == nil {
counter = self.counter + 1
print(self.counter)
if self.counter >= self.itemPin.count {
let result = (response?.routes[0].distance)!/1000
}
}
}
...
}
}