如何使用 Firestore 运行地理“附近”查询?

Posted

技术标签:

【中文标题】如何使用 Firestore 运行地理“附近”查询?【英文标题】:How to run a geo "nearby" query with firestore? 【发布时间】:2018-03-19 17:19:54 【问题描述】:

来自 firebase 的新 firestore 数据库是否原生支持基于位置的地理查询?即查找 10 英里内的帖子,还是查找最近的 50 个帖子?

我看到有一些用于实时 Firebase 数据库的现有项目,比如 geofire 之类的项目——这些项目也可以适应 firestore 吗?

【问题讨论】:

How to query closest GeoPoints in a collection in Firebase Cloud Firestore?的可能重复 【参考方案1】:

到目前为止,还没有办法进行这样的查询。 SO中还有其他与之相关的问题:

Is there a way to use GeoFire with Firestore?

How to query closest GeoPoints in a collection in Firebase Cloud Firestore?

Is there a way to use GeoFire with Firestore?

在我当前的 android 项目中,我可能会使用 https://github.com/drfonfon/android-geohash 添加一个 geohash 字段,而 Firebase 团队正在开发原生支持。

像其他问题中建议的那样使用 Firebase 实时数据库意味着您无法同时按位置和其他字段过滤结果集,这是我首先要切换到 Firestore 的主要原因。

【讨论】:

【参考方案2】:

更新:Firestore 目前不支持实际的 GeoPoint 查询,因此当以下查询成功执行时,它仅按纬度过滤,而不是按经度过滤,因此将返回许多不在附近的结果。最好的解决方案是使用geohashes。要了解如何自己做类似的事情,请查看video。

这可以通过创建一个小于大于查询的边界框来完成。至于效率,我无话可说。

注意,大约 1 英里的纬度/经度偏移的准确性应该被检查,但这里有一个快速的方法来做到这一点:

SWIFT 3.0 版本

func getDocumentNearBy(latitude: Double, longitude: Double, distance: Double) 

    // ~1 mile of lat and lon in degrees
    let lat = 0.0144927536231884
    let lon = 0.0181818181818182

    let lowerLat = latitude - (lat * distance)
    let lowerLon = longitude - (lon * distance)

    let greaterLat = latitude + (lat * distance)
    let greaterLon = longitude + (lon * distance)

    let lesserGeopoint = GeoPoint(latitude: lowerLat, longitude: lowerLon)
    let greaterGeopoint = GeoPoint(latitude: greaterLat, longitude: greaterLon)

    let docRef = Firestore.firestore().collection("locations")
    let query = docRef.whereField("location", isGreaterThan: lesserGeopoint).whereField("location", isLessThan: greaterGeopoint)

    query.getDocuments  snapshot, error in
        if let error = error 
            print("Error getting documents: \(error)")
         else 
            for document in snapshot!.documents 
                print("\(document.documentID) => \(document.data())")
            
        
    



func run() 
    // Get all locations within 10 miles of Google Headquarters
    getDocumentNearBy(latitude: 37.422000, longitude: -122.084057, distance: 10)

【讨论】:

我们正在运行与您相同的查询,但我们得到的结果忽略了经度。所以我们得到的所有文档都在 lat 范围内,但不在 lon 范围内。 @winston 看看下面的链接。我希望它有所帮助,因为我有一个带有 Promises 的 Firecloud 自定义实现。我试图简化它。 gist.github.com/zirinisp/e5cf5d9c33cb0bd815993900618eafe0 @zirinisp 对于经度问题,我在集合中设置了一个额外的字段,它是一个虚拟经度图块。我只是做 Math.floor(longitude) 然后在查询时你可以做 .where("longitude_tile", "==", Math.floor(yourCurrentLongitude))。由于这是一个相等的运算符,您可以将它与位置范围一起使用,以在服务器而不是客户端上大大过滤结果。每个经度度数约为 69 英里,这足以满足我们的目的。 如果这个答案实际上没有解决问题,为什么这个答案标记为正确? 感谢您的回答!正如其他人所提到的,有一个很大的警告。你愿意在顶部添加这样的东西吗?更新:Firestore 目前不支持实际的 GeoPoint 查询,因此当以下查询成功执行时,它仅按纬度过滤,而不是按经度过滤,因此会返回许多不在附近的结果。最好的解决方案是使用geohashes。要了解如何自己做类似的事情,请查看video。【参考方案3】:

更新:Firestore 目前不支持实际的 GeoPoint 查询,因此当以下查询成功执行时,它仅按纬度过滤,而不是按经度过滤,因此将返回许多不在附近的结果。最好的解决方案是使用geohashes。要了解如何自己做类似的事情,请查看video。

(首先让我为这篇文章中的所有代码道歉,我只是希望任何阅读此答案的人都能轻松地重现该功能。)

为了解决 OP 的同样问题,起初我调整了 GeoFire library 以与 Firestore 一起使用(您可以通过查看该库了解很多关于地理信息的知识)。然后我意识到我并不介意位置是否以精确的圆圈返回。我只是想要一些方法来获取“附近”的位置。

我不敢相信我花了多长时间才意识到这一点,但您可以使用 SW 角和 NE 角对 GeoPoint 字段执行双重不等式查询,以获取围绕中心点的边界框内的位置。

所以我制作了一个如下所示的 javascript 函数(这基本上是 Ryan Lee 答案的 JS 版本)。

/**
 * Get locations within a bounding box defined by a center point and distance from from the center point to the side of the box;
 *
 * @param Object area an object that represents the bounding box
 *    around a point in which locations should be retrieved
 * @param Object area.center an object containing the latitude and
 *    longitude of the center point of the bounding box
 * @param number area.center.latitude the latitude of the center point
 * @param number area.center.longitude the longitude of the center point
 * @param number area.radius (in kilometers) the radius of a circle
 *    that is inscribed in the bounding box;
 *    This could also be described as half of the bounding box's side length.
 * @return Promise a Promise that fulfills with an array of all the
 *    retrieved locations
 */
function getLocations(area) 
  // calculate the SW and NE corners of the bounding box to query for
  const box = utils.boundingBoxCoordinates(area.center, area.radius);

  // construct the GeoPoints
  const lesserGeopoint = new GeoPoint(box.swCorner.latitude, box.swCorner.longitude);
  const greaterGeopoint = new GeoPoint(box.neCorner.latitude, box.neCorner.longitude);

  // construct the Firestore query
  let query = firebase.firestore().collection('myCollection').where('location', '>', lesserGeopoint).where('location', '<', greaterGeopoint);

  // return a Promise that fulfills with the locations
  return query.get()
    .then((snapshot) => 
      const allLocs = []; // used to hold all the loc data
      snapshot.forEach((loc) => 
        // get the data
        const data = loc.data();
        // calculate a distance from the center
        data.distanceFromCenter = utils.distance(area.center, data.location);
        // add to the array
        allLocs.push(data);
      );
      return allLocs;
    )
    .catch((err) => 
      return new Error('Error while retrieving events');
    );

上面的函数还为返回的每条位置数据添加了一个 .distanceFromCenter 属性,这样您就可以通过检查该距离是否在您想要的范围内来获得类似圆圈的行为。

我在上面的函数中使用了两个 util 函数,所以这里也是它们的代码。 (下面所有的 util 函数实际上都是从 GeoFire 库改编而来的。)

距离():

/**
 * Calculates the distance, in kilometers, between two locations, via the
 * Haversine formula. Note that this is approximate due to the fact that
 * the Earth's radius varies between 6356.752 km and 6378.137 km.
 *
 * @param Object location1 The first location given as .latitude and .longitude
 * @param Object location2 The second location given as .latitude and .longitude
 * @return number The distance, in kilometers, between the inputted locations.
 */
distance(location1, location2) 
  const radius = 6371; // Earth's radius in kilometers
  const latDelta = degreesToRadians(location2.latitude - location1.latitude);
  const lonDelta = degreesToRadians(location2.longitude - location1.longitude);

  const a = (Math.sin(latDelta / 2) * Math.sin(latDelta / 2)) +
          (Math.cos(degreesToRadians(location1.latitude)) * Math.cos(degreesToRadians(location2.latitude)) *
          Math.sin(lonDelta / 2) * Math.sin(lonDelta / 2));

  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

  return radius * c;

boundingBoxCoordinates():(这里也用到了更多的工具,我贴在下面。)

/**
 * Calculates the SW and NE corners of a bounding box around a center point for a given radius;
 *
 * @param Object center The center given as .latitude and .longitude
 * @param number radius The radius of the box (in kilometers)
 * @return Object The SW and NE corners given as .swCorner and .neCorner
 */
boundingBoxCoordinates(center, radius) 
  const KM_PER_DEGREE_LATITUDE = 110.574;
  const latDegrees = radius / KM_PER_DEGREE_LATITUDE;
  const latitudeNorth = Math.min(90, center.latitude + latDegrees);
  const latitudeSouth = Math.max(-90, center.latitude - latDegrees);
  // calculate longitude based on current latitude
  const longDegsNorth = metersToLongitudeDegrees(radius, latitudeNorth);
  const longDegsSouth = metersToLongitudeDegrees(radius, latitudeSouth);
  const longDegs = Math.max(longDegsNorth, longDegsSouth);
  return 
    swCorner:  // bottom-left (SW corner)
      latitude: latitudeSouth,
      longitude: wrapLongitude(center.longitude - longDegs),
    ,
    neCorner:  // top-right (NE corner)
      latitude: latitudeNorth,
      longitude: wrapLongitude(center.longitude + longDegs),
    ,
  ;

metersToLongitudeDegrees():

/**
 * Calculates the number of degrees a given distance is at a given latitude.
 *
 * @param number distance The distance to convert.
 * @param number latitude The latitude at which to calculate.
 * @return number The number of degrees the distance corresponds to.
 */
function metersToLongitudeDegrees(distance, latitude) 
  const EARTH_EQ_RADIUS = 6378137.0;
  // this is a super, fancy magic number that the GeoFire lib can explain (maybe)
  const E2 = 0.00669447819799;
  const EPSILON = 1e-12;
  const radians = degreesToRadians(latitude);
  const num = Math.cos(radians) * EARTH_EQ_RADIUS * Math.PI / 180;
  const denom = 1 / Math.sqrt(1 - E2 * Math.sin(radians) * Math.sin(radians));
  const deltaDeg = num * denom;
  if (deltaDeg < EPSILON) 
    return distance > 0 ? 360 : 0;
  
  // else
  return Math.min(360, distance / deltaDeg);

wrapLongitude():

/**
 * Wraps the longitude to [-180,180].
 *
 * @param number longitude The longitude to wrap.
 * @return number longitude The resulting longitude.
 */
function wrapLongitude(longitude) 
  if (longitude <= 180 && longitude >= -180) 
    return longitude;
  
  const adjusted = longitude + 180;
  if (adjusted > 0) 
    return (adjusted % 360) - 180;
  
  // else
  return 180 - (-adjusted % 360);

【讨论】:

嗨,您缺少 degreeToRadians 方法 function degreesToRadians(degrees) return (degrees * Math.PI)/180; 但是您不能将范围过滤器应用于同一查询中的其他字段:/ @b-fg 是的。但至少对于我的应用程序,在从数据库中检索附近的位置后,我的结果集通常足够小,因此客户端过滤/排序是实用的。我知道这不是一个完美的解决方案,但在 Firestore 直接支持地理查询之前,我怀疑会有一个完美的解决方案。 感谢您的回答!不幸的是,按 SW / NE 角过滤有一个很大的警告。你愿意在顶部添加这样的东西吗?更新:Firestore 目前不支持实际的 GeoPoint 查询,因此当以下查询成功执行时,它仅按纬度过滤,而不是按经度过滤,因此会返回许多不在附近的结果。最好的解决方案是使用geohashes。要了解如何自己做类似的事情,请查看video。【参考方案4】:

自从@monkeybonkey 第一次提出这个问题以来,已经引入了一个新项目。该项目名为GEOFirestore。

使用这个库,您可以在一个圆圈内执行查询,例如查询文档:

  const geoQuery = geoFirestore.query(
    center: new firebase.firestore.GeoPoint(10.38, 2.41),
    radius: 10.5
  );

您可以通过 npm 安装 GeoFirestore。您必须单独安装 Firebase(因为它是 GeoFirestore 的对等依赖项):

$ npm install geofirestore firebase --save

【讨论】:

这实际上没用,因为它只适用于 Javascript @nikhilSridhar 它特别有用!您希望将此类查询隐藏在最终用户之外,并仅在 Cloud Functions 中执行! 1. 查询本身可能会触发多个请求,而您希望移动设备尽可能少地发出请求 2. 将此类数据直接暴露给移动应用程序可能会成为严重的安全威胁!假设您存储了所有用户的位置...如果您赋予应用程序运行此类查询的权力,那么您也将这种权力赋予任何想要监视所有用户位置的开发人员。 @NikhilSridhar JavaScript 是这个星球上使用最多的语言,它怎么会没用呢? (当然使用 TypeScript)。在我看来,如果您正在编写任何其他语言(服务器端或客户端),那么您就是在浪费时间。共享客户端/服务器代码有人吗? React-native 等。【参考方案5】:

飞镖

///
/// Checks if these coordinates are valid geo coordinates.
/// [latitude]  The latitude must be in the range [-90, 90]
/// [longitude] The longitude must be in the range [-180, 180]
/// returns [true] if these are valid geo coordinates
///
bool coordinatesValid(double latitude, double longitude) 
  return (latitude >= -90 && latitude <= 90 && longitude >= -180 && longitude <= 180);


///
/// Checks if the coordinates  of a GeopPoint are valid geo coordinates.
/// [latitude]  The latitude must be in the range [-90, 90]
/// [longitude] The longitude must be in the range [-180, 180]
/// returns [true] if these are valid geo coordinates
///
bool geoPointValid(GeoPoint point) 
  return (point.latitude >= -90 &&
      point.latitude <= 90 &&
      point.longitude >= -180 &&
      point.longitude <= 180);


///
/// Wraps the longitude to [-180,180].
///
/// [longitude] The longitude to wrap.
/// returns The resulting longitude.
///
double wrapLongitude(double longitude) 
  if (longitude <= 180 && longitude >= -180) 
    return longitude;
  
  final adjusted = longitude + 180;
  if (adjusted > 0) 
    return (adjusted % 360) - 180;
  
  // else
  return 180 - (-adjusted % 360);


double degreesToRadians(double degrees) 
  return (degrees * math.pi) / 180;


///
///Calculates the number of degrees a given distance is at a given latitude.
/// [distance] The distance to convert.
/// [latitude] The latitude at which to calculate.
/// returns the number of degrees the distance corresponds to.
double kilometersToLongitudeDegrees(double distance, double latitude) 
  const EARTH_EQ_RADIUS = 6378137.0;
  // this is a super, fancy magic number that the GeoFire lib can explain (maybe)
  const E2 = 0.00669447819799;
  const EPSILON = 1e-12;
  final radians = degreesToRadians(latitude);
  final numerator = math.cos(radians) * EARTH_EQ_RADIUS * math.pi / 180;
  final denom = 1 / math.sqrt(1 - E2 * math.sin(radians) * math.sin(radians));
  final deltaDeg = numerator * denom;
  if (deltaDeg < EPSILON) 
    return distance > 0 ? 360.0 : 0.0;
  
  // else
  return math.min(360.0, distance / deltaDeg);


///
/// Defines the boundingbox for the query based
/// on its south-west and north-east corners
class GeoBoundingBox 
  final GeoPoint swCorner;
  final GeoPoint neCorner;

  GeoBoundingBox(this.swCorner, this.neCorner);


///
/// Defines the search area by a  circle [center] / [radiusInKilometers]
/// Based on the limitations of FireStore we can only search in rectangles
/// which means that from this definition a final search square is calculated
/// that contains the circle
class Area 
  final GeoPoint center;
  final double radiusInKilometers;

  Area(this.center, this.radiusInKilometers): 
  assert(geoPointValid(center)), assert(radiusInKilometers >= 0);

  factory Area.inMeters(GeoPoint gp, int radiusInMeters) 
    return new Area(gp, radiusInMeters / 1000.0);
  

  factory Area.inMiles(GeoPoint gp, int radiusMiles) 
    return new Area(gp, radiusMiles * 1.60934);
  

  /// returns the distance in km of [point] to center
  double distanceToCenter(GeoPoint point) 
    return distanceInKilometers(center, point);
  


///
///Calculates the SW and NE corners of a bounding box around a center point for a given radius;
/// [area] with the center given as .latitude and .longitude
/// and the radius of the box (in kilometers)
GeoBoundingBox boundingBoxCoordinates(Area area) 
  const KM_PER_DEGREE_LATITUDE = 110.574;
  final latDegrees = area.radiusInKilometers / KM_PER_DEGREE_LATITUDE;
  final latitudeNorth = math.min(90.0, area.center.latitude + latDegrees);
  final latitudeSouth = math.max(-90.0, area.center.latitude - latDegrees);
  // calculate longitude based on current latitude
  final longDegsNorth = kilometersToLongitudeDegrees(area.radiusInKilometers, latitudeNorth);
  final longDegsSouth = kilometersToLongitudeDegrees(area.radiusInKilometers, latitudeSouth);
  final longDegs = math.max(longDegsNorth, longDegsSouth);
  return new GeoBoundingBox(
      swCorner: new GeoPoint(latitudeSouth, wrapLongitude(area.center.longitude - longDegs)),
      neCorner: new GeoPoint(latitudeNorth, wrapLongitude(area.center.longitude + longDegs)));


///
/// Calculates the distance, in kilometers, between two locations, via the
/// Haversine formula. Note that this is approximate due to the fact that
/// the Earth's radius varies between 6356.752 km and 6378.137 km.
/// [location1] The first location given
/// [location2] The second location given
/// sreturn the distance, in kilometers, between the two locations.
///
double distanceInKilometers(GeoPoint location1, GeoPoint location2) 
  const radius = 6371; // Earth's radius in kilometers
  final latDelta = degreesToRadians(location2.latitude - location1.latitude);
  final lonDelta = degreesToRadians(location2.longitude - location1.longitude);

  final a = (math.sin(latDelta / 2) * math.sin(latDelta / 2)) +
      (math.cos(degreesToRadians(location1.latitude)) *
          math.cos(degreesToRadians(location2.latitude)) *
          math.sin(lonDelta / 2) *
          math.sin(lonDelta / 2));

  final c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a));

  return radius * c;

我刚刚发布了一个基于上面JS代码的Flutter包 https://pub.dartlang.org/packages/firestore_helpers

【讨论】:

请不要只发布一些工具或库作为答案。至少在答案本身中展示how it solves the problem。 该库有一个很好的文档,它只是一个额外的答案 抱歉,这是典型的 SO 审核之一。它包含有价值的信息,并且链接将保持有效,因为这是一个不会取消列出任何包的包存储库。删除它会剥夺其他用户的权利 @Thomas 链接总是有可能会死掉,而且不仅有些人坐在严格的防火墙后面,可能会阻止他们访问链接。仅供参考,将相关内容带入答案总是好的。不仅如此,它看起来还好得多。 请注意,截至目前,该库不使用 geohashes 并使用客户端过滤经度,未优化【参考方案6】:

这还没有经过全面测试,但应该比 Ryan Lee 的答案有所改进

我的计算更准确,然后我过滤答案以删除落在边界框内但在半径之外的命中

斯威夫特 4

func getDocumentNearBy(latitude: Double, longitude: Double, meters: Double) 

    let myGeopoint = GeoPoint(latitude:latitude, longitude:longitude )
    let r_earth : Double = 6378137  // Radius of earth in Meters

    // 1 degree lat in m
    let kLat = (2 * Double.pi / 360) * r_earth
    let kLon = (2 * Double.pi / 360) * r_earth * __cospi(latitude/180.0)

    let deltaLat = meters / kLat
    let deltaLon = meters / kLon

    let swGeopoint = GeoPoint(latitude: latitude - deltaLat, longitude: longitude - deltaLon)
    let neGeopoint = GeoPoint(latitude: latitude + deltaLat, longitude: longitude + deltaLon)

    let docRef : CollectionReference = appDelegate.db.collection("restos")

    let query = docRef.whereField("location", isGreaterThan: swGeopoint).whereField("location", isLessThan: neGeopoint)
    query.getDocuments  snapshot, error in
      guard let snapshot = snapshot else 
        print("Error fetching snapshot results: \(error!)")
        return
      
      self.documents = snapshot.documents.filter  (document)  in
        if let location = document.get("location") as? GeoPoint 
          let myDistance = self.distanceBetween(geoPoint1:myGeopoint,geoPoint2:location)
          print("myDistance:\(myDistance) distance:\(meters)")
          return myDistance <= meters
        
        return false
      
    
  

精确测量 2 个地理点之间的距离(以米为单位)以进行过滤的功能

func distanceBetween(geoPoint1:GeoPoint, geoPoint2:GeoPoint) -> Double
    return distanceBetween(lat1: geoPoint1.latitude,
                           lon1: geoPoint1.longitude,
                           lat2: geoPoint2.latitude,
                           lon2: geoPoint2.longitude)

func distanceBetween(lat1:Double, lon1:Double, lat2:Double, lon2:Double) -> Double  // generally used geo measurement function
    let R : Double = 6378.137; // Radius of earth in KM
    let dLat = lat2 * Double.pi / 180 - lat1 * Double.pi / 180;
    let dLon = lon2 * Double.pi / 180 - lon1 * Double.pi / 180;
    let a = sin(dLat/2) * sin(dLat/2) +
      cos(lat1 * Double.pi / 180) * cos(lat2 * Double.pi / 180) *
      sin(dLon/2) * sin(dLon/2);
    let c = 2 * atan2(sqrt(a), sqrt(1-a));
    let d = R * c;
    return d * 1000; // meters

【讨论】:

【参考方案7】:

是的,这是一个老话题,但我只想对 Java 代码提供帮助。我如何解决经度问题?我使用了 Ryan LeeMichael Teper 的代码。

一个代码:

@Override
public void getUsersForTwentyMiles() 
    FirebaseFirestore db = FirebaseFirestore.getInstance();

    double latitude = 33.0076665;
    double longitude = 35.1011336;

    int distance = 20;   //20 milles

    GeoPoint lg = new GeoPoint(latitude, longitude);

    // ~1 mile of lat and lon in degrees
    double lat = 0.0144927536231884;
    double lon = 0.0181818181818182;

    final double lowerLat = latitude - (lat * distance);
    final double lowerLon = longitude - (lon * distance);

    double greaterLat = latitude + (lat * distance);
    final double greaterLon = longitude + (lon * distance);

    final GeoPoint lesserGeopoint = new GeoPoint(lowerLat, lowerLon);
    final GeoPoint greaterGeopoint = new GeoPoint(greaterLat, greaterLon);

    Log.d(LOG_TAG, "local general lovation " + lg);
    Log.d(LOG_TAG, "local lesserGeopoint " + lesserGeopoint);
    Log.d(LOG_TAG, "local greaterGeopoint " + greaterGeopoint);

    //get users for twenty miles by only a latitude 
    db.collection("users")
            .whereGreaterThan("location", lesserGeopoint)
            .whereLessThan("location", greaterGeopoint)
            .get()
            .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() 
                @Override
                public void onComplete(@NonNull Task<QuerySnapshot> task) 
                    if (task.isSuccessful()) 
                        for (QueryDocumentSnapshot document : task.getResult()) 

                            UserData user = document.toObject(UserData.class);

                            //here a longitude condition (myLocation - 20 <= myLocation <= myLocation +20)
                            if (lowerLon <= user.getUserGeoPoint().getLongitude() && user.getUserGeoPoint().getLongitude() <= greaterLon) 
                                Log.d(LOG_TAG, "location: " + document.getId());
                                                    
                          
                     else 
                        Log.d(LOG_TAG, "Error getting documents: ", task.getException());
                    
                
            );

在发出结果后将过滤器设置为经度:

if (lowerLon <= user.getUserGeoPoint().getLongitude() && user.getUserGeoPoint().getLongitude() <= greaterLon) 
    Log.d(LOG_TAG, "location: " + document.getId());
  

我希望这会对某人有所帮助。 祝你有美好的一天!

【讨论】:

【参考方案8】:

劫持此线程以希望对仍在寻找的人有所帮助。 Firestore 仍然不支持基于地理的查询,并且使用 GeoFirestore 库也不理想,因为它只能让您按位置搜索,没有别的。

我把这些放在一起: https://github.com/mbramwell1/GeoFire-Android

它基本上可以让您使用位置和距离进行附近搜索:

QueryLocation queryLocation = QueryLocation.fromDegrees(latitude, longitude);
Distance searchDistance = new Distance(1.0, DistanceUnit.KILOMETERS);
geoFire.query()
    .whereNearTo(queryLocation, distance)
    .build()
    .get();

repo 上有更多文档。它对我有用,所以试一试,希望它能满足你的需要。

【讨论】:

【参考方案9】:

Firestore 有一个 GeoFire 库,名为 Geofirestore:https://github.com/imperiumlabs/GeoFirestore(免责声明:我帮助开发了它)。它非常易于使用,并为 Firestore 提供与 Geofire 为 Firebase Realtime DB 提供的相同功能)

【讨论】:

你能帮忙再修复一次吗,因为它降低了 firebasefirestore 版本,我会收到一个错误,提示缺少某些东西。【参考方案10】:

最简单的方法是在数据库中存储位置时计算“地理哈希”。

geo hash 是一个字符串,它代表一个精确到一定精度的位置。地理散列越长,具有所述地理散列的位置必须越接近。两个位置,例如相隔 100m 可能具有相同的 6 字符地理哈希,但在计算 7 字符地理哈希时,最后一个字符可能不同。

有很多库可让您计算任何语言的地理哈希。只需将其存储在位置旁边,然后使用 == 查询来查找具有相同地理哈希的位置。

【讨论】:

【参考方案11】:

在javascript中你可以简单地

const db = firebase.firestore();

 //Geofire
import  GeoCollectionReference, GeoFirestore, GeoQuery, GeoQuerySnapshot  from 'geofirestore';

// Create a GeoFirestore reference
const geofirestore: GeoFirestore = new GeoFirestore(db);

// Create a GeoCollection reference
const geocollection: GeoCollectionReference = geofirestore.collection('<Your_collection_name>');

const query: GeoQuery = geocollectionDrivers.near( 
        center: new firebase.firestore.GeoPoint(location.latitude, location.longitude), 
        radius: 10000 
    );
    query.onSnapshot(gquerySnapshot => 
        gquerySnapshot.forEach(res => 
            console.log(res.data());
        )
    );

【讨论】:

【参考方案12】:

Flutter 的一种解决方法,直到我们在 Firestore 中有本地查询以根据 lat/long 拉取有序文档: https://pub.dev/packages/geoflutterfire 一个在 Firestore 中存储地理哈希并进行查询的插件。

限制:不支持限制

【讨论】:

【参考方案13】:

截至 2020 年底,现在还有 how to do geoqueries with Firestore 的文档。

这些适用于 ios、Android 和 Web 的解决方案建立在 Firebase 创建的 GeoFire 库的精简版本之上,然后展示如何:

生成 geohash 值并将其存储在 Firestore 中 确定某个点和半径的边界框的 geohash 范围 在这些 geohash 范围内执行查询

这比这里介绍的大多数其他库都低级一些,因此它可能更适合某些用例而更不适合其他用例。

【讨论】:

【参考方案14】:

您应该使用 GeoFire(与 Firestore 一起使用)。有了这个,您可以过滤服务器上的文档并从您的 Firestore 数据库中读取更少的文档。这也会减少您的阅读次数。

检查这个库是否有 GroFire:https://github.com/patpatchpatrick/GeoFirestore-iOS

"patpatchpatrick" 使它与 Swift 5 兼容。

只需按如下方式安装 pod:

pod 'Geofirestore', :git => 'https://github.com/patpatchpatrick/GeoFirestore-iOS'

我在我的一个项目中使用这个库,它运行良好。

设置位置:

let location: CLLocation = CLLocation(latitude: lat, longitude: lng)
yourCollection.setLocation(location: location, forDocumentWithID: "YourDocId")  (error) in 

要删除位置:

collection.removeLocation(forDocumentWithID: "YourDocId")

获取文档:

let center = CLLocation(latitude: lat, longitude: lng)
let collection = "Your collection path"
let circleQuery = collection.query(withCenter: center, radius: Double(yourRadiusVal))
        
let _ = circleQuery.observe(.documentEntered, with:  (key, location) in
        //Use info as per your need
)

我使用了.documentEntered,您可以根据需要使用其他可用的地理查询,例如(文档退出,文档移动)。

您也可以使用 GeoPoint 进行查询。

【讨论】:

以上是关于如何使用 Firestore 运行地理“附近”查询?的主要内容,如果未能解决你的问题,请参考以下文章

无法规范化查询 :: 由地理附近查询中的 :: 无效参数引起

将经度和纬度数据存储在数据库中,并查询附近的记录

查询地理位置附近的东西?

有没有办法将 GeoFire 与 Firestore 一起使用?

Redis 到底是如何实现“附近的人”这个功能呢?

如何将位置发送到 Firestore 并根据距离获取位置?