diff --git a/example/lib/main.dart b/example/lib/main.dart index 815fcb1..1a335c9 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -13,10 +13,13 @@ class TrackTraceDemo extends StatefulWidget { class _TrackTraceDemoState extends State { TrackTraceController? controller; + int step = 1; + int routeLength = 0; + late final Timer timer; @override void initState() { - Timer.periodic(const Duration(seconds: 5), (_) { + timer = Timer.periodic(const Duration(seconds: 2), (_) { moveAlongRoute(); }); super.initState(); @@ -48,6 +51,18 @@ class _TrackTraceDemoState extends State { {'visibility': 'off'}, ], ), + // GoogleMapThemeFeature( + // featureType: 'water', + // stylers: [ + // {'color': '#00ff00'} + // ], + // ), + // GoogleMapThemeFeature( + // featureType: 'road', + // stylers: [ + // {'color': '#000000'} + // ], + // ) ], ), startPosition: const Marker( @@ -61,18 +76,37 @@ class _TrackTraceDemoState extends State { googleAPIKey: 'AIzaSyDaxZX8TeQeVf5tW-D6A66WLl20arbWV6c', travelMode: TravelMode.walking, mapType: MapType.normal, - routeUpdateInterval: 60, + routeUpdateInterval: Duration(seconds: 30), timerPrecision: TimePrecision.everySecond, zoomGesturesEnabled: true, + scrollGesturesEnabled: true, line: const Polyline( + jointType: JointType.bevel, polylineId: PolylineId('test route'), color: Color(0xFFFF7884), width: 3, ), + onArrived: () { + timer.cancel(); + debugPrint('stopping simulation'); + }, + onTap: (value) { + debugPrint(value.toString()); + }, + onLongPress: (value) { + debugPrint(value.toString()); + }, + onCameraMove: (value) { + debugPrint(value.toString()); + }, onMapCreated: (ctr) => { controller = ctr, ctr.addListener(() { - setState(() {}); + setState(() { + if (ctr.route != null && ctr.route!.distance != routeLength) { + step = 1; + } + }); }), }, ), @@ -100,10 +134,12 @@ class _TrackTraceDemoState extends State { controller!.start = Marker( markerId: const MarkerId('Start Locatie'), position: LatLng( - controller!.route!.line[1].latitude, - controller!.route!.line[1].longitude, + controller!.route!.line[step].latitude, + controller!.route!.line[step].longitude, ), ); + step++; + routeLength = controller!.route!.distance; } } } diff --git a/example/pubspec.lock b/example/pubspec.lock index 6ad044c..596e1f5 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -134,7 +134,7 @@ packages: path: ".." relative: true source: path - version: "0.0.1" + version: "1.0.0" html: dependency: transitive description: diff --git a/lib/google_track_trace.dart b/lib/google_track_trace.dart index 5c5e879..6467252 100644 --- a/lib/google_track_trace.dart +++ b/lib/google_track_trace.dart @@ -12,7 +12,16 @@ import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:http/http.dart' as http; export 'package:google_maps_flutter/google_maps_flutter.dart' - show MapType, Marker, MarkerId, Polyline, PolylineId, LatLng; + show + MapType, + Marker, + MarkerId, + BitmapDescriptor, + InfoWindow, + Polyline, + PolylineId, + JointType, + LatLng; part 'src/controller.dart'; part 'src/directions_repository.dart'; diff --git a/lib/src/controller.dart b/lib/src/controller.dart index fd1f61f..91828f7 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -31,6 +31,36 @@ class TrackTraceController extends ChangeNotifier { notifyListeners(); } + void recenterCamera() { + mapController?.moveCamera( + CameraUpdate.newLatLngBounds( + LatLngBounds( + southwest: LatLng( + min( + _startPosition.position.latitude, + _destinationPosition.position.latitude, + ), + min( + _startPosition.position.longitude, + _destinationPosition.position.longitude, + ), + ), + northeast: LatLng( + max( + _startPosition.position.latitude, + _destinationPosition.position.latitude, + ), + max( + _startPosition.position.longitude, + _destinationPosition.position.longitude, + ), + ), + ), + 50, + ), + ); + } + @override void dispose() { mapController?.dispose(); @@ -39,11 +69,14 @@ class TrackTraceController extends ChangeNotifier { } class TrackTraceRoute { -TrackTraceRoute( - int durationValue, int distanceValue, List lineValue,) - : duration = durationValue, + TrackTraceRoute( + int durationValue, + int distanceValue, + List lineValue, + ) : duration = durationValue, distance = distanceValue, line = lineValue; + /// route duration in seconds int duration = 0; @@ -51,5 +84,5 @@ TrackTraceRoute( int distance = 0; /// route edge points - final List line; + List line; } diff --git a/lib/src/google_map.dart b/lib/src/google_map.dart index 8101661..58648ad 100644 --- a/lib/src/google_map.dart +++ b/lib/src/google_map.dart @@ -1,6 +1,5 @@ part of google_track_trace; -// typedef void MapCreatedCallback(TrackTraceController controller); enum TimePrecision { updateOnly, everySecond, everyMinute } class GoogleTrackTraceMap extends StatefulWidget { @@ -11,15 +10,29 @@ class GoogleTrackTraceMap extends StatefulWidget { required this.googleAPIKey, required this.routeUpdateInterval, Key? key, + this.markerUpdatePrecision = 50, this.timerPrecision = TimePrecision.everyMinute, this.travelMode = TravelMode.driving, + this.cameraTargetBounds, this.compassEnabled = false, + this.rotateGesturesEnabled = false, + this.scrollGesturesEnabled = false, this.zoomControlsEnabled = false, this.zoomGesturesEnabled = false, + this.liteModeEnabled = false, + this.tiltGesturesEnabled = false, + this.myLocationEnabled = false, + this.myLocationButtonEnabled = false, this.mapToolbarEnabled = false, this.mapType = MapType.normal, this.buildingsEnabled = false, + this.indoorViewEnabled = false, + this.trafficEnabled = false, this.mapStylingTheme, + this.onTap, + this.onArrived, + this.onLongPress, + this.onCameraMove, this.line, }) : super(key: key); @@ -31,8 +44,10 @@ class GoogleTrackTraceMap extends StatefulWidget { final TravelMode travelMode; - final int routeUpdateInterval; + final Duration routeUpdateInterval; + /// amount of meter the marker needs to move to update + final int markerUpdatePrecision; final TimePrecision timerPrecision; final Marker startPosition; @@ -41,13 +56,26 @@ class GoogleTrackTraceMap extends StatefulWidget { final Polyline? line; final bool compassEnabled; + final bool rotateGesturesEnabled; + final bool scrollGesturesEnabled; + final bool liteModeEnabled; + final bool tiltGesturesEnabled; + final bool myLocationEnabled; + final bool myLocationButtonEnabled; final bool zoomControlsEnabled; final bool zoomGesturesEnabled; final bool mapToolbarEnabled; final bool buildingsEnabled; + final bool indoorViewEnabled; + final bool trafficEnabled; + final CameraTargetBounds? cameraTargetBounds; final MapType mapType; final GoogleTrackTraceMapTheme? mapStylingTheme; + final ArgumentCallback? onTap; + final void Function()? onArrived; + final ArgumentCallback? onLongPress; + final CameraPositionCallback? onCameraMove; final String googleAPIKey; @override @@ -59,12 +87,17 @@ class _GoogleTrackTraceMapState extends State { DateTime lastRouteUpdate = DateTime.now(); + late final Timer routeCalculateTimer; + late final Timer markerUpdateTimer; + @override void initState() { super.initState(); controller = TrackTraceController(widget.startPosition, widget.destinationPosition); - controller.addListener(_onChange); + controller.addListener( + () => setState(() {}), + ); widget.onMapCreated(controller); startRouteUpdateTimer(); startMarkerUpdateTimer(); @@ -84,12 +117,23 @@ class _GoogleTrackTraceMapState extends State { controller.end.position, ), onMapCreated: _onMapCreated, + onTap: widget.onTap, + onLongPress: widget.onLongPress, + onCameraMove: widget.onCameraMove, compassEnabled: widget.compassEnabled, + rotateGesturesEnabled: widget.rotateGesturesEnabled, + scrollGesturesEnabled: widget.scrollGesturesEnabled, zoomControlsEnabled: widget.zoomControlsEnabled, zoomGesturesEnabled: widget.zoomGesturesEnabled, + liteModeEnabled: widget.liteModeEnabled, + tiltGesturesEnabled: widget.tiltGesturesEnabled, + myLocationEnabled: widget.myLocationEnabled, + myLocationButtonEnabled: widget.myLocationButtonEnabled, mapToolbarEnabled: widget.mapToolbarEnabled, mapType: widget.mapType, buildingsEnabled: widget.buildingsEnabled, + indoorViewEnabled: widget.indoorViewEnabled, + trafficEnabled: widget.trafficEnabled, markers: { controller.start, controller.end, @@ -115,10 +159,6 @@ class _GoogleTrackTraceMapState extends State { ); } - void _onChange() { - setState(() {}); - } - void _onMapCreated(GoogleMapController ctr) { if (mounted) { controller.mapController = ctr; @@ -130,6 +170,7 @@ class _GoogleTrackTraceMapState extends State { '[{"featureType": "poi","stylers": [{"visibility": "off"}]}]', ); } + controller.recenterCamera(); } } @@ -138,7 +179,6 @@ class _GoogleTrackTraceMapState extends State { (pointA.latitude + pointB.latitude) / 2, (pointA.longitude + pointB.longitude) / 2, ); - return CameraPosition( target: target, zoom: 13.0, @@ -147,26 +187,10 @@ class _GoogleTrackTraceMapState extends State { ); } - CameraUpdate moveCameraToCenter(LatLng pointA, LatLng pointB) { - return CameraUpdate.newLatLngBounds( - LatLngBounds( - southwest: LatLng( - min(pointA.latitude, pointB.latitude), - min(pointA.longitude, pointB.longitude), - ), - northeast: LatLng( - max(pointA.latitude, pointB.latitude), - max(pointA.longitude, pointB.longitude), - ), - ), - 50, - ); - } - void startRouteUpdateTimer() { calculateRoute(); // run at the start - Timer.periodic(Duration(seconds: widget.routeUpdateInterval), - (Timer timer) { + routeCalculateTimer = + Timer.periodic(widget.routeUpdateInterval, (Timer timer) { calculateRoute(); }); } @@ -175,10 +199,14 @@ class _GoogleTrackTraceMapState extends State { if (widget.timerPrecision != TimePrecision.updateOnly) { var updateInterval = (widget.timerPrecision == TimePrecision.everyMinute) ? 60 : 1; - Timer.periodic(Duration(seconds: updateInterval), (timer) { + markerUpdateTimer = + Timer.periodic(Duration(seconds: updateInterval), (timer) { if (controller.route != null) { + checkDestinationCloseBy(); controller.route = TrackTraceRoute( - controller.route!.duration - updateInterval, + (controller.route!.duration != 0) + ? controller.route!.duration - updateInterval + : 0, controller.route!.distance, controller.route!.line, ); @@ -188,33 +216,74 @@ class _GoogleTrackTraceMapState extends State { } void calculateRoute() { - 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, - ), - if (controller.mapController != null) - { - controller.mapController!.moveCamera( - moveCameraToCenter( - controller.start.position, - controller.end.position, - ), - ), - }, - setState(() { - lastRouteUpdate = DateTime.now(); - }) - }, - ); + 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 = [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; } } diff --git a/pubspec.yaml b/pubspec.yaml index 2656889..b6d9d13 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: google_track_trace description: An Iconica Flutter pluginin for Track & Trace Package -version: 0.0.1 +version: 1.0.0 environment: sdk: ">=2.14.0 <3.0.0"