FlutterでMapboxとSupabase(PostGIS)を使ってみた

13.2K Views

April 16, 22

スライド概要

MIERUNE Meetup mini #01 2021/12/16 LT 資料

profile-image

Qiita や Zenn でいろいろ書いてます。 https://qiita.com/hmatsu47 https://zenn.dev/hmatsu47 MySQL 8.0 の薄い本 : https://github.com/hmatsu47/mysql80_no_usui_hon Aurora MySQL v1 → v3 移行計画 : https://zenn.dev/hmatsu47/books/aurora-mysql3-plan-book https://speakerdeck.com/hmatsu47

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

Flutter で Mapbox と Supabase(PostGIS)を 使ってみた MIERUNE Meetup mini #01 2021/12/16 まつひさ(hmatsu47)

2.

自己紹介 松久裕保(@hmatsu47) ● https://qiita.com/hmatsu47 ● 現在のステータス: ○ 名古屋で Web インフラのお守り係をしています ○ Flutter は個人的に触り始めたところです ○ GIS も詳しくないです ■ ようやく「丸い地球」を扱うことができるようになった MySQL 8.0 で少し 触った程度 2

3.

Flutter とは? ● Google 製の UI フレームワーク ○ 使う言語は Dart ○ 当初はクロスプラットフォームモバイルアプリ開発用 ○ その後、Web や Windows / macOS / Linux も対象に 3

4.

Flutter を試そうと思ったきっかけ ● 以前 Qiita でバズったこのサイト ○ https://korette.jp/ 4

5.

Flutter を試そうと思ったきっかけ ● 以前 Qiita でバズったこのサイト ○ https://korette.jp/ ○ サポーターズの一員として大量にクイズ投稿 ○ その後、コロナ禍で観光地の状況が一変 ○ コロナが落ち着いた隙をみながら問題メンテナンスの旅へ ○ 旅のお供として、情報収集・整理のためのアプリが欲しい ○ 作ることにした 5

6.

余談ですが ● ご本人は起業されて・・・ ○ https://ambirise.jp/ 6

7.

Flutter でモバイル地図アプリ ● 主な選択肢(LIKES 順) ○ google_maps_flutter(Google マップ) ○ flutter_map(Leaflet / Azure Maps・OpenStreetMap など) ○ mapbox_gl(Mapbox) ○ flutter_osm_plugin(OpenStreetMap) 7

8.

Flutter でモバイル地図アプリ ● 主な選択肢(LIKES 順) ○ google_maps_flutter(Google マップ) ○ flutter_map(Leaflet / Azure Maps・OpenStreetMap など) ○ mapbox_gl(Mapbox) ○ flutter_osm_plugin(OpenStreetMap) ● 選んだ理由:Google マップ以外でたまたま見つけた ○ 他の 2 つは後で知った 8

9.

作っているアプリ(maptool) ● https://github.com/hmatsu47/maptool ○ 状態管理ライブラリは使わず StatefulWidget だけでどこまでいけるかチャレンジ中 ● 実装済みの主な機能 ○ 訪問(予定)地へのピン立て(登録) ○ 登録ピンと関連づけて写真撮影 ○ 登録ピンの検索 ○ 地図スタイル切り替え ○ 文化財などの近隣スポット検索 9

10.

作っているアプリ(maptool) ● 訪問(予定)地へのピン立て(登録) 注:画面は少し古めのバージョン です(以降同じ) 10

11.

作っているアプリ(maptool) ● 登録ピンと関連づけて写真撮影 11

12.

作っているアプリ(maptool) ● 登録ピンの検索 12

13.

作っているアプリ(maptool) ● 地図スタイル切り替え 13

14.

作っているアプリ(maptool) ● 文化財などの近隣スポット検索(Supabase / PostGIS) 14

15.

Flutter mapbox_gl で困ったこと ● いくつかトラブルが発生 ○ Android で地図スタイルを切り替えるとエラーで落ちる ○ mapbox_gl 0.14.0 で MapboxMap クラスに実装されている onStyleLoadedCallback の動きが変化 ■ 地図スタイル切り替え直後の言語指定・ピン再表示に困る ○ 他にも細かいトラブル発生 ■ プラットフォームによる差異(対応/非対応)など(詳細は省略) 15

16.

Flutter mapbox_gl で困ったこと ● Android で地図スタイルを切り替えるとエラーで落ちる signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x8 Cause: null pointer dereference ○ ○ mapbox_gl 0.13.0 では時々、0.14.0 では必ずエラー発生 ■ ただし機種によって発生したりしなかったりする可能性も ○ ハードウェアアクセラレーション無効化はダメ ■ 地図描画で使っているため ○ Android では地図スタイル切り替えを無効にした 16

17.

Flutter mapbox_gl で困ったこと ● mapbox_gl 0.14.0 で MapboxMap クラスに実装されて いる onStyleLoadedCallback の動きが変化 ■ 地図スタイル切り替え直後の言語指定・ピン再表示に利用していた ○ 0.13.0 ではこれを使って言語の指定とピン再表示が可能だった ○ 0.14.0 で動かなくなる ○ 仕方がないので地図ウィジェット再表示の後に別途指定&再表示 ■ ときどきピン再表示に失敗(未解決) 17

18.

Flutter から Supabase の PostGIS を使う ● こちらの記事を参照 Flutter から Supabase の PostgreSQL with PostGIS を使ってみる https://qiita.com/hmatsu47/items/c3f9cafb499aedaca1f1 ○ Supabase を設定 ■ PostgreSQL で PostGIS を有効化 ■ テーブル作成 ■ ストアドファンクション作成 ○ Flutter から RPC でストアドファンクションを呼び出す 18

19.

テーブル CREATE TABLE category ( id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, categoryname text NOT NULL ); CREATE TABLE spot_opendata ( id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, category_id int REFERENCES category (id) NOT NULL, title text NOT NULL, describe text NOT NULL, location geometry(point, 4326) NOT NULL, prefecture text NOT NULL, municipality text NOT NULL, pref_muni text GENERATED ALWAYS AS (prefecture || municipality) STORED, 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 ); CREATE INDEX spot_location_idx ON spot_opendata USING GIST (location); CREATE INDEX spot_pref_idx ON spot_opendata (prefecture); CREATE INDEX spot_muni_idx ON spot_opendata (municipality); CREATE INDEX spot_pref_muni_idx ON spot_opendata (pref_muni); 19

20.

ストアドファンクション CREATE OR REPLACE FUNCTION get_spots(point_latitude double precision, point_longitude double precision, dist_limit int, category_id_number int) RETURNS TABLE ( distance double precision, category_name text, title text, describe text, latitude double precision, longitude double precision, prefecture text, municipality text ) AS $$ BEGIN RETURN QUERY 20

21.
[beta]
ストアドファンクション
SELECT ((ST_POINT(point_longitude, point_latitude)::geography <-> spot_opendata.location::geography) / 1000) AS
distance,
category.category_name,
spot_opendata.title,
spot_opendata.describe,
ST_Y(spot_opendata.location),
ST_X(spot_opendata.location),
spot_opendata.prefecture,
spot_opendata.municipality FROM spot_opendata
INNER JOIN category ON spot_opendata.category_id = category.id
WHERE
(ST_POINT(point_longitude, point_latitude)::geography <-> spot_opendata.location::geography) <= dist_limit
AND
(CASE WHEN category_id_number = -1 THEN true ELSE category.id = category_id_number END)
ORDER BY distance;
END;
$$ LANGUAGE plpgsql;

21

22.
[beta]
Flutter コード
import 'package:mapbox_gl/mapbox_gl.dart';
import 'package:supabase/supabase.dart';
import 'class_definition.dart';
SupabaseClient getSupabaseClient(String supabaseUrl, String supabaseKey) {
return SupabaseClient(supabaseUrl, supabaseKey);
}
Future<List<SpotCategory>> searchSpotCategory(SupabaseClient client) async {
final PostgrestResponse selectResponse = await client
.from('category')
.select()
.order('id', ascending: true)
.execute();
final List<dynamic> items = selectResponse.data;
final List<SpotCategory> resultList = [];
for (dynamic item in items) {
final SpotCategory category =
SpotCategory(item['id'] as int, item['category_name'] as String);
resultList.add(category);
}
return resultList;
}

22

23.
[beta]
Flutter コード
Future<List<SpotData>> searchNearSpot(SupabaseClient client, LatLng latLng,
int distLimit, int? categoryId) async {
final PostgrestResponse selectResponse =
await client.rpc('get_spots', params: {
'point_latitude': latLng.latitude,
'point_longitude': latLng.longitude,
'dist_limit': distLimit,
'category_id_number': (categoryId ?? -1)
}).execute();
final List<dynamic> items = selectResponse.data;
final List<SpotData> resultList = [];
for (dynamic item in items) {
final SpotData spotData = SpotData(
item['distance'] as num,
item['category_name'] as String,
item['title'] as String,
item['describe'] as String,
LatLng((item['latitude'] as num).toDouble(),
(item['longitude'] as num).toDouble()),
PrefMuni(item['prefecture'] as String, item['municipality'] as String));
resultList.add(spotData);
}
return resultList;
}

23

24.

まとめ ● Flutter でモバイル地図アプリ製作 ○ 細かいことを気にしなければ比較的簡単に作れる ○ Mapbox の場合は Android で苦労することが多そう ■ iOS だけ非対応の機能もいくつかあるが、全体的に(個人の印象) ● Flutter から Supabase の PostGIS を使う ○ クエリビルダで(GIS)関数が使えないのでストアドファンク ションを RPC で呼び出す 24

25.

参考情報 ● 関連ブログ記事 ○ https://qiita.com/hmatsu47/items/b98ef4c1a87cc0ec415d ○ https://zenn.dev/hmatsu47/articles/846c3186f5b4fe ○ https://zenn.dev/hmatsu47/articles/9102fb79a99a98 ○ https://zenn.dev/hmatsu47/articles/e81bf3c2bf00f8 ○ https://qiita.com/hmatsu47/items/e4f7e310e88376d54009 ○ https://qiita.com/hmatsu47/items/86a9c028bb5b3beeebdd ○ https://qiita.com/hmatsu47/items/53ea68769c4fc2d76450 ○ https://qiita.com/hmatsu47/items/c3f9cafb499aedaca1f1 25