Firebase “如何运行地理信息系统”;“附近”;使用firestore进行查询?

Firebase “如何运行地理信息系统”;“附近”;使用firestore进行查询?,firebase,google-cloud-firestore,Firebase,Google Cloud Firestore,firebase的新firestore数据库是否本机支持基于位置的地理查询?i、 e.在10英里内找到柱子,或找到最近的50根柱子 我看到一些现有的实时firebase数据库项目,比如geofire——这些项目是否也可以应用于firestore?到今天为止,还没有办法进行这样的查询。SO中还有其他与之相关的问题: 在我当前的Android项目中,我可能会在Firebase团队开发本机支持时添加geohash字段 像其他问题中建议的那样使用Firebase实时数据库意味着您无法同时按位置和其

firebase的新firestore数据库是否本机支持基于位置的地理查询?i、 e.在10英里内找到柱子,或找到最近的50根柱子


我看到一些现有的实时firebase数据库项目,比如geofire——这些项目是否也可以应用于firestore?

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

在我当前的Android项目中,我可能会在Firebase团队开发本机支持时添加geohash字段

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

更新:Firestore目前不支持实际的地质点查询,因此,虽然下面的查询成功执行,但它只按纬度过滤,而不按经度过滤,因此将返回许多不在附近的结果。最好的解决办法是使用。要了解如何自己做类似的事情,请看看这个

这可以通过创建小于大于查询的边界框来实现。至于效率,我不能说

注意,应检查~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)
}
更新:Firestore目前不支持实际的地质点查询,因此,虽然下面的查询成功执行,但它只按纬度过滤,而不按经度过滤,因此将返回许多不在附近的结果。最好的解决办法是使用。要了解如何自己做类似的事情,请看看这个

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

为了解决OP的同样问题,首先我调整了与Firestore的合作(通过查看该库,您可以了解很多有关地理信息的内容)。然后我意识到我其实并不介意位置是否以一个精确的圆圈返回。我只是想找个办法去“附近”的地方

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

因此,我制作了一个如下所示的JavaScript函数(这基本上是Ryan Lee答案的JS版本)

/**
*获取由中心点定义的边界框内的位置以及从中心点到框侧面的距离;
*
*@param{Object}区域表示边界框的对象
*在应该检索位置的点周围
*@param{Object}area.center包含纬度和
*边界框中心点的经度
*@param{number}area.center.lation中心点的纬度
*@param{number}area.center.longitude中心点的经度
*@param{number}area.radius(以公里为单位)圆的半径
*刻在边界框中的;
*这也可以描述为边界框边长的一半。
*@return{Promise}一个用所有
*检索到的位置
*/
功能位置(区域){
//计算要查询的边界框的西南角和东北角
常量框=utils.boundingBoxCoordinates(面积.中心,面积.半径);
//建设地质点
const lesserGeopoint=新的地质点(box.swCorner.latitude,box.swCorner.longitude);
常量greaterGeopoint=新的地质点(box.neCorner.latitude,box.neCorner.longitude);
//构造Firestore查询
让query=firebase.firestore().collection('myCollection')。where('location','>',lesserGeopoint)。where('location','0?360:0;
}
//否则
返回数学最小值(360,距离/增量);
}
wrapLongitude():

/**
*将经度换行到[-180180]。
*
*@param{number}经度要包装的经度。
*@return{number}经度结果经度。
*/
函数包装图(经度){
如果(经度=-180){
返回经度;
}
调整常数=经度+180;
如果(调整后>0){
收益率(调整后的百分比360)-180;
}
//否则
返回180-(-调整为360%);
}

自从@monkeybankey第一次提出这个问题以来,一个新项目被引入。该项目被称为

使用此库,您可以执行查询,如圆内的查询文档:

  const geoQuery = geoFirestore.query({
    center: new firebase.firestore.GeoPoint(10.38, 2.41),
    radius: 10.5
  });
您可以通过npm安装GeoFirestore。您必须单独安装Firebase(因为它是GeoFirestore的对等依赖项):


用于省道

///
/// 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;
}
///
///检查这些坐标是否为有效的地理坐标。
///[纬度]纬度必须在[-90,90]范围内
///[经度]经度必须在[-180,180]范围内
///如果这些是有效的地理坐标,则返回[true]
///
布尔坐标有效(双纬度、双经度){
返回(纬度>=-90&&纬度=-180&&经度=-90&&
点纬度=-180&&
点经度(0){
收益率(调整后的百分比360)-180;
}
//否则
返回180-(-调整为360%);
}
双度弧度(双度){
返回值(度*数学pi)/180;
}
///
///计算给定距离在给定纬度处的度数。
///[距离]要转换的距离。
///[纬度]要计算的纬度。
///返回距离对应的度数。
双公里到长度(双距离,双纬度){
常数地球均衡半径=6378137.0;
//这是一个超级神奇的数字,GeoFire库可以解释(也许)
常数E2=0.00669447819799;
常数ε=1e-12;
最终弧度=度弧度(纬度);
最终数字
///
/// 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;
}
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
      }
    }
  }
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
}
@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());
}  
QueryLocation queryLocation = QueryLocation.fromDegrees(latitude, longitude);
Distance searchDistance = new Distance(1.0, DistanceUnit.KILOMETERS);
geoFire.query()
    .whereNearTo(queryLocation, distance)
    .build()
    .get();
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());
        })
    });
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
})