🌋

🌍【Flutter】geoflutterfire_plusの開発に参加している話🌍

2023/01/14に公開・約8,600字

概要

geoflutterfire_plus の開発

geoflutterfire_plus という geoflutterfire の後継に当たる OSS パッケージの開発と pub.dev への公開を目指します!

背景

既存の geoflutterfire というパッケージは、主に以下のようなことができます。

  • 緯度・経度から Geohash という文字列を計算し、かんたんに Cloud Firestore に保存する
  • 保存された Geohash を用いて「あるコレクションに対して、与えられた中心位置から半径 R km のドキュメントを取得する」ようなクエリをかんたんに書く

https://pub.dev/packages/geoflutterfire
地図上で現在位置に応じてデータを取得・表示するようなアプリで広く使われています。
https://zenn.dev/wakanao/articles/65972fe4e0894b
とても有用で素敵な OSS ですが、残念ながら以下のような問題があります

  • 2022 年末時点で 10 ヶ月開発が止まっており、肝心の cloud_firestore(2022 年末時点では 4.3.0 が最新)パッケージもかなり古いバージョンにしか対応していない
    • アプリの他の機能で最新のバージョンの cloud_firestore を使用していると、依存性の解決の際にコンフリクトが起きて、geoflutterfire は使用できない
    • geoflutterfire のために最新の cloud_firestore を使用することを諦めなければならない

1週目(調べ物 : ジオハッシュについて)

  • 一般に「ハッシュ」ってどんなもの?
    • 同じ入力に対して固定長のデータが返される
      ハッシュ (hash) とは、入力データ (文字列、ファイルなど) を元に、固定長の値 (ハッシュ値) を計算する処理です。ハッシュ関数は、入力に対して一意な出力を生成するため、データの整合性を検証するために使われる。
    • データの圧縮
      ハッシュ値はデータを圧縮するために使われることもある。暗号ハッシュ関数としてはSHA-256,SHA-512、そして bcrypt や scryptなどが有名。

https://www.pc-master.jp/security/hash-kansu.html

  • Geohashって何?

    • 地点の管理
      地理空間情報を扱う際、異なる地点を表す座標をハッシュ値として表現することで、地点を効率的に管理することができる。
      例えば、地図アプリでは、ユーザーが地図上で検索を行ったり、地図上にマーカーを表示することができる。これらを実現するために、地図上のそれぞれの地点を表す座標を扱う必要があり、そのような場合には、ジオハッシュを使うことで、地点を効率的に管理することができる。
    • 地点検索の効率化
      また、ジオハッシュを使うことで、地点を検索する際にも効率的になる。例えば、特定のエリア内にある地点を検索する場合には、そのエリアを表すジオハッシュ値を使って検索することができる。これにより、地点を検索する際に、全ての地点を検索する必要がなくなり、検索を高速化することができるのである。
  • Firestore で位置情報のクエリを書くときに Geohash を使う理由(モチベーション)は何?

    • なぜ緯度・経度でクエリしない?
      Firestore は、位置情報を扱う際に、地図上のある点からの距離を求めるために、地球を球体として扱うので、単純な緯度・経度でのクエリでは、球体上の座標を扱うことができず、精度が低くなってしまう。

    • Geohashだと?
      ジオハッシュを使ったクエリでは、位置情報を精度良く扱うことができます。ジオハッシュは、地球を二次元平面上に射影したものを用いるため、地球を球体として扱う必要がない。そのため、ジオハッシュを使ったクエリでは、単純な緯度・経度でのクエリよりも精度が良くなる。

  • Geohash の精度って何?Geohash の精度は何を見たら分かる?

https://firebase.google.com/docs/firestore/solutions/geoqueries
https://qiita.com/yabooun/items/da59e47d61ddff141f0c
https://www.fuzina.com/blog/2019/12/10/geohashで範囲検索を行う.html
https://yuroyoro.hatenablog.com/entry/20100115/1263526125
https://github.com/nfrawley93/dart_geohash/blob/master/lib/dart_geohash.dart

次週に続く。。。

2週目(調べ物:geoflutterfire_pulsの中身について)

https://github.com/KosukeSaigusa/geoflutterfire_plus

  • math.dart にはざっくり何が書かれている?

    • 距離計算に関わるメソッド群
      距離やベアリングの計算など、地理座標に関連する計算を実行するために使用される、通信を伴わない数学関数とクラスが含まれている。
      (以下、開発チームメンバーさんのまとめより抜粋🙏)
    • encode 緯度・経度からGeoHashを返す
    • decodebbox GeoHashから最大・最小×緯度・経度4種類をListにして返す
    • decode GeoHashから最大・最小×緯度・経度4種類をMapにして返す
    • neighbors GeoHashから隣り合う8箇所のGeoHashをListにして返す
    • setPrecision 距離に対して対応するGeoHashの桁数を返す
    • kmDistance 2点間の距離を返す
    • kmCalcDistance 2点間の距離を返す
  • geo_fire_point.dart にはざっくり何が書かれている?

    • 緯度・経度を元にgeohash等のデータがまとまったモデル
      GeoFirePoint : Firestoreに対応したフィールド
      /// 自身のgeohashを返す
      String get geohash => encode(latitude: latitude, longitude: longitude);
      
      /// 隣接するgeohashを返す
      List<String> get neighbors => neighborsOfGeohash(geohash);
      
      /// 隣接するgeopointを返す
      GeoPoint get geopoint => GeoPoint(latitude, longitude);
      
      /// 座標を返す
      Coordinates get coordinates => Coordinates(latitude, longitude);
      
      
  • geo_collection_reference.dart にはざっくり何が書かれている?

    • firebaseを使ってデータのやり取りをするメソッド群
      ▼例 : 中心からの距離 (キロメートル) を含む geo クエリの結果を通知
      /// * [center] Center point of detection.
      /// * [radiusInKm] Detection range in kilometers.
      /// * [field] Field name of cloud_firestore document.
      /// * [geopointFrom] Function to get cloud_firestore [GeoPoint] instance from
      /// the object (type T).
      /// * [queryBuilder] Specifies query if you would like to give additional
      /// conditions.
      /// * [strictMode] Whether to filter documents strictly within the bound of
      /// given radius.
      Stream<List<GeoDocumentSnapshot<T>>> withinWithDistance({
        required GeoFirePoint center,
        required double radiusInKm,
        required String field,
        required GeoPoint Function(T obj) geopointFrom,
        Query<T>? Function(Query<T> query)? queryBuilder,
        bool strictMode = false,
      }) {
        final collectionStreams = _collectionStreams(
          center: center,
          radiusInKm: radiusInKm,
          field: field,
          queryBuilder: queryBuilder,
        );
      
        final mergedCollectionStreams = _mergeCollectionStreams(collectionStreams);
      
        final filteredGeoDocumentSnapshots =
        mergedCollectionStreams.map((queryDocumentSnapshots) {
          final geoDocumentSnapshots = queryDocumentSnapshots
          .map(
            (queryDocumentSnapshot) =>
        	_nullableGeoDocumentSnapshotFromQueryDocumentSnapshot(
              queryDocumentSnapshot: queryDocumentSnapshot,
              geopointFrom: geopointFrom,
              center: center,
            ),
          )
          // Removes null values.
          .whereType<GeoDocumentSnapshot<T>>();
      
          // Filter fetched geoDocumentSnapshots by distance from center point on
          // client side if strict mode.
          final filteredList = geoDocumentSnapshots.where(
        (geoDocumentSnapshot) =>
            !strictMode ||
            geoDocumentSnapshot.distanceFromCenterInKm <=
        	radiusInKm * _detectionRangeBuffer,
          );
      
          // Returns sorted list by distance from center point.
          return filteredList.toList()
        ..sort(
          (a, b) =>
              (a.distanceFromCenterInKm * 1000).toInt() -
              (b.distanceFromCenterInKm * 1000).toInt(),
        );
        });
        return filteredGeoDocumentSnapshots.asBroadcastStream();
      }
      
  • publicにしているファイルやAPIはどれ?

次週に続く。。。

3週目(調べ物:geo_collection_reference.dartの詳細について)

  • geo_collection_reference.dart って具体的に何をしているの?

    • 位置情報に基づいてドキュメントを検索
      Firestoreのクエリを使用して、位置情報に基づいてドキュメントを検索するためのクラス。GeoCollectionReference<T>と呼ばれ、FirestoreのCollectionReferenceクラスを拡張している。
  • geo_collection_reference.dart のメソッド群

    • add()
      指定されたデータを持つドキュメントを作成
    • setDocument()
      指定されたIDを持つドキュメントにデータを設定
    • delete()
      指定されたIDを持つドキュメントを削除
    • setPoint()
      指定されたIDを持つドキュメントのフィールドに、(latitude、longitude)のペアをGeoPointとして設定
    • within()
      指定された条件に基づいて、位置情報に基づいてドキュメントを検索し、その結果を通知
    • withinWithDistance()
      指定された条件に基づいて、位置情報に基づいてドキュメントを検索し、その結果を距離を含む形で通知
      • _collectionStreams(),_mergeCollectionStreams(),filteredGeoDocumentSnapshots
        withinWithDistance()メソッド内で使用されている内部関数で、位置情報に基づいてドキュメントを検索し、その結果を処理するために使用されている。
  • 197行目の rxdart はどんな目的で使われているの?

    Stream<List<QueryDocumentSnapshot<T>>> _mergeCollectionStreams(
      List<Stream<List<QueryDocumentSnapshot<T>>>> collectionStreams,
    ) =>
        Rx.combineLatest<List<QueryDocumentSnapshot<T>>,
            List<QueryDocumentSnapshot<T>>>(
          collectionStreams,
          (values) => [
            for (final queryDocumentSnapshots in values)
              ...queryDocumentSnapshots,
          ],
        );
    
    • Rx.combineLatest
      RxDartのAPIで、複数のストリームからの最新の値を結合し、新しいストリームを生成する
      具体的には、collectionStreamsというリストに格納された複数のストリームから最新の値を取得し、それらを結合し、新しいストリームを生成している。
      それらを展開し、List<QueryDocumentSnapshot<T>>に変換して返す。
      RxDartを使用して、複数のストリームからの最新の値を取得し、それらを結合し、新しいストリームを生成するために使用されている。

https://zenn.dev/heyhey1028/articles/1d120ae0c86098
▼3年前と少し古いので注意!!でもわかりやすかった☺️
https://qiita.com/gaku3601/items/7ec79ff38b0afd47e29a

次週に続く。。。

Discussion

ログインするとコメントできます