@@ -13,7 +13,6 @@ import 'debug_screen.dart';
1313import 'tile_sources.dart' ;
1414import 'viam_connection.dart' ;
1515import 'weather.dart' ;
16- import 'wind_layer.dart' ;
1716
1817/// Full-screen chart with a heading-rotated boat marker. The data readouts live
1918/// in a dashboard drawer (DataDrawer) rather than overlaid on the chart; only
@@ -37,6 +36,7 @@ class _MapScreenState extends State<MapScreen> {
3736 WindField ? _wind;
3837 bool _windOn = false ;
3938 bool _windLoading = false ;
39+ LatLngBounds ? _bounds; // current viewport, for sampling wind arrows
4040
4141 Future <void > _toggleWind () async {
4242 if (_windOn) {
@@ -57,6 +57,7 @@ class _MapScreenState extends State<MapScreen> {
5757 _wind = f;
5858 _windOn = true ;
5959 _windLoading = false ;
60+ _bounds = _map.camera.visibleBounds; // seed so arrows show immediately
6061 });
6162 widget.state
6263 .setWindInfo ('loaded ${f .nx }×${f .ny } grid (${f .u .length } cells)' );
@@ -92,6 +93,46 @@ class _MapScreenState extends State<MapScreen> {
9293 if (mounted) setState (() {});
9394 }
9495
96+ /// Wind arrows sampled across the visible bounds. Uses MarkerLayer (which is
97+ /// proven to render here) rather than a CustomPainter. Each arrow points the
98+ /// way the wind blows and is coloured by speed.
99+ List <Marker > _windMarkers (WindField f, LatLngBounds b) {
100+ final markers = < Marker > [];
101+ final span = math.max (b.east - b.west, b.north - b.south);
102+ final step = math.max (f.dx, span / 14 );
103+ if (step <= 0 ) return markers;
104+ for (double lat = b.north; lat >= b.south; lat -= step) {
105+ for (double lon = b.west; lon <= b.east; lon += step) {
106+ final nlon = ((lon + 540 ) % 360 ) - 180 ;
107+ final s = f.sample (nlon, lat);
108+ if (s == null ) continue ;
109+ final knots = math.sqrt (s.u * s.u + s.v * s.v) * 1.94384 ;
110+ // Bearing (clockwise from north) the wind blows toward.
111+ final ang = math.atan2 (s.u, s.v);
112+ markers.add (Marker (
113+ point: LatLng (lat, nlon),
114+ width: 24 ,
115+ height: 24 ,
116+ child: Transform .rotate (
117+ angle: ang,
118+ child: Icon (Icons .navigation, size: 16 , color: _windColor (knots)),
119+ ),
120+ ));
121+ if (markers.length >= 1500 ) return markers;
122+ }
123+ }
124+ return markers;
125+ }
126+
127+ Color _windColor (double kn) {
128+ if (kn < 5 ) return const Color (0xFFabd9e9 );
129+ if (kn < 12 ) return const Color (0xFF74add1 );
130+ if (kn < 18 ) return const Color (0xFF66bd63 );
131+ if (kn < 25 ) return const Color (0xFFfdae61 );
132+ if (kn < 34 ) return const Color (0xFFf46d43 );
133+ return const Color (0xFFd73027 );
134+ }
135+
95136 void _showAisDetails (AisBoat b) {
96137 showModalBottomSheet <void >(
97138 context: context,
@@ -160,9 +201,13 @@ class _MapScreenState extends State<MapScreen> {
160201 children: [
161202 FlutterMap (
162203 mapController: _map,
163- options: const MapOptions (
164- initialCenter: LatLng (41.3 , - 72.0 ), // Long Island Sound-ish
204+ options: MapOptions (
205+ initialCenter: const LatLng (41.3 , - 72.0 ), // Long Island Sound-ish
165206 initialZoom: 9 ,
207+ onPositionChanged: (camera, _) {
208+ _bounds = camera.visibleBounds;
209+ if (_windOn && mounted) setState (() {});
210+ },
166211 ),
167212 children: [
168213 TileLayer (
@@ -176,8 +221,9 @@ class _MapScreenState extends State<MapScreen> {
176221 errorTileCallback: (tile, error, stackTrace) =>
177222 debugPrint ('tile load failed (${_base .id }): $error ' ),
178223 ),
179- // Wind overlay (over the chart, under markers).
180- if (_windOn && _wind != null ) WindLayer (field: _wind! ),
224+ // Wind overlay (arrow markers, over the chart, under boat markers).
225+ if (_windOn && _wind != null && _bounds != null )
226+ MarkerLayer (markers: _windMarkers (_wind! , _bounds! )),
181227 // Active route: line from the boat to the destination.
182228 if (s.position != null && s.destination != null )
183229 PolylineLayer (
0 commit comments