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
Ryotaro Onoue
July 24, 2023
Programming
750
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
130
FlutterKaigi 2025 システム裏側
yumnumm
0
2.1k
Terraform+cloud-initで自宅サーバのLXDをIaCするお話
yumnumm
2
250
FlutterKaigi 2024における開発チームの取り組み と 2025への展望
yumnumm
0
110
Dart WebAssemblyを使ったWeb API on Cloudflare Workers
yumnumm
0
130
Apple Walletでパスを作るお話
yumnumm
0
260
私がやってきたアウトプット集
yumnumm
0
210
俺/私のこだわりデスク大大大自慢LT大会 (LTFes #12)
yumnumm
0
150
仕事以外で作成したプロダクトの自慢大会
yumnumm
0
200
Other Decks in Programming
See All in Programming
そこに3びきプロダクトがいるじゃろう——生成AI時代における“価値が届かない理由”の構造
kosuket
0
420
Built Our Own Background Agent at LayerX
layerx
PRO
9
5k
Japan Community Day at Kubecon + CloudNativeCon Japan 2026: Learning Container Privilege Control by Building My Own Low-Level Container Runtime
ternbusty
1
140
テーブルをDELETEした
yuzneri
0
140
全PRの83%がAIレビューだけでマージできるようになった開発組織はその後どうなったか
athug
1
1.5k
ドリフトを絶対に許さない(?)CDK運用 / CDK Ops with Zero Tolerance for Drifts (?)
akihisaikeda
1
170
AI時代、エンジニアはどう育つのか -未経験エンジニアの成長を間近で見て考えたこと-
thasu0123
0
220
PHP に部分適用が来るぞ!……ところで何それ?おいしいの? #phpcon / phpcon-2026
shogogg
0
580
Terraform標準の組織で AWS CDKをどう使うか
mu7889yoon
1
490
Cloudflare is Agents
chimame
0
140
freee が目指す データ マネジメント戦略 AI-Ready 時代を支える 攻めのガバナンスとは
freee
PRO
0
290
JAWS-UG横浜 #102 AWSサ終供養LT会 成仏できない AWS サービスたち 〜本日、三体供養します〜
maroon1st
0
330
Featured
See All Featured
Six Lessons from altMBA
skipperchong
29
4.4k
The Illustrated Guide to Node.js - THAT Conference 2024
reverentgeek
1
420
From π to Pie charts
rasagy
0
250
Building a Scalable Design System with Sketch
lauravandoore
463
34k
Ten Tips & Tricks for a 🌱 transition
stuffmc
0
160
What’s in a name? Adding method to the madness
productmarketing
PRO
24
4.1k
Jamie Indigo - Trashchat’s Guide to Black Boxes: Technical SEO Tactics for LLMs
techseoconnect
PRO
0
580
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
34
2.8k
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
1.1k
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
4.3k
Avoiding the “Bad Training, Faster” Trap in the Age of AI
tmiket
0
200
Noah Learner - AI + Me: how we built a GSC Bulk Export data pipeline
techseoconnect
PRO
0
350
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 ιʔείʔυ