2021-09-28 08:18:14 +02:00
|
|
|
part of google_track_trace;
|
|
|
|
|
2021-09-29 10:07:56 +02:00
|
|
|
enum TimePrecision { updateOnly, everySecond, everyMinute }
|
2021-09-28 08:18:14 +02:00
|
|
|
|
|
|
|
class GoogleTrackTraceMap extends StatefulWidget {
|
2021-09-30 10:09:57 +02:00
|
|
|
const GoogleTrackTraceMap({
|
2021-09-29 10:07:56 +02:00
|
|
|
required this.onMapCreated,
|
2021-09-28 08:18:14 +02:00
|
|
|
required this.startPosition,
|
|
|
|
required this.destinationPosition,
|
2021-09-29 10:07:56 +02:00
|
|
|
required this.googleAPIKey,
|
|
|
|
required this.routeUpdateInterval,
|
2021-09-30 10:09:57 +02:00
|
|
|
Key? key,
|
2021-10-01 15:00:23 +02:00
|
|
|
this.markerUpdatePrecision = 50,
|
2021-09-29 10:07:56 +02:00
|
|
|
this.timerPrecision = TimePrecision.everyMinute,
|
2021-09-28 10:37:48 +02:00
|
|
|
this.travelMode = TravelMode.driving,
|
2021-10-01 15:00:23 +02:00
|
|
|
this.cameraTargetBounds,
|
2021-09-29 10:07:56 +02:00
|
|
|
this.compassEnabled = false,
|
2021-10-01 15:00:23 +02:00
|
|
|
this.rotateGesturesEnabled = false,
|
|
|
|
this.scrollGesturesEnabled = false,
|
2021-09-29 10:07:56 +02:00
|
|
|
this.zoomControlsEnabled = false,
|
|
|
|
this.zoomGesturesEnabled = false,
|
2021-10-01 15:00:23 +02:00
|
|
|
this.liteModeEnabled = false,
|
|
|
|
this.tiltGesturesEnabled = false,
|
|
|
|
this.myLocationEnabled = false,
|
|
|
|
this.myLocationButtonEnabled = false,
|
2021-09-29 10:07:56 +02:00
|
|
|
this.mapToolbarEnabled = false,
|
|
|
|
this.mapType = MapType.normal,
|
|
|
|
this.buildingsEnabled = false,
|
2021-10-01 15:00:23 +02:00
|
|
|
this.indoorViewEnabled = false,
|
|
|
|
this.trafficEnabled = false,
|
2021-09-30 12:34:43 +02:00
|
|
|
this.mapStylingTheme,
|
2021-10-01 15:00:23 +02:00
|
|
|
this.onTap,
|
|
|
|
this.onArrived,
|
|
|
|
this.onLongPress,
|
|
|
|
this.onCameraMove,
|
2021-09-29 12:18:53 +02:00
|
|
|
this.line,
|
2021-09-30 12:34:43 +02:00
|
|
|
}) : super(key: key);
|
2021-09-28 08:18:14 +02:00
|
|
|
|
|
|
|
/// Callback method for when the map is ready to be used.
|
|
|
|
///
|
|
|
|
/// Used to receive a [TrackTraceController] for this [GoogleTrackTraceMap].
|
|
|
|
/// this [TrackTraceController] also contains the [GoogleMapController]
|
2021-09-29 10:07:56 +02:00
|
|
|
final void Function(TrackTraceController) onMapCreated;
|
2021-09-28 08:18:14 +02:00
|
|
|
|
|
|
|
final TravelMode travelMode;
|
2021-09-29 10:07:56 +02:00
|
|
|
|
2021-10-01 15:00:23 +02:00
|
|
|
final Duration routeUpdateInterval;
|
2021-09-29 10:07:56 +02:00
|
|
|
|
2021-10-01 15:00:23 +02:00
|
|
|
/// amount of meter the marker needs to move to update
|
|
|
|
final int markerUpdatePrecision;
|
2021-09-29 10:07:56 +02:00
|
|
|
final TimePrecision timerPrecision;
|
|
|
|
|
|
|
|
final Marker startPosition;
|
|
|
|
final Marker destinationPosition;
|
|
|
|
|
2021-09-30 10:09:57 +02:00
|
|
|
final Polyline? line;
|
2021-09-29 12:18:53 +02:00
|
|
|
|
2021-09-29 10:07:56 +02:00
|
|
|
final bool compassEnabled;
|
2021-10-01 15:00:23 +02:00
|
|
|
final bool rotateGesturesEnabled;
|
|
|
|
final bool scrollGesturesEnabled;
|
|
|
|
final bool liteModeEnabled;
|
|
|
|
final bool tiltGesturesEnabled;
|
|
|
|
final bool myLocationEnabled;
|
|
|
|
final bool myLocationButtonEnabled;
|
2021-09-29 10:07:56 +02:00
|
|
|
final bool zoomControlsEnabled;
|
|
|
|
final bool zoomGesturesEnabled;
|
|
|
|
final bool mapToolbarEnabled;
|
|
|
|
final bool buildingsEnabled;
|
2021-10-01 15:00:23 +02:00
|
|
|
final bool indoorViewEnabled;
|
|
|
|
final bool trafficEnabled;
|
|
|
|
final CameraTargetBounds? cameraTargetBounds;
|
2021-09-29 10:07:56 +02:00
|
|
|
final MapType mapType;
|
2021-09-30 12:34:43 +02:00
|
|
|
final GoogleTrackTraceMapTheme? mapStylingTheme;
|
2021-09-29 12:18:53 +02:00
|
|
|
|
2021-10-01 15:00:23 +02:00
|
|
|
final ArgumentCallback<LatLng>? onTap;
|
|
|
|
final void Function()? onArrived;
|
|
|
|
final ArgumentCallback<LatLng>? onLongPress;
|
|
|
|
final CameraPositionCallback? onCameraMove;
|
2021-09-29 10:07:56 +02:00
|
|
|
final String googleAPIKey;
|
2021-09-28 08:18:14 +02:00
|
|
|
|
|
|
|
@override
|
|
|
|
State createState() => _GoogleTrackTraceMapState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _GoogleTrackTraceMapState extends State<GoogleTrackTraceMap> {
|
2021-09-29 12:18:53 +02:00
|
|
|
late final TrackTraceController controller;
|
2021-09-29 10:07:56 +02:00
|
|
|
|
|
|
|
DateTime lastRouteUpdate = DateTime.now();
|
|
|
|
|
2021-10-01 15:00:23 +02:00
|
|
|
late final Timer routeCalculateTimer;
|
|
|
|
late final Timer markerUpdateTimer;
|
|
|
|
|
2021-09-29 10:07:56 +02:00
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
2021-09-29 12:18:53 +02:00
|
|
|
controller =
|
2021-09-29 10:07:56 +02:00
|
|
|
TrackTraceController(widget.startPosition, widget.destinationPosition);
|
2021-10-01 15:00:23 +02:00
|
|
|
controller.addListener(
|
|
|
|
() => setState(() {}),
|
|
|
|
);
|
2021-09-29 12:18:53 +02:00
|
|
|
widget.onMapCreated(controller);
|
2021-09-29 10:07:56 +02:00
|
|
|
startRouteUpdateTimer();
|
|
|
|
startMarkerUpdateTimer();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
2021-09-29 12:18:53 +02:00
|
|
|
controller.dispose();
|
2021-09-29 10:07:56 +02:00
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return GoogleMap(
|
2021-09-30 12:34:43 +02:00
|
|
|
initialCameraPosition: calculateCameraPosition(
|
|
|
|
controller.start.position,
|
|
|
|
controller.end.position,
|
|
|
|
),
|
|
|
|
onMapCreated: _onMapCreated,
|
2021-10-01 15:00:23 +02:00
|
|
|
onTap: widget.onTap,
|
|
|
|
onLongPress: widget.onLongPress,
|
|
|
|
onCameraMove: widget.onCameraMove,
|
2021-09-30 12:34:43 +02:00
|
|
|
compassEnabled: widget.compassEnabled,
|
2021-10-01 15:00:23 +02:00
|
|
|
rotateGesturesEnabled: widget.rotateGesturesEnabled,
|
|
|
|
scrollGesturesEnabled: widget.scrollGesturesEnabled,
|
2021-09-30 12:34:43 +02:00
|
|
|
zoomControlsEnabled: widget.zoomControlsEnabled,
|
|
|
|
zoomGesturesEnabled: widget.zoomGesturesEnabled,
|
2021-10-01 15:00:23 +02:00
|
|
|
liteModeEnabled: widget.liteModeEnabled,
|
|
|
|
tiltGesturesEnabled: widget.tiltGesturesEnabled,
|
|
|
|
myLocationEnabled: widget.myLocationEnabled,
|
|
|
|
myLocationButtonEnabled: widget.myLocationButtonEnabled,
|
2021-09-30 12:34:43 +02:00
|
|
|
mapToolbarEnabled: widget.mapToolbarEnabled,
|
|
|
|
mapType: widget.mapType,
|
|
|
|
buildingsEnabled: widget.buildingsEnabled,
|
2021-10-01 15:00:23 +02:00
|
|
|
indoorViewEnabled: widget.indoorViewEnabled,
|
|
|
|
trafficEnabled: widget.trafficEnabled,
|
2021-09-30 12:34:43 +02:00
|
|
|
markers: <Marker>{
|
|
|
|
controller.start,
|
|
|
|
controller.end,
|
|
|
|
},
|
|
|
|
polylines: <Polyline>{
|
|
|
|
if (controller.route != null)
|
|
|
|
(widget.line != null)
|
|
|
|
? widget.line!.copyWith(
|
|
|
|
pointsParam: controller.route!.line
|
|
|
|
.map((PointLatLng e) => LatLng(e.latitude, e.longitude))
|
|
|
|
.toList(),
|
|
|
|
)
|
|
|
|
: Polyline(
|
|
|
|
// default PolyLine if none is provided
|
|
|
|
polylineId: const PolylineId('track&trace route'),
|
|
|
|
color: Theme.of(context).primaryColor,
|
|
|
|
width: 4,
|
|
|
|
points: controller.route!.line
|
|
|
|
.map((PointLatLng e) => LatLng(e.latitude, e.longitude))
|
|
|
|
.toList(),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
);
|
2021-09-29 10:07:56 +02:00
|
|
|
}
|
2021-09-28 08:18:14 +02:00
|
|
|
|
2021-09-29 12:18:53 +02:00
|
|
|
void _onMapCreated(GoogleMapController ctr) {
|
2021-09-28 08:18:14 +02:00
|
|
|
if (mounted) {
|
2021-09-29 12:18:53 +02:00
|
|
|
controller.mapController = ctr;
|
2021-09-30 12:34:43 +02:00
|
|
|
if (widget.mapStylingTheme != null) {
|
|
|
|
ctr.setMapStyle(widget.mapStylingTheme!.getJson());
|
|
|
|
} else {
|
|
|
|
// No theme provided so switching to default
|
|
|
|
ctr.setMapStyle(
|
|
|
|
'[{"featureType": "poi","stylers": [{"visibility": "off"}]}]',
|
|
|
|
);
|
|
|
|
}
|
2021-10-01 15:00:23 +02:00
|
|
|
controller.recenterCamera();
|
2021-09-28 08:18:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CameraPosition calculateCameraPosition(LatLng pointA, LatLng pointB) {
|
2021-09-30 10:09:57 +02:00
|
|
|
var target = LatLng(
|
|
|
|
(pointA.latitude + pointB.latitude) / 2,
|
|
|
|
(pointA.longitude + pointB.longitude) / 2,
|
|
|
|
);
|
2021-09-28 08:18:14 +02:00
|
|
|
return CameraPosition(
|
2021-09-30 10:09:57 +02:00
|
|
|
target: target,
|
|
|
|
zoom: 13.0,
|
|
|
|
tilt: 0.0,
|
|
|
|
bearing: 0.0,
|
|
|
|
);
|
2021-09-28 08:18:14 +02:00
|
|
|
}
|
|
|
|
|
2021-09-29 10:07:56 +02:00
|
|
|
void startRouteUpdateTimer() {
|
|
|
|
calculateRoute(); // run at the start
|
2021-10-01 15:00:23 +02:00
|
|
|
routeCalculateTimer =
|
|
|
|
Timer.periodic(widget.routeUpdateInterval, (Timer timer) {
|
2021-09-29 10:07:56 +02:00
|
|
|
calculateRoute();
|
|
|
|
});
|
2021-09-28 08:18:14 +02:00
|
|
|
}
|
|
|
|
|
2021-09-29 10:07:56 +02:00
|
|
|
void startMarkerUpdateTimer() {
|
|
|
|
if (widget.timerPrecision != TimePrecision.updateOnly) {
|
2021-09-30 10:09:57 +02:00
|
|
|
var updateInterval =
|
2021-09-29 12:18:53 +02:00
|
|
|
(widget.timerPrecision == TimePrecision.everyMinute) ? 60 : 1;
|
2021-10-01 15:00:23 +02:00
|
|
|
markerUpdateTimer =
|
|
|
|
Timer.periodic(Duration(seconds: updateInterval), (timer) {
|
2021-09-29 12:18:53 +02:00
|
|
|
if (controller.route != null) {
|
2021-10-01 15:00:23 +02:00
|
|
|
checkDestinationCloseBy();
|
2021-09-29 13:42:13 +02:00
|
|
|
controller.route = TrackTraceRoute(
|
2021-10-01 15:00:23 +02:00
|
|
|
(controller.route!.duration != 0)
|
|
|
|
? controller.route!.duration - updateInterval
|
|
|
|
: 0,
|
2021-09-30 12:34:43 +02:00
|
|
|
controller.route!.distance,
|
|
|
|
controller.route!.line,
|
|
|
|
);
|
2021-09-29 12:18:53 +02:00
|
|
|
}
|
2021-09-29 10:07:56 +02:00
|
|
|
});
|
|
|
|
}
|
2021-09-28 08:18:14 +02:00
|
|
|
}
|
|
|
|
|
2021-09-30 12:34:43 +02:00
|
|
|
void calculateRoute() {
|
2021-10-01 15:00:23 +02:00
|
|
|
if (controller.route == null || checkTargetMoved()) {
|
|
|
|
DirectionsRepository() // TODO(freek): refactor this away
|
|
|
|
.getDirections(
|
|
|
|
origin: controller.start.position,
|
|
|
|
destination: controller.end.position,
|
|
|
|
mode: widget.travelMode,
|
|
|
|
key: widget.googleAPIKey,
|
|
|
|
)
|
|
|
|
.then(
|
|
|
|
(value) => {
|
|
|
|
controller.route = TrackTraceRoute(
|
|
|
|
value.totalDuration,
|
|
|
|
value.totalDistance,
|
|
|
|
value.polylinePoints,
|
|
|
|
),
|
|
|
|
checkDestinationCloseBy(),
|
|
|
|
controller.recenterCamera(),
|
|
|
|
setState(() {
|
|
|
|
lastRouteUpdate = DateTime.now();
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void checkDestinationCloseBy() {
|
|
|
|
if (calculatePointProximity(
|
|
|
|
controller.start.position,
|
|
|
|
controller.end.position,
|
|
|
|
) <
|
|
|
|
widget.markerUpdatePrecision) {
|
|
|
|
routeCalculateTimer.cancel();
|
|
|
|
markerUpdateTimer.cancel();
|
|
|
|
if (controller.route != null) {
|
|
|
|
controller.route!.line = <PointLatLng>[controller.route!.line[1]];
|
|
|
|
controller.route!.distance = 0;
|
|
|
|
controller.route!.duration = 0;
|
|
|
|
}
|
|
|
|
widget.onArrived?.call();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool checkTargetMoved() {
|
|
|
|
return calculatePointProximity(
|
|
|
|
controller.start.position,
|
|
|
|
LatLng(
|
|
|
|
controller.route!.line[0].latitude,
|
|
|
|
controller.route!.line[0].longitude,
|
|
|
|
),
|
|
|
|
) >=
|
|
|
|
widget.markerUpdatePrecision;
|
|
|
|
}
|
|
|
|
|
|
|
|
double calculatePointProximity(LatLng pointA, LatLng pointB) {
|
|
|
|
var p = 0.017453292519943295;
|
|
|
|
var c = cos;
|
|
|
|
var a = 0.5 -
|
|
|
|
c(
|
|
|
|
(pointA.latitude - pointB.latitude) * p,
|
|
|
|
) /
|
|
|
|
2 +
|
|
|
|
c(controller.route!.line[0].latitude * p) *
|
|
|
|
c(pointA.latitude * p) *
|
|
|
|
(1 -
|
|
|
|
c(
|
|
|
|
(pointA.longitude - pointB.longitude) * p,
|
|
|
|
)) /
|
|
|
|
2;
|
|
|
|
return 12742 * asin(sqrt(a)) * 1000;
|
2021-09-28 08:18:14 +02:00
|
|
|
}
|
|
|
|
}
|