Supabaseで地図や位置情報を扱うときの便利情報
PostGISを使うときはまずSupabaseの管理画面から拡張機能を有効にしてあげる。管理画面左側のメニューのデータベースからExtensions
に入ってPostGIS
を有効化する。また、以下のSQLでも有効化できる。
-- これで有効化
create extension postgis with schema extensions;
-- これで無効化
drop extension if exists postgis;
PostGISには様々データタイプや関数、そしてIndexがついてくる。とりあえず、今回はPoint型を使った簡単なサンプルを用意してみる。Point
型はシンプルな緯度経度のセットで、文字通り地図上に緯度経度で表される点を保存するための型。
まずはテーブル作成。今回は街中のレストラン一覧検索アプリ見たいなものを作る想定でレストラン情報を保存するテーブルを作る。このときlocation
カラムがPoint
型になっている。
create table if not exists public.restaurants (
id int generated by default as identity primary key,
name text not null,
location geography(POINT) not null
);
また、テーブルができたら効率よくそのテーブルをクエリーできるようにインデックスを貼るべき。location
カラムにこのようにしてインデックスを貼ることができる。
create index restaurants_geo_index
on public.restaurants
using GIST (location);
また、Point
以外にもPostGISには地図上の線や多角形を保存するための様々な型があるが、今回はそこまで深掘りしないので、深堀したいかたはこちらから。
テーブルができたらサンプルデータを挿入してみる。
SQLを使ってこんな感じでデータの挿入ができる。
insert into public.restaurants
(name, location)
values
('Supa Burger', st_point(-73.946823 40.807416)),
('Supa Pizza', st_point(-73.94581 40.807475)),
('Supa Taco', st_point(-73.945826 40.80629));
Supabaseのjsライブラリーを使ってデータを挿入することも可能。このときも上の注意事項と同じお作法。
const { error } = await supabase.from('restaurants').insert([
{
name: 'Supa Burger',
location: 'POINT(-73.946823 40.807416)',
},
{
name: 'Supa Pizza',
location: 'POINT(-73.94581 40.807475)',
},
{
name: 'Supa Taco',
location: 'POINT(-73.945826 40.80629)',
},
])
Supabaseの管理画面からテーブルを覗いてみるとlocation
カラムの値がこんな感じの人には読めないフォーマットになっているが、これで正しく保存されている。
0101000020E6100000A4DFBE0E9C91614044FAEDEBC0494240
実際に今度これらの情報を読み込む際はこの人には読めないフォーマットで値が返ってくるので、それをまたPoint(X, Y)
のフォーマットで返してくれるst_astext()
というデータベース関数に通すことで解決。
データを特定の位置から近い順に並び替えて取得する
特定のデータを近い順に並び替えるのはよくあるユースケースだと思う。例えば、食べログ的なアプリを作っていて現在地から近い順に周辺のレストランを検索したい時など。
Supabaseでこのような複雑なクエリーをするときはデータベース関数を使う。実際の関数がこんな感じ。latとlongが緯度経度で、それをそれぞれ小数で渡せる形になっている。
create or replace function nearby_restaurants(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.restaurants
order by location <-> st_point(long, lat)::geography;
$$;
フロントエンドからはこのように呼び出せる。
const { data, error } = await supabase.rpc('nearby_restaurants', {
lat: 40.807313,
long: -73.946713,
})
そうするとこんな感じのデータが返ってくる。dist_meters
にクエリーした地点からのメートルでの距離が入っている。
[
{
"id": 1,
"name": "Supa Burger",
"location": "POINT(-73.946823 40.807416)",
"dist_meters": 14.73033739
},
{
"id": 2,
"name": "Supa Pizza",
"location": "POINT(-73.94581 40.807475)",
"dist_meters": 78.28980007
},
{
"id": 3,
"name": "Supa Taco",
"location": "POINT(-73.945826 40.80629)",
"dist_meters": 136.04329002
}
]
綺麗に近い順に並び替えられていることがわかる。
四角で囲った場所の中のデータを取得してくる。
もう一つ地図を使ったユースケースでよく出てくるのが地図上の特定の四角で囲ったエリアにあるデータを引っ張ってくること。例えば、地図からレストランを検索する際に、ユーザーが地図をスワイプしたら新しくスワイプして表示されているエリアの中にあるレストラン一覧を引っ張ってきたいみたいなケース。
これもSupabaseで簡単に実装できる。まず、それ用のデータベース関数を用意。この関数には、検索したい四角いエリアの左下の点の緯度経度と右上の点の緯度経度をわたしている。
create or replace function restaurants_in_view(min_lat float, min_long float, max_lat float, max_long float)
returns setof record
language sql
as $$
select id, name, st_astext(location) as location
from public.restaurants
where location && ST_SetSRID(ST_MakeBox2D(ST_Point(min_long, min_lat), ST_Point(max_long, max_lat)),4326)
$$;
フロントエンドから上の関数を.rpc()
で呼んであげるだけでサクッとそのエリアにあるレストラン一覧を引っ張ってくることができる!
const { data, error } = await supabase.rpc('restaurants_in_view', {
min_lat: 40.807,
min_long: -73.946,
max_lat: 40.808,
max_long: -73.945,
})
返ってくるデータはこんな感じ
[
{
"id": 2,
"name": "Supa Pizza",
"location": "POINT(-73.94581 40.807475)"
}
]