Flutter地理位置编码与Firestore集成插件geohash_plus_firestore的使用

发布于 1周前 作者 yuanlaile 来自 Flutter

Flutter地理位置编码与Firestore集成插件geohash_plus_firestore的使用

geohash_plus_firestore 是一个用于在Firestore中进行地理查询的插件。它基于可定制的Geohash库 geohash_plus 进行地理编码。默认的编码是4位Base16编码,因此与传统的Geohash相比,在不同缩放级别下精度差异较小,搜索时损失较少。此外,该插件提供了两种查询方法,使得构建避免Firestore限制的查询变得更加容易。

geohash_plus 允许你自定义每个字符的位数和转换字母表。这意味着你可以根据需求选择Base16或其他编码方式,而无需更改转换算法,传统Geohash通常是Base32编码。

添加文档

首先,创建一个 GeohashStore 对象,并通过指定数据属性将其添加到Firestore中。

final store = GeohashStore(57.64911, 10.40744);
db.collection('user').add({
  'location': store.data,
  'name': 'test1',
  'age': 30,
});

搜索数据

你可以通过指定区域坐标来搜索文档内的矩形范围。

final col = db.collection('user');
final bounds = LatLngBounds(
  northEast: LatLng(57.650, 10.408),
  southWest: LatLng(57.649, 10.407),
);
final result = await GeohashQuery.withinBounds(bounds,
    query: col, field: 'location');

查询

query 可以传递为 CollectionReferenceQuery。查询类型可以是数组或排序,分别使用 arrayContainsAnyorderBy 内部实现。根据使用的查询类型选择不同的类型。默认类型为数组,通信量较少。

final query = db.collection('user').where('age', isGreaterThan: 35);
final result = await GeohashQuery.withinBounds(bounds,
    query: query, field: 'location', type: QueryType.array);

完整示例

以下是一个完整的示例,展示了如何在Flutter应用中使用 geohash_plus_firestore 插件进行地理编码和查询。

import 'dart:math';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:fake_cloud_firestore/fake_cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart' hide LatLngBounds;
import 'package:latlong2/latlong.dart' as latlong2;

import 'package:geohash_plus_firestore/geohash_plus_firestore.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  [@override](/user/override)
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final _controller = MapController();
  final _fireStore = FakeFirebaseFirestore();
  final _searched = <String>[];
  final _circles = <String, CircleMarker>{};
  final _polygons = <String, Polygon>{};

  [@override](/user/override)
  void initState() {
    setup();
    super.initState();
  }

  void setup() async {
    final col = _fireStore.collection('users');
    final user = await col.limit(1).get();
    if (user.size == 0) {
      Future.wait(
        Iterable.generate(50).map((i) {
          final store = GeohashStore(
            51.4 + Random().nextDouble() / 5,
            -0.3 + Random().nextDouble() / 2,
          );
          return col.add({
            'location': store.data,
            'name': 'user_$i',
          });
        }),
      );
    }

    _controller.mapEventStream.listen((event) async {
      if (event.zoom < 10) return;
      if (event is MapEventMoveEnd) {
        final ne = _controller.bounds?.northEast;
        final sw = _controller.bounds?.southWest;
        if (ne == null || sw == null) return;
        final bounds = LatLngBounds(
          northEast: LatLng(ne.latitude, ne.longitude),
          southWest: LatLng(sw.latitude, sw.longitude),
        );
        final result = await GeohashQuery.withinBounds(bounds,
            query: col, field: 'location', clip: false, exclude: _searched);
        _searched.addAll(result.searched);
        for (var doc in result.documents) {
          final pos = doc.get('location.point') as GeoPoint;
          setState(() {
            _circles[doc.id] = CircleMarker(
              point: latlong2.LatLng(pos.latitude, pos.longitude),
              radius: 10,
              color: Colors.red,
            );
          });
        }
        for (var geohash in result.searched) {
          final bounds = GeoHash.decode(geohash, bits: 4).bounds;
          setState(() {
            _polygons[geohash] = Polygon(
              points: [
                latlong2.LatLng(bounds.northEast.latitude, bounds.northEast.longitude),
                latlong2.LatLng(bounds.northWest.latitude, bounds.northWest.longitude),
                latlong2.LatLng(bounds.southWest.latitude, bounds.southWest.longitude),
                latlong2.LatLng(bounds.southEast.latitude, bounds.southEast.longitude),
              ],
              borderColor: Colors.black45,
              borderStrokeWidth: 3,
              color: Colors.green.withOpacity(0.2),
              isFilled: true,
            );
          });
        }
      }
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: FlutterMap(
          mapController: _controller,
          options: MapOptions(
            center: latlong2.LatLng(51.509364, -0.128928),
            zoom: 14,
          ),
          nonRotatedChildren: [
            AttributionWidget.defaultWidget(
              source: 'OpenStreetMap contributors',
              onSourceTapped: null,
            ),
          ],
          children: [
            TileLayer(
              urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
              userAgentPackageName: 'com.example.app',
            ),
            CircleLayer(
              circles: _circles.values.toList(),
            ),
            PolygonLayer(
              polygons: _polygons.values.toList(),
            ),
          ],
        ),
      ),
    );
  }
}

更多关于Flutter地理位置编码与Firestore集成插件geohash_plus_firestore的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter地理位置编码与Firestore集成插件geohash_plus_firestore的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用geohash_plus_firestore插件来实现地理位置编码并与Firestore集成的示例代码。

1. 添加依赖

首先,在你的pubspec.yaml文件中添加geohash_plus_firestore依赖:

dependencies:
  flutter:
    sdk: flutter
  geohash_plus_firestore: ^最新版本号  # 请替换为实际的最新版本号
  cloud_firestore: ^最新版本号  # 请替换为实际的最新版本号

然后运行flutter pub get来安装依赖。

2. 初始化Firestore

在你的应用入口文件(通常是main.dart)中初始化Firestore:

import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:geohash_plus_firestore/geohash_plus_firestore.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

3. 使用geohash_plus_firestore插件

在你的页面或组件中,使用geohash_plus_firestore插件来编码地理位置并存储到Firestore中。

import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:geohash_plus_firestore/geohash_plus_firestore.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String? geohash;
  double? latitude;
  double? longitude;

  final CollectionReference<Map<String, dynamic>> firestoreCollection =
      FirebaseFirestore.instance.collection('locations');

  Future<void> getCurrentLocation() async {
    bool serviceEnabled;
    LocationPermission permission;

    // Test if location services are enabled.
    serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      return Future.error('Location services are disabled.');
    }

    permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      permission = await Geolocator.requestPermission();
      if (permission == LocationPermission.denied) {
        return Future.error('Location permissions are denied');
      }
    }

    if (permission == LocationPermission.deniedForever) {
      return Future.error(
          'Location permissions are permanently denied, we cannot request permissions.');
    }

    Position position = await Geolocator.getCurrentPosition(
        desiredAccuracy: LocationAccuracy.high);

    setState(() {
      latitude = position.latitude;
      longitude = position.longitude;
      geohash = GeoHash.encode(latitude!, longitude!);
    });

    // Save to Firestore
    await firestoreCollection.add({
      'latitude': latitude,
      'longitude': longitude,
      'geohash': geohash,
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('GeoHash Plus Firestore Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('Latitude: ${latitude ?? 'Loading...'}'),
            Text('Longitude: ${longitude ?? 'Loading...'}'),
            Text('GeoHash: ${geohash ?? 'Loading...'}'),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: getCurrentLocation,
              child: Text('Get Current Location'),
            ),
          ],
        ),
      ),
    );
  }
}

4. 权限配置

确保在AndroidManifest.xmlInfo.plist中配置了必要的权限。

Android (AndroidManifest.xml)

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

iOS (Info.plist)

<key>NSLocationAlwaysUsageDescription</key>
<string>Need location always permission</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Need location when in use permission</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Need location always and when in use permission</string>

5. 运行应用

现在你可以运行你的Flutter应用,它将请求位置权限,获取当前位置,生成GeoHash,并将其存储到Firestore中。

请确保你的Firebase项目已经配置好,并且你的应用有正确的Firebase凭证。

回到顶部