geoflutterfire_plus ?

geoflutterfire_plus 允许您的 flutter 应用查询存储在 Cloud Firestore 中的地理数据。

此包是 GeoFlutterFire 的分支,并努力保持与最新的 Flutter SDK、Dart SDK 和其他依赖包的兼容性。

先决条件

Dart: '>=2.17.0 <3.0.0'
Flutter: '>=2.10.0'

安装

运行此命令

flutter pub add geoflutterfire_plus

或者将依赖项添加到您的 pubspec.yaml

dependencies:
  geoflutterfire_plus: <latest-version>

地理查询

请参阅 Firebase 官方文档 地理查询,以了解什么是 Geohash,为什么需要将地理位置保存为 Geohash,以及如何查询它们。它还将帮助您了解使用 Geohash 查询位置的限制。

保存地理数据

为了将地理数据保存为 Cloud Firestore 的文档,请使用 GeoFirePointGeoFirePoint.data 提供 geopoint(cloud_firestore 包中定义的 GeoPoint 类型)和 Geohash 字符串。

// Define GeoFirePoint instance by giving latitude and longitude.
final GeoFirePoint geoFirePoint = GeoFirePoint(35.681236, 139.767125);

// Get GeoPoint instance and Geohash string as Map<String, dynamic>.
final Map<String, dynamic> data = geoFirePoint.data;

// {geopoint: Instance of 'GeoPoint', geohash: xn76urx66}
print(data);

GeoCollectionReference 实例提供 add 方法来在新集合的文档中创建文档(内部,只是调用 cloud_firestoreadd 方法)。

// Add new documents to locations collection.
GeoCollectionReference<Map<String, dynamic>>(
  FirebaseFirestore.instance.collection('locations'),
).add(<String, dynamic>{
  'geo': geoFirePoint.data,
  'name': name,
  'isVisible': true,
});

或者,您也可以直接调用 cloud_firestoreaddset 方法来保存数据。例如,

// Add new documents to locations collection.
FirebaseFirestore.instance.collection('locations').add(
  <String, dynamic>{
    'geo': data,
    'name': 'Tokyo Station',
    'isVisible': true,
  },
);

创建的文档将如下面的截图所示。Geohash 字符串(geohash)和 Cloud Firestore GeoPoint 数据(geopoint)以地图类型保存在 geo 字段中。

Cloud Firestore

为了在指定文档的给定字段上设置或更新经纬度对作为 cloud_firestore GeoPoint 和 Geohash 字符串,可以使用 GeoCollectionReference.setPoint

// Set or update geo field on the specified document.
GeoCollectionReference(FirebaseFirestore.instance.collection('locations'))
    .setPoint(
  id: 'your-document-id',
  field: 'geo',
  latitude: 35.681236,
  longitude: 139.767125,
);

查询地理数据

为了查询东京站 50 公里半径内的位置文档,您将编写如下查询:

基本查询

// Center of the geo query.
final GeoFirePoint center = GeoFirePoint(35.681236, 139.767125);

// Detection range from the center point.
const double radiusInKm = 50;

// Field name of Cloud Firestore documents where the geohash is saved.
const String field = 'geo';

// Reference to locations collection.
final CollectionReference<Map<String, dynamic>> collectionReference =
    FirebaseFirestore.instance.collection('locations');

// Function to get GeoPoint instance from Cloud Firestore document data.
GeoPoint geopointFrom(Map<String, dynamic> data) =>
     (data['geo'] as Map<String, dynamic>)['geopoint'] as GeoPoint;

// Streamed document snapshots of geo query under given conditions.
final Stream<List<DocumentSnapshot<Map<String, dynamic>>>> stream =
    GeoCollectionReference<Map<String, dynamic>>(collectionReference).within(
  center: center,
  radiusInKm: radiusInKm,
  field: field,
  geopointFrom: geopointFrom,
);

使用 withConverter

如果您想使用 withConverter 来进行类型安全的查询编写,首先需要定义其实体类和工厂构造函数。

/// An entity of Cloud Firestore location document.
class Location {
  Location({
    required this.geo,
    required this.name,
    required this.isVisible,
  });

  factory Location.fromJson(Map<String, dynamic> json) => Location(
        geo: Geo.fromJson(json['geo'] as Map<String, dynamic>),
        name: json['name'] as String,
        isVisible: (json['isVisible'] ?? false) as bool,
      );

  factory Location.fromDocumentSnapshot(DocumentSnapshot documentSnapshot) =>
      Location.fromJson(documentSnapshot.data()! as Map<String, dynamic>);

  final Geo geo;
  final String name;
  final bool isVisible;

  Map<String, dynamic> toJson() => <String, dynamic>{
        'geo': geo.toJson(),
        'name': name,
        'isVisible': isVisible,
      };
}

/// An entity of `geo` field of Cloud Firestore location document.
class Geo {
  Geo({
    required this.geohash,
    required this.geopoint,
  });

  factory Geo.fromJson(Map<String, dynamic> json) => Geo(
        geohash: json['geohash'] as String,
        geopoint: json['geopoint'] as GeoPoint,
      );

  final String geohash;
  final GeoPoint geopoint;

  Map<String, dynamic> toJson() => <String, dynamic>{
        'geohash': geohash,
        'geopoint': geopoint,
      };
}

然后,定义类型化的集合引用。

/// Reference to the collection where the location data is stored.
final typedCollectionReference =
    FirebaseFirestore.instance.collection('locations').withConverter(
          fromFirestore: (ds, _) => Location.fromDocumentSnapshot(ds),
          toFirestore: (obj, _) => obj.toJson(),
        );

// Function to get GeoPoint instance from Location instance.
GeoPoint geopointFrom: (Location location) => location.geo.geopoint;

您可以像第一个示例一样编写查询。

// Streamed document snapshots of geo query under given conditions.
final Stream<List<DocumentSnapshot<Location>>> stream =
    GeoCollectionReference<Location>(typedCollectionReference).within(
  center: center,
  radiusInKm: radiusInKm,
  field: field,
  geopointFrom: geopointFrom,
);

自定义查询条件

如果您想添加自定义查询条件,可以使用 within 方法的 queryBuilder 参数。

例如,当您只过滤 isVisible 字段为 true 的文档时,您的 queryBuilder 将如下所示:

// Custom query condition.
Query<Location> queryBuilder(Query<Location> query) =>
    query.where('isVisible', isNotEqualTo: true);

然后,只需将 queryBuilder 传递给 within 方法的参数即可。

? 注意:自定义查询条件可能需要复合索引。如果未创建索引,您将在 Firestore 的调试控制台中看到 “[cloud_firestore/failed-precondition] The query requires an index…” 错误。您可以通过单击错误消息中的链接来创建索引。

// Streamed document snapshots of geo query under given conditions.
final Stream<List<DocumentSnapshot<Map<String, dynamic>>>> stream =
    GeoCollectionReference<Map<String, dynamic>>(typedCollectionReference).within(
  center: center,
  radiusInKm: radiusInKm,
  field: field,
  geopointFrom: geopointFrom,
  queryBuilder: queryBuilder,
);

GitHub

查看 Github