用例:
问题:
次优选择: [Web Javascript Firebase V9 模块化] 我会使用 Firestore,为给定范围(静态的,非实时的)创建一个 Geoquery,并从中获取所有文档。然后在控制台中,用户只能通过单击特定卡车(文档)来查看实时信息,因此我可以将 onSnapshot 附加到该文档。
这个用例是否根本不受支持,或者有没有办法以合理的成本对数据进行建模以适应它?
GeoFire 仅在实时数据库上得到完全支持。 GeoFire 的数据旨在与您自己的数据分开保存,并通过唯一的密钥/ID 链接到它。这最大限度地减少了执行查询所需的索引,并最大限度地减少了查询时传递的数据。
Cloud Firestore 有一个解决方法,但它不适合您的用例,因为它会获取所有附近的记录,然后在客户端过滤其余记录。
如果你很好奇,GeoFire 条目在上传时看起来类似于以下内容(为了便于阅读而格式化):
"path/to/geofire/": {
"<key>/": {
".priority": string, // a geohash, hidden
"g": string, // a geohash
"l": {
"0": number, // latitude, number
"1": number, // longitude, number
}
}
}
从上面可以看出,这里没有任何用户提供的数据,只有一个“密钥”。此键可以具有任何含义,例如注册表中的机场代码、实时数据库位置下的推送 ID、Cloud Firestore 文档 ID 或一些 base64 编码的数据。
随着 Firestore 的推出,许多用户将 GeoFire 数据存储在实时数据库中,并使用存储在 GeoFire 中的密钥将其链接回 Cloud Firestore 文档。
在下面的例子中,一个
truckInfo
对象看起来像这样:
interface TruckInfo {
key: string;
location: Geopoint; // [number, number]
distance: number;
snapshot: DataSnapshot | undefined;
cancelling?: number; // an ID from a setTimeout call
errorCount: number; // number of errors trying to attach data listener
hidden?: true; // indicates whether the vehicle is currently out of range
listening?: boolean; // indicates whether the listener was attached successfully
}
要使下面的代码起作用,您必须定义两个回调方法:
const truckUpdatedCallback = (truckInfo, snapshot) => { // or you can use ({ key, location, snapshot }) => { ... }
// TODO: handle new trucks, updated locations and/or updated truck info
// truckInfo.hidden may be true!
}
const truckRemovedCallback => (truckInfo, snapshot) => {
// TODO: remove completely (deleted/out-of-range)
}
这些回调随后由以下“引擎”调用:
const firebaseRef = ref(getDatabase(), "gf"), // RTDB location for GeoFire data
trucksColRef = collection(getFirestore(), "trucks"), // Firestore location for truck-related data
geoFireInstance = new geofire.GeoFire(firebaseRef),
trackedTrucks = new Map(), // holds all the tracked trucks
listenToTruck = (truckInfo) => { // attaches the Firestore listeners for the trucks
if (truckInfo.cancelling !== void 0) {
clearTimeout(truckInfo.cancelling);
delete truckInfo.cancelling;
}
if (truckInfo.listening || truckInfo.errorCount >= 3)
return; // do nothing.
truckInfo.unsub = onSnapshot(
truckInfo.docRef,
(snapshot) => {
truckInfo.listening = true;
truckInfo.errorCount = 0;
truckInfo.snapshot = snapshot;
truckUpdatedCallback(truckInfo, snapshot); // fire callback
},
(err) => {
truckInfo.listening = false;
truckInfo.errorCount++;
console.error("Failed to track truck #" + truckInfo.key, err);
}
)
},
cancelQuery = () => { // removes the listeners for all trucks and disables query
// prevents all future updates
geoQuery.cancel();
trackedTrucks.forEach(({unsub}) => {
unsub && unsub();
});
};
const geoQuery = geoFireInstance.query({ center, radius });
geoQuery.on("key_entered", function(key, location, distance) {
let truckInfo = trackedTrucks.get(key);
if (!truckInfo) {
// new truck to track
const docRef = document(truckColRef, key);
truckInfo = { key, location, distance, docRef, errorCount: 0 };
trackedTrucks.set(key, truckInfo);
} else {
// truck has re-entered watch area, update position
Object.assign(truckInfo, { location, distance });
delete truckInfo.hidden;
}
listenToTruck(truckInfo);
});
geoQuery.on("key_moved", function(key, location, distance) {
const truckInfo = trackedTrucks.get(key);
if (!truckInfo) return; // not being tracked?
Object.assign(truckInfo, { location, distance });
truckUpdatedCallback(truckInfo, snapshot); // fire callback
});
geoQuery.on("key_exited", function(key, location, distance) {
const truckInfo = trackedTrucks.get(key);
if (!truckInfo) return; // not being tracked?
truckInfo.hidden = true;
const unsub = truckInfo.unsub,
cleanup = () => { // removes any listeners for this truck and removes it from tracking
unsub && unsub();
truckInfo.listening = false;
trackedTrucks.delete(key);
truckRemovedCallback(truckInfo, snapshot); // fire removed callback
};
if (location === null && distance === null) {
// removed from database, immediately remove from view
return cleanup();
}
// keep getting updates for at least 60s after going out of
// range in case vehicle returns. Afterwards, remove from view
truckInfo.cancelling = setTimeout(cleanup, 60000);
// fire callback (to hide the truck)
truckUpdatedCallback(truckInfo, snapshot);
});