Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

B1dca90d4b3ffd2ccd918774e1ba170d?s=47 hmatsu47
PRO
December 14, 2021

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

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

B1dca90d4b3ffd2ccd918774e1ba170d?s=128

hmatsu47
PRO

December 14, 2021
Tweet

More Decks by hmatsu47

Other Decks in Technology

Transcript

  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) 14 • 文化財などの近隣スポット検索(Supabase / PostGIS)

  15. Flutter mapbox_gl で困ったこと • いくつかトラブルが発生 ◦ Android で地図スタイルを切り替えるとエラーで落ちる ◦ mapbox_gl

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

    では時々、0.14.0 では必ずエラー発生 ▪ ただし機種によって発生したりしなかったりする可能性も ◦ ハードウェアアクセラレーション無効化はダメ ▪ 地図描画で使っているため ◦ Android では地図スタイル切り替えを無効にした 16 signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x8 Cause: null pointer dereference
  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. ストアドファンクション 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
  21. ストアドファンクション 21 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;
  22. 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. 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. 参考情報 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