如何在不过滤客户端的情况下使用 geohashes 在 x 英里半径内查找文档?

How to find documents in an x mile radius using geohashes without filtering on client?

所以目前我正在使用 geohashes 来进行基于位置的查询(遵循这个 Whosebug post:Finding geohashes of certain length within radius from a point

public 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
    }

}
func getGeohashPrefix(){
        let loc = CLLocationCoordinate2D(latitude: lat!, longitude: long!)
        MBR = loc.boundingBox(radius: 16093.4) //16093.4 meters = 10 miles
        //corners = [NorthWest, SouthWest, SouthEast, NorthEast] in lat n long
        let corners = [CLLocationCoordinate2D(latitude: MBR.0.latitude,longitude: MBR.1.longitude),
                       MBR.1, CLLocationCoordinate2D(latitude: MBR.1.latitude, longitude: MBR.0.longitude),
                        MBR.0]
        var geohashes_of_corners: [String] = []
        for corner in corners {
            geohashes_of_corners.append(corner.geohash(length: 12))
        }
        geohashes_prefix = geohashes_of_corners.longestCommonPrefix()

    }

var query: Query = db.collection("Users").whereField("geohash",isGreaterThanOrEqualTo: geohashes_prefix).whereField("geohash",isLessThanOrEqualTo: geohashes_prefix + "~").order(by: "geohash", descending: false)

       query.getDocuments { (querySnapshot, err) in
           if err != nil{
               print("error getting da documents")
           }else{
                if querySnapshot!.isEmpty{
                    return completion(arr_of_people)
                }
                for document in querySnapshot!.documents {
                    let d = document.data()
                    let isPersonWithin = CLLocationCoordinate2D(latitude: (d["loc"] as! GeoPoint).latitude, longitude: (d["loc"] as! GeoPoint).longitude).isWithin(min: self.MBR.1, max: self.MBR.0)
                  if !isPersonWithin{
                         continue
                 }
 
                    arr_of_people.append([d["firstName"] as! String,  d["lastName"] as! String])
                   }

               return completion(arr_of_people)
           }
       }

如您所见,我正在查询具有特定前缀的文档,然后在客户端再次过滤这些文档。那安全吗?如果没有,解决方法是什么?使用云函数、不同的算法(如果有的话,建议一个)或其他什么?

对 geohashes 的查询 returns 指向一定范围内的 geohashes,这些(有点)矩形区域。

关于中心点和距离 returns 个圆上的点的地理查询。

由于这两个形状不同,您的代码使用 client-side 检查来切断圆外但在矩形内的点。当使用 geohashes 对一个点周围的最大距离执行地理查询时,这是正常步骤。


这是地图上的示例:

绿色图钉在旧金山周围250公里的圆圈内,这是我查询的。红色图钉在该圆圈之外,但在需要查询的一组 geohash 范围内(此处为 [["9q0","9qh"],["9nh","9n~"],["9r0","9rh"],["9ph","9p~"]]),以确保所有点都在范围内。

如前所述:这种 so-called 过采样是使用 geohashes 执行 point-and-distance 查询所固有的。根据我的经验,您最终会阅读 2 到 8 倍的文档。

可能可以通过查询 more-but-smaller 范围来减少误报的数量,但我不知道任何 geo-libraries这样做的 Firestore。

我将额外成本映射为心智模型:在距某个点一定距离内查找文档比阅读常规文档花费的成本高 2 到 8 倍。


将操作移动到服务器上的 Cloud Functions 对需要读取的文档数量没有影响,只是改变了读取位置。因此,您可以在服务器上执行操作以减少将文档从数据库传输到客户端的带宽。但它不会对需要阅读的文档数量产生影响。

如评论中所述:在服务器上执行查询 是否 允许您从数据中删除 client-side 访问权限,以便您可以确保应用程序代码将永远不会看到不在请求范围内的文档。因此,如果您担心文档访问,在受信任的环境(例如您控制的服务器或 Cloud Functions)中执行查询是一个不错的选择。


为了不为额外的文档读取付费,请考虑寻找一种本机支持地理查询的服务,其定价模型基于它 returns 的结果数量 - 而不是它必须考虑的结果数量。这样的服务(很可能)仍然考虑太多点,但如果定价模型符合您的需求,那可能是值得的。