import CoreLocation extension CLLocationCoordinate2D { func boundingBox(radius: CLLocationDistance) -> (max: CLLocationCoordinate2D, min: CLLocationCoordinate2D) { // 0.0000089982311916 ~= 1m let offset = 0.0000089982311916 * radius let latMax = self.latitude + offset let latMin = self.latitude - offset // 1 degree of longitude = 111km only at equator // (gradually shrinks to zero at the poles) // So need to take into account latitude too let lngOffset = offset * cos(self.latitude * .pi / 180.0); let lngMax = self.longitude + lngOffset; let lngMin = self.longitude - lngOffset; let max = CLLocationCoordinate2D(latitude: latMax, longitude: lngMax) let min = CLLocationCoordinate2D(latitude: latMin, longitude: lngMin) return (max, min) } func isWithin(min: CLLocationCoordinate2D, max: CLLocationCoordinate2D) -> Bool { return self.latitude > min.latitude && self.latitude < max.latitude && self.longitude > min.longitude && self.longitude < max.longitude } } private func convertFirestoreSpots(_ firestoreSpots: [FirestoreSpot]?, currentLocation: CLLocationCoordinate2D) -> [(Spot, CLLocationDistance)] { guard let firestoreSpots = firestoreSpots else { return [] } let spotsWithDistance: [(Spot, CLLocationDistance)] = firestoreSpots.flatMap { firestoreSpot in guard let spot = Spot(firestoreSpot) else { return nil } return ( spot, currentLocation.distance(to: firestoreSpot.location.coordinate) ) } return spotsWithDistance.sorted { $0.1 < $1.1 } } let location = CLLocationCoordinate2D(latitude: 10.0, longitude: 20.0) let (maxCoord, minCoord) = location.boundingBox(radius: 100) let maxPoint = GeoPoint( latitude: maxCoord.latitude, longitude: maxCoord.longitude ) let minPoint = GeoPoint( latitude: minCoord.latitude, longitude: minCoord.longitude ) let locationField = "location" let query = self.collectionReference .whereField(locationField, isGreaterThan: minPoint) .whereField(locationField, isLessThan: maxPoint) .limit(to: 20) // getModels will run the query and return the results. return query.getModels(FirestoreSpot.self).then({ (firestoreSpots) -> [(Spot, CLLocationDistance)] in guard let firestoreSpots = firestoreSpots else { return [(Spot, CLLocationDistance)]() } // As the results are only filterd by latitude, we will have to filter them again let filtered = firestoreSpots.filter { firestoreSpot in return firestoreSpot.location.coordinate.isWithin( min: minCoord, max: maxCoord ) } let sorted = self.convertFirestoreSpots( filtered, currentLocation: location ) let limited = Array(sorted.prefix(maxSpotsNumber)) return limited })