🌋
🌍【Flutter】geoflutterfire_plusの開発に参加している話🌍
概要
geoflutterfire_plus の開発
geoflutterfire_plus という geoflutterfire の後継に当たる OSS パッケージの開発と pub.dev への公開を目指します!
背景
既存の geoflutterfire というパッケージは、主に以下のようなことができます。
- 緯度・経度から Geohash という文字列を計算し、かんたんに Cloud Firestore に保存する
- 保存された Geohash を用いて「あるコレクションに対して、与えられた中心位置から半径 R km のドキュメントを取得する」ようなクエリをかんたんに書く
地図上で現在位置に応じてデータを取得・表示するようなアプリで広く使われています。 とても有用で素敵な OSS ですが、残念ながら以下のような問題があります
- 2022 年末時点で 10 ヶ月開発が止まっており、肝心の cloud_firestore(2022 年末時点では 4.3.0 が最新)パッケージもかなり古いバージョンにしか対応していない
- アプリの他の機能で最新のバージョンの cloud_firestore を使用していると、依存性の解決の際にコンフリクトが起きて、geoflutterfire は使用できない
- geoflutterfire のために最新の cloud_firestore を使用することを諦めなければならない
1週目(調べ物 : ジオハッシュについて)
- 一般に「ハッシュ」ってどんなもの?
- 同じ入力に対して固定長のデータが返される
ハッシュ (hash) とは、入力データ (文字列、ファイルなど) を元に、固定長の値 (ハッシュ値) を計算する処理です。ハッシュ関数は、入力に対して一意な出力を生成するため、データの整合性を検証するために使われる。 - データの圧縮
ハッシュ値はデータを圧縮するために使われることもある。暗号ハッシュ関数としてはSHA-256,SHA-512、そして bcrypt や scryptなどが有名。
- 同じ入力に対して固定長のデータが返される
-
Geohashって何?
- 地点の管理
地理空間情報を扱う際、異なる地点を表す座標をハッシュ値として表現することで、地点を効率的に管理することができる。
例えば、地図アプリでは、ユーザーが地図上で検索を行ったり、地図上にマーカーを表示することができる。これらを実現するために、地図上のそれぞれの地点を表す座標を扱う必要があり、そのような場合には、ジオハッシュを使うことで、地点を効率的に管理することができる。 - 地点検索の効率化
また、ジオハッシュを使うことで、地点を検索する際にも効率的になる。例えば、特定のエリア内にある地点を検索する場合には、そのエリアを表すジオハッシュ値を使って検索することができる。これにより、地点を検索する際に、全ての地点を検索する必要がなくなり、検索を高速化することができるのである。
- 地点の管理
-
Firestore で位置情報のクエリを書くときに Geohash を使う理由(モチベーション)は何?
-
なぜ緯度・経度でクエリしない?
Firestore は、位置情報を扱う際に、地図上のある点からの距離を求めるために、地球を球体として扱うので、単純な緯度・経度でのクエリでは、球体上の座標を扱うことができず、精度が低くなってしまう。 -
Geohashだと?
ジオハッシュを使ったクエリでは、位置情報を精度良く扱うことができます。ジオハッシュは、地球を二次元平面上に射影したものを用いるため、地球を球体として扱う必要がない。そのため、ジオハッシュを使ったクエリでは、単純な緯度・経度でのクエリよりも精度が良くなる。
-
-
Geohash の精度って何?Geohash の精度は何を見たら分かる?
- 文字列が長いほど精度が上がる
Geohashの精度は、そのGeohash文字列の長さによって決まる。長いGeohash文字列はより高精度で、短いGeohash文字列はより低精度である。
例えば、Geohash文字列 "xn76" は低精度であり、"xn76u" は高精度。
出典▼:地図上の位置情報を扱うには? Geohashの応用と「災害マップ」機能の裏側
- 文字列が長いほど精度が上がる
次週に続く。。。
2週目(調べ物:geoflutterfire_pulsの中身について)
-
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);
- 緯度・経度を元にgeohash等のデータがまとまったモデル
-
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(); }
- firebaseを使ってデータのやり取りをするメソッド群
-
publicにしているファイルやAPIはどれ?
- public
geo_collection_reference.dart のadd()
,setDocument()
,delete()
,within()
,withinWithdistasce()
geo_fire_point.dart - publicではない
math.dart
utils.dart
- public
次週に続く。。。
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を使用して、複数のストリームからの最新の値を取得し、それらを結合し、新しいストリームを生成するために使用されている。
-
▼3年前と少し古いので注意!!でもわかりやすかった☺️
次週に続く。。。
Discussion