map_display_controller.dart 8.38 KB
Newer Older
Maya Boeckh's avatar
Maya Boeckh committed
1
2
import 'dart:async';

Maya Boeckh's avatar
Maya Boeckh committed
3
4
5
import 'package:flutter/material.dart';
import 'package:flutter_map/plugin_api.dart';
import 'package:latlong/latlong.dart';
Maya Boeckh's avatar
Maya Boeckh committed
6
import 'package:logair_application/logic/handlers/data_handler.dart';
7
import 'package:logair_application/logic/handlers/database_handler.dart';
Maya Boeckh's avatar
Maya Boeckh committed
8
9
import 'package:logair_application/logic/handlers/network_handler.dart';
import 'package:logair_application/logic/data_header.dart';
Maya Boeckh's avatar
Maya Boeckh committed
10
import 'package:logair_application/logic/data_packet.dart';
Maya Boeckh's avatar
Maya Boeckh committed
11
import 'package:logair_application/logic/handlers/position_handler.dart';
12
import 'package:logair_application/utils/enums/pm_symbol.dart';
Maya Boeckh's avatar
Maya Boeckh committed
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

/// The [MapDisplayController] is a [Singleton] that handles the mapping of information.
class MapDisplayController {
  /// Whether or not the user has assumed manual control of the [FlutterMap],  
  bool _manualMotion = false;

  /// The current [Position] of the device.
  LatLng _currentPos;

  /// The previous [Position] of the device, for drawing lines.
  LatLng _lastPos;

  /// The [FlutterMap]'s zoom level, used to keep centering stable.
  double _zoom;

  /// The [FlutterMap]s [MapController], kept unique in order to avoid incessant reloading of resources.
  MapController _controller;

  /// [List] of [Polyline] representing the evolution of the PM2.5 value.
  List<Polyline> _polylines = [];
  
  /// [Marker] representing the user's current [Position].
  Marker _marker;
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

  Future<void> _rebuildMarkers() async {
    List<DataPacket> packets = await DatabaseHandler().getLastPackets(200);
    packets.forEach((DataPacket packet) {
      switch (_pmSymbol) {
        case PMSymbol.PM1:
          this.addToList(this._currentPos, packet.pm1(), 200);
          break;
        case PMSymbol.PM1:
          this.addToList(this._currentPos, packet.pm2_5(), 200);
          break;
        case PMSymbol.PM4:
          this.addToList(this._currentPos, packet.pm4(), 200);
          break;
        case PMSymbol.PM10:
          this.addToList(this._currentPos, packet.pm10(), 200);
          break;
        default:
          break;
      }
    });
  }

  List<CircleMarker> _pmMarkers = <CircleMarker>[];
Maya Boeckh's avatar
Maya Boeckh committed
60
61
62

  /// [Singleton]() instantiation factory.
  factory MapDisplayController() => _singleton;
Maya Boeckh's avatar
Maya Boeckh committed
63
64

  /// @Getter for [_polylines].
65
  List<Polyline> get polylines => this._polylines;
Maya Boeckh's avatar
Maya Boeckh committed
66
67

  /// @Getter for [_marker].
68
  Marker get marker => this._marker != null ? this._marker : _buildLocationMarker(LatLng(0, 0));
Maya Boeckh's avatar
Maya Boeckh committed
69
70
71
72
73

  /// Object used to calculate the real-life distance of two [Position]s
  final Distance _distance = new Distance();


74
75
76
  List<CircleMarker> get pmMarkers => this._pmMarkers;

  PMSymbol _pmSymbol = PMSymbol.PM2_5;
Maya Boeckh's avatar
Maya Boeckh committed
77

78
79
80
81
82
  void setPMKeyIndex(PMSymbol value) { 
    print('PM Index $value gives ${value.key}');
    this._pmSymbol = value;
    this._rebuildMarkers();
  }
Maya Boeckh's avatar
Maya Boeckh committed
83

84
85
86
87
  void addToList(LatLng position, double value, int maxPoints) {
    if (this._pmMarkers.length > maxPoints)
      this._pmMarkers.removeRange(0, this._pmMarkers.length - 200);
    
88
89
    if (value != null)
      this._pmMarkers.add(_buildPMMarker(position, value));
90
  }
Maya Boeckh's avatar
Maya Boeckh committed
91
92
93
94
95
96
97
98
99
  
  /// This function is executed on first initialization of the [MapDisplayController]
  /// TODO: Throttle to 1 in 5 updates for efficiency ?
  MapDisplayController._internal() {
    /// A new [MapController] is used as the global Controller.
    /// This avoids Garbage Collection rules and enables the [FlutterMap] to survive [State] changes, and other such events.
    this._controller = new MapController();

    /// This function is hooked to the [PositionHandler]s progress, ie. when a new [Position] is acquired (once per second), it is broadcasted across the app and can be listened to.
Maya Boeckh's avatar
Maya Boeckh committed
100
    // TODO Fix this to allow external data (bounded map data)
Maya Boeckh's avatar
Maya Boeckh committed
101
    PositionHandler().getCurrentOrLastPosition().listen((posChanged) {
Maya Boeckh's avatar
Maya Boeckh committed
102
103
104
      if (posChanged == null)
        return;
      
Maya Boeckh's avatar
Maya Boeckh committed
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
      /// If a [BluetoothDevice] is connected, then the [DataHandler] will have a [DataPacket] available.
      DataPacket latest = DataHandler().getLatestData();

      /// If the [DataPacket] is older than 5 seconds we invalidate it.
      if (latest != null && latest.timestamp() <= (DateTime.now().millisecondsSinceEpoch - 5000)) 
        latest = null;

      /// Keep track of the last [Position] to draw [Polyline]s.
      this._lastPos = this._currentPos;

      /// Convert the coorodinates to a [LatLng] structure that can be understood by [FlutterMap].
      this._currentPos = LatLng(posChanged.latitude, posChanged.longitude);

      /// If the user has not acquired manual control, then center the map onto the newer position.
      if (!this._manualMotion && this._controller != null && this._controller.ready)
        this._controller.move(this._currentPos, this._zoom);

      /// Move the [Marker] to the current [Position].
Maya Boeckh's avatar
Maya Boeckh committed
123
124
125
126
      this._marker = this._buildLocationMarker(this._currentPos);

      if ((this._lastPos != null && this._currentPos != null)) {// && this._distance.as(LengthUnit.Meter, this._lastPos, this._currentPos) >= 2)) {
        if (this._polylines.length == 0) {
Maya Boeckh's avatar
Maya Boeckh committed
127
128
129
          this._polylines.add(
            Polyline(
              points: [this._lastPos, this._currentPos],
Maya Boeckh's avatar
Maya Boeckh committed
130
              color: Colors.blue
Maya Boeckh's avatar
Maya Boeckh committed
131
132
            )
          );
Maya Boeckh's avatar
Maya Boeckh committed
133
134
135
136
        } else
          this._polylines.last.points.add(this._currentPos);
        
        if (latest != null) {
137
138
          switch (_pmSymbol) {
            case PMSymbol.PM1:
Maya Boeckh's avatar
Maya Boeckh committed
139
140
              if (latest.pm1() != null)
                this.addToList(this._currentPos, latest.pm1(), 200);
141
              break;
Maya Boeckh's avatar
Maya Boeckh committed
142
143
144
            case PMSymbol.PM2_5:
              if (latest.pm2_5() != null)
                this.addToList(this._currentPos, latest.pm2_5(), 200);
145
146
              break;
            case PMSymbol.PM4:
Maya Boeckh's avatar
Maya Boeckh committed
147
148
              if (latest.pm4() != null)
                this.addToList(this._currentPos, latest.pm4(), 200);
149
150
              break;
            case PMSymbol.PM10:
Maya Boeckh's avatar
Maya Boeckh committed
151
152
              if (latest.pm10() != null)
                this.addToList(this._currentPos, latest.pm10(), 200);
153
154
155
156
              break;
            default:
              break;
          }
Maya Boeckh's avatar
Maya Boeckh committed
157
158
159
        }
      }
    });
Maya Boeckh's avatar
Maya Boeckh committed
160
161
162
163
164


    // TODO Set user defined duration
    // TODO Set user defined radius
    new Timer.periodic(Duration(minutes: 1), (Timer t) {
165
      if (this._controller != null && this._controller.bounds != null) {
166
        print('${this._controller.bounds.southWest}, ${this._controller.bounds.northEast}');
Maya Boeckh's avatar
Maya Boeckh committed
167
        NetworkHandler().getProximityPoints(DataHeader().deviceID, this._controller.bounds);
168
      }
Maya Boeckh's avatar
Maya Boeckh committed
169
    });
Maya Boeckh's avatar
Maya Boeckh committed
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
  }

  /// Internal static [Singleton]() reference.
  static final MapDisplayController _singleton = new MapDisplayController._internal();

  /// @Getter for [_currentPos]
  LatLng currentPosition() => _currentPos != null ? _currentPos : LatLng(0, 0);

  /// @Getter for [_controller]
  MapController controller() => _controller;

  /// Function called whenever the user interacts with the [FlutterMap], setting the [FlutterMap]'s viewport under their control.
  void setControllerChange(LatLng position, double zoom) {
    /// Avoid triggering full user control when only the zoom value changes.
    if (this._currentPos != null && this._distance.as(LengthUnit.Meter, position, this._currentPos) >= 10)
      this._manualMotion = true;
    this._zoom = zoom;
  }

  /// Make the map respond to automatic events once more.
  void setControllerAutomatic() => this._manualMotion = false;

  /// Enables the user to have a controlled zoom in, zoom out experience.
  void setControllerZoom(bool zoomIn) {
    /// Bound the zoomability.
195
196
197
198
    if (zoomIn && this._zoom <= 19)
      this._zoom += 0.5;
    else if (!zoomIn && this._zoom >= 1)
      this._zoom -= 0.5;
Maya Boeckh's avatar
Maya Boeckh committed
199
200
201
202
203
204
205
206
207
208
209
210
211
    this._controller.move(this.controller().center, this._zoom);
  }

  /// Stream whether or not the [FlutterMap] is under full user control,
  /// Prompt re-center on true.
  Stream<bool> isManualMotionUsed() async* {
    while (true) {
      yield this._manualMotion;
      await Future.delayed(Duration(milliseconds: 500));
    }
  }

  /// Wrapper function to build a [Marker], with default values.
Maya Boeckh's avatar
Maya Boeckh committed
212
  Marker _buildLocationMarker(LatLng position) => Marker(
Maya Boeckh's avatar
Maya Boeckh committed
213
214
215
216
217
218
219
220
    width: 20.0,
    height: 20.0,
    point: position,
    builder: (ctx) => Container(
      child: Icon(Icons.location_on, color: Colors.red,),
    ),
  );

221

Maya Boeckh's avatar
Maya Boeckh committed
222
223
224
225
  CircleMarker _buildPMMarker(LatLng position, double pmValue) { 
    return CircleMarker(
      radius: 8,
      point: position,
226
      color: this._pmSymbol.colorizeValue(pmValue).withAlpha(0xff),
Maya Boeckh's avatar
Maya Boeckh committed
227
228
229
230
      borderStrokeWidth: 1,
      borderColor: Colors.blue[300]
    );
  }
Maya Boeckh's avatar
Maya Boeckh committed
231
}