🍚

FlutterKaigi 2024 有明周辺のランチマップ (あくまで非公式です)

FlutterKaigi 2024 では非公式という位置付けながら、ランチマップ機能の提供を試みました。その裏側で PostGIS と Database Function が一役を買った話に触れさせていただきます。

By jiyuujin at

#Supabase
#PostgreSQL
#FlutterKaigi
#Flutter

FlutterKaigi 2024

(OGP 画像)

https://2024.flutterkaigi.jp/

昨日 FlutterKaigi 公式 medium からも、有明周辺のランチ事情についてのアナウンスがなされました。

https://medium.com/flutterkaigi/flutterkaigi-2024-有明セントラルタワーホール-カンファレンスへのアクセス-ランチ情報-68ee1681c159

昨年 FlutterKaigi 2023 公式アプリで好評だったランチマップについて、今年はチームメンバーの負荷を考慮し提供されておりません。

ちなみに昨年のランチマップでは、会場スポンサーとして協賛いただいていた 株式会社ナビタイムジャパン のマップと合わせ、提供されていました。

FlutterKaigi 2023 で提供していたランチマップのスクリーンショット。

カンファレンスアプリの一機能としてランチマップが提供されていました。

初期コンテンツとして読み込んだ時 右へスクロールした時 動線

そこで今回、非公式という形で有明周辺のランチマップを設計・実装してみました。

情報出所は 食べログ から参照させてもらっています。

そもそも、こうしたランチマップを制作したきっかけは、有明近辺にそのようなスポットが少ないのでは無いかという疑問から。

結論として全くそんな心配は無く、よしなりにお店をピックアップできる環境と認識できました。

ここで今回捕捉するのは、下記 3 点の中で 2 点目を取り上げます。

  • Supabase の DB 設計
  • Database Function の使いどころ
  • 気合いで seed.sql を書いて、データを流し込む

DB 設計

位置情報 (緯度や経度) をいかにして記録するのか。

緯度、経度についてぞれぞれ double 型 として格納する必要はありません。

Postgres 内で地理データを操作できるようにする Postgres 拡張機能 PostGIS のお世話になります。

https://supabase.com/docs/guides/database/extensions/postgis

緯度、経度のシンプルなセットから構成されている Point 型 を使って、位置情報を DB に保存することを目指します。

create table if not exists public.shops (
  id uuid not null primary key default uuid_generate_v4(),
  name varchar(100) not null,
  comment varchar(500),
  link_url varchar(500),
  location geography(POINT) not null,
  is_open bool not null,
  created_at timestamp with time zone default timezone('utc' :: text, now()) not null,
  updated_at timestamp with time zone default timezone('utc' :: text, now()) not null
);

Database Function の使いどころ

まず今回 FlutterKaigi 2024 では、有明セントラルタワーホール&カンファレンスにて、午前の部から終日にわたって開催されます。

https://ariake-hall.jp/

この開催場所から近い順に直線距離 (と、歩いてかかる想定時間) を算出することを目指します。しかし、この直線距離の算出を Flutter アプリ内で計算するのは、非常にたいへんで複雑なロジックが求められます。

そのロジックの責務を Postgre 関数で気軽に書くことのできる Database Function に任せてしまおうというのが、今回の狙いのひとつになります。

https://supabase.com/docs/reference/javascript/rpc

余談: PostGIS の例で参照されており、そちらの考えを参考にさせていただきました。

https://supabase.com/docs/guides/database/extensions/postgis

create or replace function nearby_shops(lat float, long float)
returns setof record
language sql
as $$
  select id, name, st_astext(location) as location, st_distance(location, st_point(long, lat)::geography) as dist_meters
  from public.shops
  order by location <-> st_point(long, lat)::geography;
$$;

関数を作成したら、あとは Flutter アプリから supabaseClient.rpc で読み込みます。

なお、centerLatcenterLng には、有明セントラルタワーホール&カンファレンスの緯度や経度が入ります。

Stream? stream({
  Shop? condition,
}) {
  final data = supabaseClient.rpc('nearby_shops',
      params: {'lat': centerLat, 'long': centerLng}).asStream();

  return data;
}

歩いてかかる想定時間は、取得した直線距離を基に、Flutter アプリ内で計算します。

ちなみに、時速 4km と仮定し、徒歩の時間を計算しました。

seed.sql を書く

基本的には新規のデータを追加する際の SQL を書けば良いのですが、ここでの肝は Point 型 の書き方になります。

insert into public.shops
(name, comment, link_url, location, is_open)
values
  ('春華秋実', '', '', st_point(139.791585, 35.6319108), true);

実際に値を渡す順番として st_point() では、経度の次に緯度が入ります。

https://postgis.net/docs/ja/ST_Point.html

st_point(<経度>, <緯度>)

数学における座標軸で x 軸に経度が入り、y 軸に緯度が入ることを想像してください。

あと、こればかりは気合いで SQL を書いていくのみ。ですがこちらは割愛させていただきます。

最後に

あくまで Supabase プロジェクトについては、課金プランまで上げる措置をとっていませんので、ある一定の時間を経ればアクセスできなくなる可能性があります。

非営利な技術コミュニティに対し、コミット可能なコミュニティプランを検討して欲しいなどと考える場面も多い今日このころ。

というわけで、今回も FlutterKaigi コアスタッフのひとりとして 前・当日を迎えましょう