GeoHashを可視化できるアプリを作ってみた

はじめに こんにちは、Swift Advent Calendar 2024の19日目を担当しますfummicc1です。 この記事では、ジオハッシュ(GeoHash)を可視化できるアプリについての話を書きました。 Swift Advent CalendarなのでSwiftに関連する話も書いたつもりですが、Swift以外の話も含まれているのであらかじめご了承いただけると嬉しいです。 この記事に書いてあること ジオハッシュについて ジオハッシュを可視化するアプリを作った話 ジオハッシュを確認できるアプリを作った際のSwiftに関連する話 ジオハッシュとは ジオハッシュは地図上の特定の区域を表現した文字列のことやそのアルゴリズムのことを指します。世界地図に対して「経度を2分割→緯度を2分割」という処理を繰り返すことで生成されるビット列をハッシュ化することで文字列を生成します。 ジオハッシュの例: 区域ごとに一意の文字列が割り当てられる ジオハッシュの生成では、境界を区切って0,1を割り当てる作業を緯度・経度ごとに行います。経度だけに絞ってみると下記のように分割されていきます。 経度を1bitで分割 経度を2bitで分割 経度を3bitで分割 どこまで分割を行うかでビット列の長さが変わり、長ければ長いほど狭い区域を表現するジオハッシュ文字列となります。 ジオハッシュのビット列は、経度と緯度それぞれについてのビット列を交互に並べたものです。 例えば、経度のビット列が「11100」で,緯度のビット列が「10001」の場合、ジオハッシュは「1110100001」となります。 ビット列は5bitごとに32種類の英数字にマッピングされます。数字10個 + アルファベット22個からa,i,l,oを除いたものの合計32種類から構成されます。 ジオハッシュ生成の流れ このジオハッシュから、地点の緯度・経度の区域を特定することができます。 例えば、日本はおおよそ1桁のジオハッシュでは「w」もしくは「x」で表現されます。ジオハッシュの桁が長ければ長いほど、その文字列が示す区域が狭くなるので1桁のジオハッシュだと結構な領域を示しています。詳しい制度についてはこちらに記述がありました。 日本はおおよそwかxに属する 西日本付近でxからwに変わる ジオハッシュの特性を応用することで、「ある地点の近くにある場所」の探索を文字列の比較で実現ができることができます。(ただし、多少の精度の誤差があることに注意が必要です。) ジオハッシュをSwiftで実装したい 勉強も兼ねてGeoHashSwiftというリポジトリに実装しました。ですが、既にGeoHashというSwiftで実装されたライブラリがあったのでそちらも参考に実装をしました。 ジオハッシュを可視化するアプリが欲しかった ジオハッシュのライブラリを作った後に実装が正しいことを確認する必要があったのですが、文字列を見ても正しいジオハッシュかどうかが直感的に分からなかったので入力した緯度経度から生成されるジオハッシュを簡単に確認できるアプリを作りました。 検索機能もつけてみました。(手元で動かすととても重いです) ビット列の長さをスライダーで変えられることができて、マップの中心のジオハッシュと周囲の区域のジオハッシュが表示されるアプリとなっています。 周囲のジオハッシュは幅優先探索で5ステップ先の周囲までを取得しています。全てのジオハッシュを取得すると2 ** ビット列の長さのジオハッシュを取得することになるので、計算が重くなったので断念しました。 5ステップ先のジオハッシュを取得するだけでも、Map上で動作確認をすると処理が重いと感じたので改善に取り組んだことをメモします。 Viewに記述された処理をメインスレッド外で実行する SwiftUIのViewはMainActorであるため、Viewに直接計算メソッドを書いている場合、処理がメインスレッド上で実行されてしまいます。今回はGlobalActorを定義してメソッドをactorで隔離させることでメインスレッドで処理が走らないようにしました。 @globalActor struct ComputationActor { actor ActorType {} static let shared = ActorType() } struct ContentData: Identifiable { var bound: [CLLocationCoordinate2D] = [] var geohash: GeoHash var id: GeoHash { geohash } } struct ContentView: View { // ....

December 19, 2024 · 2 min · Fumiya Tanaka

SwiftUIのEmptyViewのonAppearは呼ばれることがある

SwiftUIのEmptyViewはレイアウトに影響しないことで知られています。 EmptyViewはViewが存在しないことを意味するのでonAppearメソッドも呼ばれないと想定されます。 ただ、RootViewにEmptyViewのみを配置した場合はonAppearメソッドは呼ばれるので注意が必要です。 @main struct SampleApp: App { var body: some Scene { WindowGroup { ContentView() // ContentViewだけを表示するとonAppearは呼ばれる // ContentView2() // ContentView2だけを表示するとonAppearは呼ばれない } } } struct ContentView: View { var body: some View { EmptyView() .onAppear { print("onAppear is called") } } } struct ContentView2: View { var body: some View { EmptyView() EmptyView() .onAppear { print("onAppear is not called") } } } ContentViewではView HierarchyにEmptyViewがあって、ContentView2ではView HierarchyにEmptyViewがないのでonAppearは呼ばれないみたいです。 ContentView ContentView2 onAppear is called onAppear is not called おわりに 簡潔な記事ですが、EmptyViewのonAppearはRootViewに単体で配置すると呼ばれるという備忘録でした。

December 16, 2024 · 1 min · Fumiya Tanaka

[SwiftUI] AsyncImage that can perform downsampling

Prologue This article is originated from AsyncImage that can perform downsampling. AsyncImage is a SwiftUI component to show Image fetched from internet resource. It is available since iOS15 like the following. AsyncImage(url: url) { phase in switch phase { case .empty: ProgressView().progressViewStyle(.circular) case .failure: Text("Error") case .success(let image): image .resizable() .frame(width: 56, height: 56) @unknown default: fatalError() } } Such a simple way is beneficial to build a UI layout with ease....

February 3, 2023 · 4 min · Fumiya Tanaka