Firebase的新Firestore數據庫是否本地支持基於位置的地理查詢?即在10英里內查找帖子,或查找50個最近的帖子?如何使用firestore運行地理「附近」查詢?
我看到有一些針對實時Firebase數據庫的現有項目,諸如geofire之類的項目也可以將這些項目改編爲firestore?
Firebase的新Firestore數據庫是否本地支持基於位置的地理查詢?即在10英里內查找帖子,或查找50個最近的帖子?如何使用firestore運行地理「附近」查詢?
我看到有一些針對實時Firebase數據庫的現有項目,諸如geofire之類的項目也可以將這些項目改編爲firestore?
截至今日,沒有辦法做這樣的查詢。有在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添加地理散列領域,而火力地堡的團隊正在開發的原生支持。
使用Firebase實時數據庫就像在其他問題中建議的一樣意味着您無法同時過濾位置和其他字段的結果集,這是我首先想要切換到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)
}
(第一讓我爲這篇文章中的所有代碼道歉,我只是希望任何讀這個答案的人都可以輕鬆地再現這個功能。)
爲了解決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():(有在這裏也使用更utils的,我已經粘貼下面)
/**
* 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);
}
[如何在Firebase Cloud Firestore中的集合中查詢最近的GeoPoints?]可能的重複?(https://stackoverflow.com/questions/46607760/how-to-query-closest-geopoints-in-a-collection -in-火力-雲公司的FireStore) –