Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Flutterでも地図を描きたいお話
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Ryotaro Onoue
July 24, 2023
Programming
730
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Flutterでも地図を描きたいお話
YOUTRUST x ゆめみ Flutter LT会@渋谷 #2
https://yumemi.connpass.com/event/287984/
Ryotaro Onoue
July 24, 2023
More Decks by Ryotaro Onoue
See All by Ryotaro Onoue
FlutterKaigi 2025 公式アプリ&WebサイトのCDについて
yumnumm
0
110
FlutterKaigi 2025 システム裏側
yumnumm
0
2.1k
Terraform+cloud-initで自宅サーバのLXDをIaCするお話
yumnumm
2
230
FlutterKaigi 2024における開発チームの取り組み と 2025への展望
yumnumm
0
96
Dart WebAssemblyを使ったWeb API on Cloudflare Workers
yumnumm
0
130
Apple Walletでパスを作るお話
yumnumm
0
250
私がやってきたアウトプット集
yumnumm
0
200
俺/私のこだわりデスク大大大自慢LT大会 (LTFes #12)
yumnumm
0
140
仕事以外で作成したプロダクトの自慢大会
yumnumm
0
190
Other Decks in Programming
See All in Programming
Webフレームワークの ベンチマークについて
yusukebe
0
170
決定論的オーケストレーションの設計と実装 / Design and Implementation of Deterministic Orchestration
nrslib
4
1.5k
AI時代のUIはどこへ行く?その2!
yusukebe
22
7.4k
正しくソフトウェアを作る、前提を疑うための認知の視点 / doubt-premise
minodriven
21
6.9k
Hunting Vulnerabilities in Symfony with LLMs
vinceamstoutz
0
550
Go1.27で導入されるジェネリクスメソッドでできること
mackee
0
160
Performance Engineering for Everyone
elenatanasoiu
0
200
エンジニアと一緒にテストコードの設計と実装を改善した話
mototakatsu
0
210
The NotImplementedError Problem in Ruby
koic
1
870
Snowflake Summitでの新機能 CoCo / CoWork / snowflake-summit-2026-overall-what-new-coco
tatsuhiro
1
160
TypeScript+Orvalで実現する型安全かつ堅牢でスケーラブルなマルチチャネル通知基盤 / TSKaigi Night talks ~after conference~
d0riven
0
350
Vue × Nuxt × Oxc どこまで使える?実運用の現在地
andpad
0
290
Featured
See All Featured
Redefining SEO in the New Era of Traffic Generation
szymonslowik
1
340
Agile Leadership in an Agile Organization
kimpetersen
PRO
0
170
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
240
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
Agile Actions for Facilitating Distributed Teams - ADO2019
mkilby
0
210
How to Talk to Developers About Accessibility
jct
2
240
Navigating Algorithm Shifts & AI Overviews - #SMXNext
aleyda
1
1.3k
Testing 201, or: Great Expectations
jmmastey
46
8.2k
Lessons Learnt from Crawling 1000+ Websites
charlesmeaden
PRO
1
1.3k
[SF Ruby Conf 2025] Rails X
palkan
2
1.1k
The Anti-SEO Checklist Checklist. Pubcon Cyber Week
ryanjones
0
170
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
Transcript
2023.7.24 FlutterͰਤΛඳ͖͍͓ͨ YUMEMI Inc. Flutter Engineer - Onoue Ryotaro
YOUTRUST × ΏΊΈ Flutter LTձ@ौ୩ #2
0. whoami
1. Today’s topic FlutterͰਤΛඳ͖͍ͨΜ͡Όɻ
https: / / github.com/ingen084/KyoshinEewViewerIngen ਤඳըʹؔͯ͠KyoshinEewViewer for Ingenͱ͍͏ ΦʔϓϯιʔεͷΞϓϦέʔγϣϯΛࢀߟʹ ͍ͯ͠·͢
(C#.NET, AvaloniaUI)
1. ཁ݅ ࡞Δલʹ .. . 1. άϦάϦಈ͔ͤΔ (=ΠϯλϥΫςΟϒͳ)Ϛοϓʹ͍ͨ͠ - Ҡಈɾ֦େॖখ
- ͳΊΒ͔ʹಈ͘Α͏ʹ͍ͨ͠ 2. දࣔྖҬΛ Controller ͔Βࣗ༝ʹૢ࡞Ͱ͖ΔΑ͏ʹ͍ͨ͠ - த৺ͷҢܦɾζʔϜͷมߋɾΞχϝʔγϣϯ 3. ҙͷ Widget Λਤͷ্ʹॏͶΒΕΔΑ͏ʹ͍ͨ͠ - PlatformViewΛ͏ͱɺϚοϓҠಈ࣌ʹগ͠ΕͯWidgetͷҐஔमਖ਼͕͔͔ΔͨΊ BAD - ը໘্ͷWidgetͷ͕ଟ͍ͱ Ңܦ <—> ը໘࠲ඪͷMethodChannelΛୟ͘ճ͕ଟ͘ͳΓ ΓऔΓʹ͕͔͔࣌ؒΔ ↑ ͜ͷ ڈͷ12݄ʙ2݄ࠒͷMapBoxΛͬͨΞϓϦ։ൃڠྗͰࢮ͵΄Ͳۤ࿑ͨ͠
2. ਤσʔλͷ४උ ͓ֆ͔͖લͷԼ४උ
2. ਤσʔλͷ४උ https: / / www.data.jma.go.jp/developer/gis.html https: / /
github.com/datasets/geo-countries/ blob/master/data/countries.geojson
None
߹ܭͰ2GB͘Β͍͋Δ! Ͱ͔͗͢Δ!!!! GeoJSON
Douglas–Peucker๏ Λ༻͍ͯɺઢͷ؆ૉԽΛߦ͏ 2-1. σʔλѹॖख๏ 1 ڐ༰ൣғͰਤσʔλΛখ͘͞Ͱ͖Δ
2-2. σʔλѹॖख๏ 2 A B A B Ңܦͷྻ ཁૉ͝ͱʹղ͢Δͱʜ
GeoJSONͱTopoJSONͷҧ͍ GeoJSON
2-2. ѹॖख๏ 2 A B A B Ңܦͷྻ ཁૉ͝ͱʹղ͢Δͱʜ
GeoJSONͱTopoJSONͷҧ͍ GeoJSON ॏෳ͍ͯͯ͠ϜμͰ σʔλαΠζతʹ͓ֆ͔͖తʹ A B
A B 0 arcs ʹղ͢Δ 1 GeoJSONͱTopoJSONͷҧ͍ TopoJSON
2-2. ѹॖख๏ 2
ݩσʔλ (FP+40/ .# %PVHMBVT1FVDLFS ద༻ޙ (FP+40/ .# ݩσʔλ (FP+40/ .#
3. ࠲ඪܥͷ͓ Ңܦͷ··͡Ό͓ֆ͔͖Ͱ͖ͳ͍!
3. ࠲ඪܥͷ͓ ؙ͍ٿΛͲ͏ͬͯฏ໘ʹඳ͘ͷ͔ .. . ? Ө࠲ඪ GlobalPoint(224.0, 101.4010)
class LatLng extends Point<double> { const LatLng(this.lat, this.lon) : super(lat, lon); final double lat; final double lon; } class GlobalPoint extends Point<double> { const GlobalPoint(super.x, super.y); factory GlobalPoint.fromLatlng( LatLng latLng, Projection proj ) = > proj.project(latLng); LatLng toLatLng(Projection proj) = > proj.unproject(this); } Ңܦ LatLng(35,135) ը໘࠲ඪ -PDBM Offset(0, 0)
3. ࠲ඪܥͷ͓ ͬ͟ͱ֓ཁ class LatLng extends Point<double> { const
LatLng(this.lat, this.lon) : super(lat, lon); final double lat; final double lon; } class GlobalPoint extends Point<double> { const GlobalPoint(super.x, super.y); factory GlobalPoint.fromLatlng( LatLng latLng, Projection proj ) = > proj.project(latLng); LatLng toLatLng(Projection proj) = > proj.unproject(this); } ը໘࠲ඪ -PDBM GlobalPoint(35,135) Ңܦ LatLng(35,135) Ө࠲ඪ GlobalPoint(224.0, 101.4010) class WebMercatorProjection implements Projection { static const int tileSize = 256; static const pixelsPerLonDegree = tileSize / 360; static const pixelsPerLonRadian = tileSize / (2 * math.pi); static const origin = Offset(128, 128); @override GlobalPoint project(LatLng latLng) { final siny = math.min( math.max( math.sin(latLng.lat * (math.pi / 180)), -0.9999, ), 0.9999, ); return GlobalPoint( origin.dx + latLng.lon * pixelsPerLonDegree, origin.dy + 0.5 * math.log((1 + siny) / (1 - siny)) * -pixelsPerLonRadian, ); } @override LatLng unproject(GlobalPoint point) { final lng = point.x - origin.dx / pixelsPerLonDegree; final latRadians = (point.y - origin.dy) / -pixelsPerLonRadian; final lat = 180 / math.pi * (2 * math.atan(math.exp(latRadians)) - math.pi / 2); return LatLng(lat, lng); } } WebϝϧΧτϧਤ๏Λ༻͍ͯӨ
3. ࠲ඪܥͷ͓ ͬ͟ͱ֓ཁ Ө࠲ඪ GlobalPoint(35,135) class LatLng extends Point<double>
{ const LatLng(this.lat, this.lon) : super(lat, lon); final double lat; final double lon; } class GlobalPoint extends Point<double> { const GlobalPoint(super.x, super.y); factory GlobalPoint.fromLatlng( LatLng latLng, Projection proj ) = > proj.project(latLng); LatLng toLatLng(Projection proj) = > proj.unproject(this); } Ңܦ LatLng(35,135) ը໘࠲ඪ -PDBM GlobalPoint(35,135) Ө࠲ඪ GlobalPoint(224.0, 101.4010) class ProjectedPolygonFeature { ProjectedPolygonFeature._({ required this.code, required this.bbox, required this.points, }); final int? code; final LatLngBoundary bbox; final List<GlobalPoint> points; } ىಈ࣌ʹɺಡΈࠐΈ - > ӨΛࡁ·͓͖ͤͯ ӨޙͷσʔλΛอ͓࣋ͯ͘͠
3. ࠲ඪܥͷ͓ ͬ͟ͱ֓ཁ Ө࠲ඪ GlobalPoint(35,135) class LatLng extends Point<double>
{ const LatLng(this.lat, this.lon) : super(lat, lon); final double lat; final double lon; } class GlobalPoint extends Point<double> { const GlobalPoint(super.x, super.y); factory GlobalPoint.fromLatlng( LatLng latLng, Projection proj ) = > proj.project(latLng); LatLng toLatLng(Projection proj) = > proj.unproject(this); } Ңܦ LatLng(35,135) ը໘࠲ඪ -PDBM GlobalPoint(35,135) 0 925 0 427 @freezed class MapState with _$MapState { const factory MapState({ @JsonKey(fromJson: _offsetFromJson, toJson: _offsetToJson) required Offset offset, required double zoomLevel, }) = _MapState; factory MapState.fromJson(Map<String, dynamic> json) = > _$MapStateFromJson(json); } extension MapStateProjection on MapState { / / / GlobalPointΛOffsetʹม͢Δ Offset globalPointToOffset(GlobalPoint point) { return Offset( (point.x - offset.dx) * zoomLevel, (point.y - offset.dy) * zoomLevel, ); } / / / ը໘࠲ඪOffsetΛGlobalPointʹม͢Δ GlobalPoint offsetToGlobalPoint(Offset offset) { return GlobalPoint( offset.dx / zoomLevel + this.offset.dx, offset.dy / zoomLevel + this.offset.dy, ); }a } 1. Offset(x,y)ͣΒ͢ 2. ݪΛத৺ʹzoomLevelഒ͢Δ
3. ࠲ඪܥͷ͓ ͬ͟ͱ֓ཁ Ө࠲ඪ GlobalPoint(35,135) class LatLng extends Point<double>
{ const LatLng(this.lat, this.lon) : super(lat, lon); final double lat; final double lon; } class GlobalPoint extends Point<double> { const GlobalPoint(super.x, super.y); factory GlobalPoint.fromLatlng( LatLng latLng, Projection proj ) = > proj.project(latLng); LatLng toLatLng(Projection proj) = > proj.unproject(this); } Ңܦ LatLng(35,135) ը໘࠲ඪ -PDBM GlobalPoint(35,135) @freezed class MapState with _$MapState { const factory MapState({ @JsonKey(fromJson: _offsetFromJson, toJson: _offsetToJson) required Offset offset, required double zoomLevel, }) = _MapState; factory MapState.fromJson(Map<String, dynamic> json) = > _$MapStateFromJson(json); } extension MapStateProjection on MapState { / / / GlobalPointΛOffsetʹม͢Δ Offset globalPointToOffset(GlobalPoint point) { return Offset( (point.x - offset.dx) * zoomLevel, (point.y - offset.dy) * zoomLevel, ); } / / / ը໘࠲ඪOffsetΛGlobalPointʹม͢Δ GlobalPoint offsetToGlobalPoint(Offset offset) { return GlobalPoint( offset.dx / zoomLevel + this.offset.dx, offset.dy / zoomLevel + this.offset.dy, ); }a } 925 427 0 0
3. ࠲ඪܥͷ͓ ͬ͟ͱ֓ཁ Ө࠲ඪ GlobalPoint(35,135) class LatLng extends Point<double>
{ const LatLng(this.lat, this.lon) : super(lat, lon); final double lat; final double lon; } class GlobalPoint extends Point<double> { const GlobalPoint(super.x, super.y); factory GlobalPoint.fromLatlng( LatLng latLng, Projection proj ) = > proj.project(latLng); LatLng toLatLng(Projection proj) = > proj.unproject(this); } Ңܦ LatLng(35,135) ը໘࠲ඪ -PDBM GlobalPoint(35,135) 0 925 0 427 @freezed class MapState with _$MapState { const factory MapState({ @JsonKey(fromJson: _offsetFromJson, toJson: _offsetToJson) required Offset offset, required double zoomLevel, }) = _MapState; factory MapState.fromJson(Map<String, dynamic> json) = > _$MapStateFromJson(json); } extension MapStateProjection on MapState { / / / GlobalPointΛOffsetʹม͢Δ Offset globalPointToOffset(GlobalPoint point) { return Offset( (point.x - offset.dx) * zoomLevel, (point.y - offset.dy) * zoomLevel, ); } / / / ը໘࠲ඪOffsetΛGlobalPointʹม͢Δ GlobalPoint offsetToGlobalPoint(Offset offset) { return GlobalPoint( offset.dx / zoomLevel + this.offset.dx, offset.dy / zoomLevel + this.offset.dy, ); }a }
4. ࣮ࡍʹFlutterͰ͓ֆ͔͖͢Δ CustomPainter × GestureDetectorͷເͷίϥϘϨʔγϣϯ
4-1. CustomPainter ͓ֆ͔͖Widget flutter/rendering
4-1. CustomPainterͷհ ͓ֆ͔͖Widget CustomPaint Widgetʹ CustomPainter classͷΠϯελϯεΛ͢ paintؔͰ canvas
ʹԿΛඳ͔͑͘Δ ࠶ඳը͢Δඞཁ͕͋Δ͔Ͳ͏͔
4-1. CustomPainter ͓ֆ͔͖Widget - ༷ʑͳਤܗΛඳըͰ͖Δ - ਤܗඳը࣌ʹϨΠΞτ͢Δඞཁ͕ͳ͍ͷͰ ۪ʹStackͰॏͶͨΓ͢ΔΑΓߴύϑΥʔϚϯε flutter/rendering
IUUQTEPDT fl VUUFSEFWDPPLCPPLF ff FDUTHSBEJFOUCVCCMFT
4-1. CustomPainter ͓ֆ͔͖Widget - ༷ʑͳਤܗΛඳըͰ͖Δ - ਤܗඳը࣌ʹϨΠΞτ͢Δඞཁ͕ͳ͍ͷͰ ۪ʹStackͰॏͶͨΓ͢ΔΑΓߴύϑΥʔϚϯε flutter/rendering
Flutter άϥϑͷ࣮͔ΒֶͿ Container ͱ Canvas ͷ͍͚ | CyberAgent Developers Blog https: / / developers.cyberagent.co.jp/blog/archives/36573/
͓ֆ͔͖Widget - ༷ʑͳਤܗΛඳըͰ͖Δ - ਤܗඳը࣌ʹϨΠΞτ͢Δඞཁ͕ͳ͍ͷͰ ۪ʹStackͰॏͶͨΓ͢ΔΑΓߴύϑΥʔϚϯε Flutter άϥϑͷ࣮͔ΒֶͿ Container
ͱ Canvas ͷ͍͚ | CyberAgent Developers Blog https: / / developers.cyberagent.co.jp/blog/archives/36573/ 4-1. CustomPainter Widget ਤܗඳըͰ͖ΔͷͰ ਤඳ͚Δ! -> Ͳ͏ͬͯಈ͔͢…?
ΠϯλϥΫςΟϒͳૢ࡞ΛՄೳʹ - ը૾ө૾ͷදࣔྖҬΛ֦େɾॖখɾҠಈɾճస͢Δͷʹ͑Δ - Listener - > GestureDetector -
> Transform Ϣʔβͷૢ࡞ࢹ දࣔௐ https: / / dartpad.dev/?id=e6f75e0d35a958257440ab514a4196af flutter/widgets 4-2. InteractiveViewer
ΠϯλϥΫςΟϒͳૢ࡞ΛՄೳʹ - ը૾ө૾ͷදࣔྖҬΛ֦େɾॖখɾҠಈɾճస͢Δͷʹ͑Δ - Listener - > GestureDetector -
> Transform Ϣʔβͷૢ࡞ࢹ දࣔௐ https: / / dartpad.dev/?id=e6f75e0d35a958257440ab514a4196af flutter/widgets - - Ҡಈɾ֦େͯ͠ ཁૉ͕ը໘֎ʹग़͔ͨͲ͏͔ΛผͰ͖ͳ͍ - Πϝʔδͱͯ͠WidgetΛը૾Խͯ͠ ͦΕΛࣸਅΞϓϦͰද͍ࣔͯ͠Δײ͡ - > දࣔྖҬʹ߹Θͤͯ࠷దԽͰ͖ͳ͍ - Controller͕ੜ͍͑ͯΔ͕ɺঢ়ଶΛߦྻͰอ͍࣋ͯ͠Δ - ਤ͚ͷૢ࡞Λૢ࡞͠ʹ͍͘ 4-2. InteractiveViewer
4-3-1. MapStateͱMapViewModel ΜͰͧ͘ʙ ঢ়ଶཧ InteractiveViewer WidgetͷதΛࢀߟʹͭͭ͠ ࣗલͰਤؔ࿈ͷؔΛ࣮͍ͯ͘͠ (with Riverpod)
- globalPointToOffset - handleScaleStart / handleScaleUpdate / . . . - animatedMoveTo(LatLng, Duration, Curve) - animatedZoomTo(double, Duration, Curve) - animatedApplyBounds (LatLngBoundary, Duration, Curve) ࣗ࡞
4-3-2. MapTouchHandlerWidget ΜͰͧ͘ʙ λονݕͱঢ়ଶཧ - λονͷݕग़෦ (ಁ໌) - Listener
- > GestureDetectorͰ Ϣʔβͷૢ࡞Λࢹ - ൃՐ࣌ʹ MapViewModelͷ .handleXXؔΛݺΜͰ ঢ়ଶΛߋ৽ͤ͞Δ ࣗ࡞
4-3-3. BaseMapPainter ΜͰͧ͘ʙ ਤඳը - ੈքਤɾຊਤΛCustomPainterͰ͓ֆ͔͖ - ֤छ࠷దԽ -
ζʔϜഒʹԠͯ͡Douglas-Peucker ๏ Λద ༻ͯ͠ઢΛ؆ૉԽ - ζʔϜϨϕϧ࣌ͷͷΛݮΒ͢ - > ύϑΥʔϚϯε͕ྑ͘ͳΔ ↑ Ωϟογϡ͓ͯ͘͠ - ը໘֎ͷϙϦΰϯඳը͠ͳ͍Α͏ʹ ࣗ࡞
4-3-4. 39 - stackͰॏͶ͍ͯ͘ Stack( children: [ <എܠ৭>, <ਤඳը>,
<ΖΖͷඳը> <δΣενϟʔݕ>, ], );
5. ·ͱΊ Μͩͧ! - ਤ͍͚͠ΕͲ ςΫ͍ ࣮͕͋ͬͨΓͯ͠ ͓͠Ζ͍ -
leaflet.js MapLibreͷਤؔ࿈ͷϥΠϒϥϦࢀߟʹͳΔ - FlutterͰਤΛඳ͍͍͘ײ͡ͷϥΠϒϥϦ͕ͳ͔ͬͨͷͰ ࠓޙ࡞͍͖͍ͬͯͨ - InteractiveViewerͷWidgetͷ෦࣮ΛோΊͯ ͦΕΛࢀߟʹ࣮͢Δ͜ͱ͕Ͱ͖Δ - SwiftUIͱ͔ͩͱ͜ͷٕ͑ͳ͍…? Φʔϓϯιʔεͷ͋Γ͕ͨΈ - CustomPainterͰදݱͷ෯͕͕Δ - ൺֱతϨϕϧͳCanvasΛ͏͜ͱͰ ٯʹදݱͷ෯͕͕Δ(͜ͱ͋Δ)
͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠! ࣭ɾؾʹͳΔ͜ͱ͕͋Ε ͍ͭͰ࿈བྷ͍ͩ͘͞! Follow me on Twitter (X?): @YumNumm
TestFlight ιʔείʔυ