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

Flutterでも地図を描きたいお話

 Flutterでも地図を描きたいお話

YOUTRUST x ゆめみ Flutter LT会@渋谷 #2
https://yumemi.connpass.com/event/287984/

Ryotaro Onoue

July 24, 2023
Tweet

More Decks by Ryotaro Onoue

Other Decks in Programming

Transcript

  1. 

  2. 

  3. 1. ཁ݅ ࡞Δલʹ .. . 1. άϦάϦಈ͔ͤΔ (=ΠϯλϥΫςΟϒͳ)Ϛοϓʹ͍ͨ͠ - Ҡಈɾ֦େॖখ

    - ͳΊΒ͔ʹಈ͘Α͏ʹ͍ͨ͠ 2. දࣔྖҬΛ Controller ͔Βࣗ༝ʹૢ࡞Ͱ͖ΔΑ͏ʹ͍ͨ͠ - த৺ͷҢ౓ܦ౓ɾζʔϜ཰ͷมߋɾΞχϝʔγϣϯ 3. ೚ҙͷ Widget Λ஍ਤͷ্ʹॏͶΒΕΔΑ͏ʹ͍ͨ͠ - PlatformViewΛ࢖͏ͱɺϚοϓҠಈ࣌ʹগ͠஗ΕͯWidgetͷҐஔमਖ਼͕͔͔ΔͨΊ BAD - ը໘্ͷWidgetͷ਺͕ଟ͍ͱ Ң౓ܦ౓ <—> ը໘࠲ඪͷMethodChannelΛୟ͘ճ਺͕ଟ͘ͳΓ ΍ΓऔΓʹ͕͔͔࣌ؒΔ ↑ ͜ͷ໰୊͸ ڈ೥ͷ12݄ʙ2݄ࠒͷMapBoxΛ࢖ͬͨΞϓϦ։ൃڠྗͰࢮ͵΄Ͳۤ࿑ͨ͠ 
  4. 2. ஍ਤσʔλͷ४උ  https: / / www.data.jma.go.jp/developer/gis.html https: / /

    github.com/datasets/geo-countries/ blob/master/data/countries.geojson
  5. 2-2. ѹॖख๏ 2  A B A B Ң౓ܦ౓ͷ഑ྻ ཁૉ͝ͱʹ෼ղ͢Δͱʜ

    GeoJSONͱTopoJSONͷҧ͍ GeoJSON ॏෳ͍ͯͯ͠ϜμͰ͸  σʔλαΠζతʹ΋͓ֆ͔͖తʹ΋ A B
  6. 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)
  7. 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ϝϧΧτϧਤ๏Λ༻͍ͯ౤Ө
  8. 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; } ىಈ࣌ʹɺಡΈࠐΈ - > ౤ӨΛࡁ·͓͖ͤͯ ౤ӨޙͷσʔλΛอ͓࣋ͯ͘͠
  9. 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ഒ͢Δ
  10. 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
  11. 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 }
  12. 

  13. 

  14. 

  15. 

  16. 4-1. CustomPainter ͓ֆ͔͖Widget  - ༷ʑͳਤܗΛඳըͰ͖Δ - ਤܗඳը࣌ʹϨΠΞ΢τ͢Δඞཁ͕ͳ͍ͷͰ ۪௚ʹStackͰॏͶͨΓ͢ΔΑΓ΋ߴύϑΥʔϚϯε flutter/rendering

    Flutter ๮άϥϑͷ࣮૷͔ΒֶͿ Container ͱ Canvas ͷ࢖͍෼͚ | CyberAgent Developers Blog https: / / developers.cyberagent.co.jp/blog/archives/36573/
  17. ͓ֆ͔͖Widget  - ༷ʑͳਤܗΛඳըͰ͖Δ - ਤܗඳը࣌ʹϨΠΞ΢τ͢Δඞཁ͕ͳ͍ͷͰ ۪௚ʹStackͰॏͶͨΓ͢ΔΑΓ΋ߴύϑΥʔϚϯε Flutter ๮άϥϑͷ࣮૷͔ΒֶͿ Container

    ͱ Canvas ͷ࢖͍෼͚ | CyberAgent Developers Blog https: / / developers.cyberagent.co.jp/blog/archives/36573/ 4-1. CustomPainter Widget ਤܗඳըͰ͖ΔͷͰ ஍ਤඳ͚Δ! -> Ͳ͏΍ͬͯಈ͔͢…?
  18. ΠϯλϥΫςΟϒͳૢ࡞ΛՄೳʹ  - ը૾΍ө૾ͷදࣔྖҬΛ֦େɾॖখɾҠಈɾճస͢Δͷʹ࢖͑Δ - Listener - > GestureDetector -

    > Transform Ϣʔβͷૢ࡞؂ࢹ දࣔௐ੔ https: / / dartpad.dev/?id=e6f75e0d35a958257440ab514a4196af flutter/widgets  4-2. InteractiveViewer
  19. ΠϯλϥΫςΟϒͳૢ࡞ΛՄೳʹ  - ը૾΍ө૾ͷදࣔྖҬΛ֦େɾॖখɾҠಈɾճస͢Δͷʹ࢖͑Δ - Listener - > GestureDetector -

    > Transform Ϣʔβͷૢ࡞؂ࢹ දࣔௐ੔ https: / / dartpad.dev/?id=e6f75e0d35a958257440ab514a4196af flutter/widgets  - ໰୊఺ - Ҡಈɾ֦େͯ͠ ཁૉ͕ը໘֎ʹग़͔ͨͲ͏͔Λ൑ผͰ͖ͳ͍ - Πϝʔδͱͯ͠͸WidgetΛը૾Խͯ͠ ͦΕΛࣸਅΞϓϦͰද͍ࣔͯ͠Δײ͡ - > දࣔྖҬʹ߹Θͤͯ࠷దԽͰ͖ͳ͍ - Controller͕ੜ͍͑ͯΔ͕ɺঢ়ଶΛߦྻͰอ͍࣋ͯ͠Δ - ஍ਤ޲͚ͷૢ࡞Λૢ࡞͠ʹ͍͘ 4-2. InteractiveViewer
  20. 4-3-1. MapStateͱMapViewModel ૊ΜͰͧ͘ʙ ঢ়ଶ؅ཧ  InteractiveViewer Widgetͷத਎Λࢀߟʹͭͭ͠ ࣗલͰ஍ਤؔ࿈ͷؔ਺Λ࣮૷͍ͯ͘͠ (with Riverpod)

    - globalPointToOffset - handleScaleStart / handleScaleUpdate / . . . - animatedMoveTo(LatLng, Duration, Curve) - animatedZoomTo(double, Duration, Curve) - animatedApplyBounds (LatLngBoundary, Duration, Curve) ࣗ࡞
  21. 4-3-2. MapTouchHandlerWidget ૊ΜͰͧ͘ʙ λονݕ஌ͱঢ়ଶ؅ཧ  - λονͷݕग़෦෼ (ಁ໌) - Listener

    - > GestureDetectorͰ Ϣʔβͷૢ࡞Λ؂ࢹ - ൃՐ࣌ʹ͸ MapViewModelͷ .handleXXؔ਺ΛݺΜͰ ঢ়ଶΛߋ৽ͤ͞Δ ࣗ࡞
  22. 4-3-3. BaseMapPainter ૊ΜͰͧ͘ʙ ஍ਤඳը  - ੈք஍ਤɾ೔ຊ஍ਤΛCustomPainterͰ͓ֆ͔͖ - ֤छ࠷దԽ -

    ζʔϜഒ཰ʹԠͯ͡Douglas-Peucker ๏ Λద ༻ͯ͠ઢ෼Λ؆ૉԽ - ௿ζʔϜϨϕϧ࣌ͷ఺ͷ਺ΛݮΒ͢ - > ύϑΥʔϚϯε͕ྑ͘ͳΔ ↑ Ωϟογϡ͓ͯ͘͠ - ը໘֎ͷϙϦΰϯ͸ඳը͠ͳ͍Α͏ʹ ࣗ࡞
  23. 5. ·ͱΊ ૊Μͩͧ!  - ஍ਤ͸೉͍͚͠ΕͲ ςΫ͍ ࣮૷͕͋ͬͨΓͯ͠ ͓΋͠Ζ͍ -

    leaflet.js ΍ MapLibre౳ͷ஍ਤؔ࿈ͷϥΠϒϥϦ΋ࢀߟʹͳΔ - FlutterͰ஍ਤΛඳ͍͍͘ײ͡ͷϥΠϒϥϦ͕ͳ͔ͬͨͷͰ ࠓޙ࡞͍͖͍ͬͯͨ - InteractiveViewer౳ͷWidgetͷ಺෦࣮૷ΛோΊͯ ͦΕΛࢀߟʹ࣮૷͢Δ͜ͱ͕Ͱ͖Δ - SwiftUIͱ͔ͩͱ͜ͷٕ͸࢖͑ͳ͍…? Φʔϓϯιʔεͷ͋Γ͕ͨΈ - CustomPainterͰදݱͷ෯͕޿͕Δ - ൺֱత௿ϨϕϧͳCanvasΛ࢖͏͜ͱͰ ٯʹදݱͷ෯͕޿͕Δ(͜ͱ΋͋Δ)