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
740
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
120
FlutterKaigi 2025 システム裏側
yumnumm
0
2.1k
Terraform+cloud-initで自宅サーバのLXDをIaCするお話
yumnumm
2
240
FlutterKaigi 2024における開発チームの取り組み と 2025への展望
yumnumm
0
100
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
自作OSでスライド発表する
uyuki234
1
3.9k
ローカルLLMでどこまでコードが書けるか -拡張版 / How much code can be written on a local LLM Extended
kishida
12
4.8k
ソフトウェア設計に溶けるインフラ ― AWS CDK のインフラ認識論
konokenj
2
560
OSINT for SRE: 学術論文とポストモーテムから探る システム障害の共通パターン / SRE NEXT 2026
tomoyk
1
3.9k
えっ!!コードを読まずに開発を!?
hananouchi
0
220
SLOをサービス品質の共通言語にするために 取り組んできたこと
wakana0222
0
510
型も通る、synthも通る、それでも危ない 〜AIのCDKの権限とコストを機械で検証する〜 / It Passes Type Checks, It Passes Synth Checks, but It’s Still Risky — Automatically Verifying Permissions and Costs in AI’s CDK —
seike460
PRO
1
360
ローカルLLMでどこまでコードが書けるか -縮小版 / How much code can be written on a local LLM Shortened
kishida
2
190
1B+ /day規模のログを管理する技術
broadleaf
0
140
Foundation Models frameworkで画像分析
ryodeveloper
1
120
Terraform標準の組織で AWS CDKをどう使うか
mu7889yoon
0
290
act2-costs.pdf
sumedhbala
0
120
Featured
See All Featured
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
72
40k
GraphQLとの向き合い方2022年版
quramy
50
15k
YesSQL, Process and Tooling at Scale
rocio
174
15k
Why Your Marketing Sucks and What You Can Do About It - Sophie Logan
marketingsoph
0
290
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
118
120k
How to Ace a Technical Interview
jacobian
281
24k
Designing Experiences People Love
moore
143
24k
Rails Girls Zürich Keynote
gr2m
96
14k
Build The Right Thing And Hit Your Dates
maggiecrowley
39
3.3k
Ruling the World: When Life Gets Gamed
codingconduct
0
280
Primal Persuasion: How to Engage the Brain for Learning That Lasts
tmiket
0
390
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 ιʔείʔυ