地理位置查询
许多应用都有按实际地理位置编入索引的文档。例如,您的应用可能允许用户浏览他们当前所在位置附近的商店。
解决方案:Geohash
Geohash 是用于将 (latitude, longitude) 对编码为单个 Base32 字符串的体系。在 Geohash 体系中,世界被划分为一个矩形网格。Geohash 字符串的每个字符都指定了前缀哈希值 32 个细分中的其中一个。例如,Geohash abcd 是完全涵盖在更大的 Geohash abc 范围之内的 32 个四字符哈希值的其中之一。
两个哈希值之间的共同前缀越长,彼此就越近。例如,abcdef 离 abcdeg 比 abcdff 更近。但是,反之却不成立!两个地理区域可能会非常近,却拥有截然不同的 Geohash:

我们可以使用 Geohash 在 Firestore 中的位置存储和查询文档,同时获得合理的效率,但只需要一个已编入索引的字段。
安装帮助程序库
创建和解析 Geohash 涉及一些复杂的数学概念,因此我们构建了帮助程序库,以抽象化 Android、Apple 和 Web 上最棘手的部分:
Web 版本 9
// Install from NPM. If you prefer to use a static .js file visit
// https://github.com/firebase/geofire-js/releases and download
// geofire-common.min.js from the latest version
npm install --save geofire-common
Web 版本 8
// Install from NPM. If you prefer to use a static .js file visit
// https://github.com/firebase/geofire-js/releases and download
// geofire-common.min.js from the latest version
npm install --save geofire-common
Swift
注意:此产品不适用于 watchOS 和 App Clip 目标。
// 将其添加到您的 Podfile pod“GeoFire/Utils” Kotlin
Android
// Add this to your app/build.gradle
implementation 'com.firebase:geofire-android-common:3.2.0'
Java
Android
// Add this to your app/build.gradle
implementation 'com.firebase:geofire-android-common:3.1.0'
存储 Geohash
对于您要按地理位置编制索引的每个文档,您需要存储一个 Geohash 字段:
Web 版本 9
import { doc, updateDoc } from 'firebase/firestore'; // Compute the GeoHash for a lat/lng point const lat = 51.5074; const lng = 0.1278; const hash = geofire.geohashForLocation([lat, lng]); // Add the hash and the lat/lng to the document. We will use the hash // for queries and the lat/lng for distance comparisons. const londonRef = doc(db, 'cities', 'LON'); await updateDoc(londonRef, { geohash: hash, lat: lat, lng: lng });
Web 版本 8
// Compute the GeoHash for a lat/lng point const lat = 51.5074; const lng = 0.1278; const hash = geofire.geohashForLocation([lat, lng]); // Add the hash and the lat/lng to the document. We will use the hash // for queries and the lat/lng for distance comparisons. const londonRef = db.collection('cities').doc('LON'); londonRef.update({ geohash: hash, lat: lat, lng: lng }).then(() => { // ... });
Swift
注意:此产品不适用于 watchOS 和 App Clip 目标。
// Compute the GeoHash for a lat/lng point let latitude = 51.5074 let longitude = 0.12780 let location = CLLocationCoordinate2D(latitude: latitude, longitude: longitude) let hash = GFUtils.geoHash(forLocation: location) // Add the hash and the lat/lng to the document. We will use the hash // for queries and the lat/lng for distance comparisons. let documentData: [String: Any] = [ "geohash": hash, "lat": latitude, "lng": longitude ] let londonRef = db.collection("cities").document("LON") londonRef.updateData(documentData) { error in // ... }
Kotlin
Android
// Compute the GeoHash for a lat/lng point val lat = 51.5074 val lng = 0.1278 val hash = GeoFireUtils.getGeoHashForLocation(GeoLocation(lat, lng)) // Add the hash and the lat/lng to the document. We will use the hash // for queries and the lat/lng for distance comparisons. val updates: MutableMap<String, Any> = mutableMapOf( "geohash" to hash, "lat" to lat, "lng" to lng, ) val londonRef = db.collection("cities").document("LON") londonRef.update(updates) .addOnCompleteListener { // ... }
Java
Android
// Compute the GeoHash for a lat/lng point double lat = 51.5074; double lng = 0.1278; String hash = GeoFireUtils.getGeoHashForLocation(new GeoLocation(lat, lng)); // Add the hash and the lat/lng to the document. We will use the hash // for queries and the lat/lng for distance comparisons. Map<String, Object> updates = new HashMap<>(); updates.put("geohash", hash); updates.put("lat", lat); updates.put("lng", lng); DocumentReference londonRef = db.collection("cities").document("LON"); londonRef.update(updates) .addOnCompleteListener(new OnCompleteListener<Void>() { @Override public void onComplete(@NonNull Task<Void> task) { // ... } });
查询 Geohash
Geohash 可让我们针对 Geohash 字段加入一组查询来进行大致的区域查询,然后再过滤掉一些假正例:
Web 版本 9
import { collection, query, orderBy, startAt, endAt, getDocs } from 'firebase/firestore'; // Find cities within 50km of London const center = [51.5074, 0.1278]; const radiusInM = 50 * 1000; // Each item in 'bounds' represents a startAt/endAt pair. We have to issue // a separate query for each pair. There can be up to 9 pairs of bounds // depending on overlap, but in most cases there are 4. // @ts-ignore const bounds = geofire.geohashQueryBounds(center, radiusInM); const promises = []; for (const b of bounds) { const q = query( collection(db, 'cities'), orderBy