mirror of
https://github.com/Iconica-Development/flutter_google_track_and_trace.git
synced 2025-05-19 05:03:45 +02:00
working track&trace demo
This commit is contained in:
parent
65262045f8
commit
a39de81555
5 changed files with 252 additions and 126 deletions
|
@ -1,3 +1,6 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
import 'package:google_track_trace/google_track_trace.dart';
|
import 'package:google_track_trace/google_track_trace.dart';
|
||||||
|
@ -10,11 +13,30 @@ class TrackTraceDemo extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TrackTraceDemoState extends State<TrackTraceDemo> {
|
class _TrackTraceDemoState extends State<TrackTraceDemo> {
|
||||||
late final TrackTraceController controller;
|
TrackTraceController? controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
// TODO: implement initState
|
||||||
|
Timer.periodic(const Duration(seconds: 10), (_) {
|
||||||
|
print('updating marker');
|
||||||
|
getRandomPointOnMap();
|
||||||
|
});
|
||||||
|
|
||||||
|
Timer.periodic(const Duration(seconds: 60), (_) {
|
||||||
|
print('updating route');
|
||||||
|
getRandomRoute();
|
||||||
|
});
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('TrackTrace example')),
|
appBar: AppBar(
|
||||||
|
title: (controller == null)
|
||||||
|
? const Text('TrackTrace example')
|
||||||
|
: Text(controller!.duration.toString() + ' seconds')),
|
||||||
body: GoogleTrackTraceMap(
|
body: GoogleTrackTraceMap(
|
||||||
startPosition: const Marker(
|
startPosition: const Marker(
|
||||||
markerId: MarkerId('Start locatie'),
|
markerId: MarkerId('Start locatie'),
|
||||||
|
@ -24,14 +46,62 @@ class _TrackTraceDemoState extends State<TrackTraceDemo> {
|
||||||
markerId: MarkerId('Eind locatie'),
|
markerId: MarkerId('Eind locatie'),
|
||||||
position: LatLng(51.958996, 6.296520),
|
position: LatLng(51.958996, 6.296520),
|
||||||
),
|
),
|
||||||
travelMode: TravelMode.bicycling,
|
googleAPIKey: 'AIzaSyDaxZX8TeQeVf5tW-D6A66WLl20arbWV6c',
|
||||||
onMapCreated: (ctr) => {controller = ctr},
|
travelMode: TravelMode.walking,
|
||||||
|
routeUpdateInterval: 60,
|
||||||
|
routeLabel: 'Test route',
|
||||||
|
timerPrecision: TimePrecision.everySecond,
|
||||||
|
zoomGesturesEnabled: true,
|
||||||
|
onMapCreated: (ctr) => {
|
||||||
|
controller = ctr,
|
||||||
|
ctr.addListener(() {
|
||||||
|
setState(() {});
|
||||||
|
}),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateMap() {
|
||||||
|
controller!.current = const Marker(
|
||||||
|
markerId: MarkerId('Huidige locatie'),
|
||||||
|
position: LatLng(51.962578, 6.294439),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void getRandomPointOnMap() {
|
||||||
|
// 51.989909, 6.234950
|
||||||
|
|
||||||
|
// 51.939909, 6.314950
|
||||||
|
if (controller != null) {
|
||||||
|
controller!.current = Marker(
|
||||||
|
markerId: MarkerId('Huidige Locatie'),
|
||||||
|
position: LatLng(51.93 + Random().nextDouble() * 0.06,
|
||||||
|
6.23 + Random().nextDouble() * 0.08));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void getRandomRoute() {
|
||||||
|
// if (route != null) {
|
||||||
|
// print('removing point');
|
||||||
|
// PointLatLng point = route!.polylinePoints[1];
|
||||||
|
// trackTraceController.startMarker = Marker(
|
||||||
|
// markerId: MarkerId('Start locatie'),
|
||||||
|
// position: LatLng(point.latitude, point.longitude));
|
||||||
|
// }
|
||||||
|
if (controller != null) {
|
||||||
|
controller!.start = Marker(
|
||||||
|
markerId: MarkerId('Start Locatie'),
|
||||||
|
position: LatLng(51.93 + Random().nextDouble() * 0.06,
|
||||||
|
6.23 + Random().nextDouble() * 0.08));
|
||||||
|
controller!.end = Marker(
|
||||||
|
markerId: MarkerId('Bestemming Locatie'),
|
||||||
|
position: LatLng(51.93 + Random().nextDouble() * 0.06,
|
||||||
|
6.23 + Random().nextDouble() * 0.08));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const MaterialApp(home: TrackTraceDemo(
|
runApp(const MaterialApp(home: TrackTraceDemo()));
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ library google_track_trace;
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
|
@ -1,24 +1,48 @@
|
||||||
part of google_track_trace;
|
part of google_track_trace;
|
||||||
|
|
||||||
class TrackTraceController {
|
class TrackTraceController extends ChangeNotifier {
|
||||||
late final Completer<GoogleMapController> _mapController;
|
late final GoogleMapController _mapController;
|
||||||
|
Marker startPosition;
|
||||||
|
Marker destinationPosition;
|
||||||
|
Marker? currentPosition;
|
||||||
|
|
||||||
// get the duration
|
int durationInSeconds = 0;
|
||||||
|
|
||||||
// get the distance
|
TrackTraceController(Marker start, Marker destination)
|
||||||
|
: startPosition = start,
|
||||||
|
destinationPosition = destination;
|
||||||
|
|
||||||
// listen to updates on the source marker
|
set start(Marker start) {
|
||||||
|
startPosition = start;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
// listen to updates on the route
|
set current(Marker? current) {
|
||||||
|
currentPosition = current;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
set end(Marker end) {
|
||||||
|
destinationPosition = end;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
void dispose() {}
|
Marker get start => startPosition;
|
||||||
|
|
||||||
void setController(Completer<GoogleMapController> controller) {
|
Marker? get current => currentPosition;
|
||||||
|
|
||||||
|
Marker get end => destinationPosition;
|
||||||
|
|
||||||
|
set duration(int duration) {
|
||||||
|
durationInSeconds = duration;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
int get duration => durationInSeconds;
|
||||||
|
|
||||||
|
set mapController(GoogleMapController controller) {
|
||||||
_mapController = controller;
|
_mapController = controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
Completer<GoogleMapController> getController() {
|
GoogleMapController get mapController => _mapController;
|
||||||
return _mapController;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,6 @@ class DirectionsRepository {
|
||||||
HttpHeaders.contentTypeHeader: 'application/json',
|
HttpHeaders.contentTypeHeader: 'application/json',
|
||||||
});
|
});
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
print(response.body);
|
|
||||||
return Directions.fromMap(jsonDecode(response.body));
|
return Directions.fromMap(jsonDecode(response.body));
|
||||||
}
|
}
|
||||||
} on HttpException catch (e) {
|
} on HttpException catch (e) {
|
||||||
|
@ -43,8 +42,8 @@ class DirectionsRepository {
|
||||||
class Directions {
|
class Directions {
|
||||||
final LatLngBounds bounds;
|
final LatLngBounds bounds;
|
||||||
final List<PointLatLng> polylinePoints;
|
final List<PointLatLng> polylinePoints;
|
||||||
final String totalDistance;
|
final int totalDistance;
|
||||||
final String totalDuration;
|
final int totalDuration;
|
||||||
|
|
||||||
const Directions({
|
const Directions({
|
||||||
required this.bounds,
|
required this.bounds,
|
||||||
|
@ -67,12 +66,12 @@ class Directions {
|
||||||
northeast: LatLng(northeast['lat'], northeast['lng']),
|
northeast: LatLng(northeast['lat'], northeast['lng']),
|
||||||
);
|
);
|
||||||
|
|
||||||
String distance = '';
|
int distance = 0;
|
||||||
String duration = '';
|
int duration = 0;
|
||||||
if ((data['legs'] as List).isNotEmpty) {
|
if ((data['legs'] as List).isNotEmpty) {
|
||||||
final leg = data['legs'][0];
|
final leg = data['legs'][0];
|
||||||
distance = leg['distance']['text'];
|
distance = leg['distance']['value'];
|
||||||
duration = leg['duration']['text'];
|
duration = leg['duration']['value'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return Directions(
|
return Directions(
|
||||||
|
|
|
@ -1,149 +1,181 @@
|
||||||
part of google_track_trace;
|
part of google_track_trace;
|
||||||
|
|
||||||
typedef void MapCreatedCallback(TrackTraceController controller);
|
// typedef void MapCreatedCallback(TrackTraceController controller);
|
||||||
|
enum TimePrecision { updateOnly, everySecond, everyMinute }
|
||||||
|
|
||||||
class GoogleTrackTraceMap extends StatefulWidget {
|
class GoogleTrackTraceMap extends StatefulWidget {
|
||||||
GoogleTrackTraceMap({
|
GoogleTrackTraceMap({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.initialCameraPosition = const CameraPosition(
|
required this.onMapCreated,
|
||||||
target: LatLng(51.965578, 6.293439),
|
|
||||||
zoom: 15.0), // doetinchem default initialCamera
|
|
||||||
this.onMapCreated,
|
|
||||||
required this.startPosition,
|
required this.startPosition,
|
||||||
required this.destinationPosition,
|
required this.destinationPosition,
|
||||||
this.currentPosition,
|
required this.googleAPIKey,
|
||||||
|
required this.routeUpdateInterval,
|
||||||
|
this.timerPrecision = TimePrecision.everyMinute,
|
||||||
this.travelMode = TravelMode.driving,
|
this.travelMode = TravelMode.driving,
|
||||||
|
this.routeLabel = '',
|
||||||
|
this.compassEnabled = false,
|
||||||
|
this.zoomControlsEnabled = false,
|
||||||
|
this.zoomGesturesEnabled = false,
|
||||||
|
this.mapToolbarEnabled = false,
|
||||||
|
this.mapType = MapType.normal,
|
||||||
|
this.buildingsEnabled = false,
|
||||||
}) : assert(true),
|
}) : assert(true),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
CameraPosition initialCameraPosition;
|
|
||||||
|
|
||||||
/// Callback method for when the map is ready to be used.
|
/// Callback method for when the map is ready to be used.
|
||||||
///
|
///
|
||||||
/// Used to receive a [TrackTraceController] for this [GoogleTrackTraceMap].
|
/// Used to receive a [TrackTraceController] for this [GoogleTrackTraceMap].
|
||||||
/// this [TrackTraceController] also contains the [GoogleMapController]
|
/// this [TrackTraceController] also contains the [GoogleMapController]
|
||||||
final MapCreatedCallback? onMapCreated;
|
final void Function(TrackTraceController) onMapCreated;
|
||||||
|
|
||||||
final TravelMode travelMode;
|
final TravelMode travelMode;
|
||||||
Marker? startPosition;
|
|
||||||
Marker? destinationPosition;
|
final String routeLabel;
|
||||||
Marker? currentPosition;
|
|
||||||
|
final int routeUpdateInterval;
|
||||||
|
|
||||||
|
final TimePrecision timerPrecision;
|
||||||
|
|
||||||
|
final Marker startPosition;
|
||||||
|
final Marker destinationPosition;
|
||||||
|
|
||||||
|
final bool compassEnabled;
|
||||||
|
final bool zoomControlsEnabled;
|
||||||
|
final bool zoomGesturesEnabled;
|
||||||
|
final bool mapToolbarEnabled;
|
||||||
|
final bool buildingsEnabled;
|
||||||
|
final MapType mapType;
|
||||||
|
|
||||||
|
CameraPosition initialCameraPosition = const CameraPosition(
|
||||||
|
// doetinchem default initialCamera
|
||||||
|
target: LatLng(51.965578, 6.293439),
|
||||||
|
zoom: 12.0);
|
||||||
|
|
||||||
|
final String googleAPIKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State createState() => _GoogleTrackTraceMapState();
|
State createState() => _GoogleTrackTraceMapState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GoogleTrackTraceMapState extends State<GoogleTrackTraceMap> {
|
class _GoogleTrackTraceMapState extends State<GoogleTrackTraceMap> {
|
||||||
final Completer<TrackTraceController> _controller =
|
late final TrackTraceController trackTraceController;
|
||||||
Completer<TrackTraceController>();
|
|
||||||
late final GoogleMapController _mapController;
|
|
||||||
bool mapLoading = true;
|
|
||||||
|
|
||||||
Directions? route; // this needs to be in the controller
|
Directions? route;
|
||||||
|
DateTime lastRouteUpdate = DateTime.now();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
trackTraceController =
|
||||||
|
TrackTraceController(widget.startPosition, widget.destinationPosition);
|
||||||
|
trackTraceController.addListener(_onChange);
|
||||||
|
widget.onMapCreated(trackTraceController);
|
||||||
|
startRouteUpdateTimer();
|
||||||
|
startMarkerUpdateTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
trackTraceController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GoogleMap(
|
||||||
|
initialCameraPosition: calculateCameraPosition(
|
||||||
|
trackTraceController.start.position,
|
||||||
|
trackTraceController.end.position),
|
||||||
|
onMapCreated: _onMapCreated,
|
||||||
|
compassEnabled: widget.compassEnabled,
|
||||||
|
zoomControlsEnabled: widget.zoomControlsEnabled,
|
||||||
|
zoomGesturesEnabled: widget.zoomGesturesEnabled,
|
||||||
|
mapToolbarEnabled: widget.mapToolbarEnabled,
|
||||||
|
mapType: widget.mapType,
|
||||||
|
buildingsEnabled: widget.buildingsEnabled,
|
||||||
|
markers: {
|
||||||
|
// style the markers
|
||||||
|
trackTraceController.start,
|
||||||
|
trackTraceController.end,
|
||||||
|
if (trackTraceController.current != null)
|
||||||
|
trackTraceController.current!,
|
||||||
|
},
|
||||||
|
polylines: {
|
||||||
|
if (route != null)
|
||||||
|
Polyline(
|
||||||
|
polylineId: PolylineId(widget.routeLabel),
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
width: 4,
|
||||||
|
points: route!.polylinePoints
|
||||||
|
.map((e) => LatLng(e.latitude, e.longitude))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onChange() {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
void _onMapCreated(GoogleMapController controller) {
|
void _onMapCreated(GoogleMapController controller) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
_mapController = controller;
|
trackTraceController.mapController = controller;
|
||||||
controller.setMapStyle(
|
controller.setMapStyle(
|
||||||
'[{"featureType": "poi","stylers": [{"visibility": "off"}]}]');
|
'[{"featureType": "poi","stylers": [{"visibility": "off"}]}]'); // move to dart json file
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CameraPosition calculateCameraPosition(LatLng pointA, LatLng pointB) {
|
CameraPosition calculateCameraPosition(LatLng pointA, LatLng pointB) {
|
||||||
LatLng target = LatLng((pointA.latitude + pointB.latitude) / 2,
|
LatLng target = LatLng((pointA.latitude + pointB.latitude) / 2,
|
||||||
(pointA.longitude + pointB.longitude) / 2);
|
(pointA.longitude + pointB.longitude) / 2);
|
||||||
double calculatedZoom = 16.0;
|
double calculatedZoom = 13.0; // TODO calculate this zoom
|
||||||
|
|
||||||
return CameraPosition(
|
return CameraPosition(
|
||||||
target: target, zoom: calculatedZoom, tilt: 0.0, bearing: 0.0);
|
target: target, zoom: calculatedZoom, tilt: 0.0, bearing: 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
void startRouteUpdateTimer() {
|
||||||
Widget build(BuildContext context) {
|
calculateRoute(); // run at the start
|
||||||
return (!mapLoading)
|
Timer.periodic(Duration(seconds: widget.routeUpdateInterval), (timer) {
|
||||||
? GoogleMap(
|
|
||||||
initialCameraPosition: calculateCameraPosition(
|
|
||||||
widget.startPosition!.position,
|
|
||||||
widget.destinationPosition!.position),
|
|
||||||
onMapCreated: _onMapCreated,
|
|
||||||
compassEnabled: false,
|
|
||||||
zoomControlsEnabled: false,
|
|
||||||
mapToolbarEnabled: false,
|
|
||||||
mapType: MapType.normal,
|
|
||||||
buildingsEnabled: false,
|
|
||||||
markers: {
|
|
||||||
// style the markers
|
|
||||||
if (widget.startPosition != null) widget.startPosition!,
|
|
||||||
if (widget.destinationPosition != null)
|
|
||||||
widget.destinationPosition!,
|
|
||||||
if (widget.currentPosition != null) widget.currentPosition!,
|
|
||||||
},
|
|
||||||
polylines: {
|
|
||||||
if (route != null)
|
|
||||||
Polyline(
|
|
||||||
polylineId: const PolylineId('Route van verzorger'),
|
|
||||||
color: const Color(0xFFFF7884),
|
|
||||||
width: 3,
|
|
||||||
points: route!.polylinePoints
|
|
||||||
.map((e) => LatLng(e.latitude, e.longitude))
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: const [
|
|
||||||
CircularProgressIndicator(),
|
|
||||||
Text('Map is Loading'),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
updateMaps();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() async {
|
|
||||||
super.dispose();
|
|
||||||
TrackTraceController controller = await _controller.future;
|
|
||||||
controller.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateMaps() async {
|
|
||||||
if (route == null) {
|
|
||||||
calculateRoute();
|
calculateRoute();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void startMarkerUpdateTimer() {
|
||||||
|
if (widget.timerPrecision != TimePrecision.updateOnly) {
|
||||||
|
Timer.periodic(
|
||||||
|
Duration(
|
||||||
|
seconds: (widget.timerPrecision == TimePrecision.everyMinute)
|
||||||
|
? 60
|
||||||
|
: 1), (timer) {
|
||||||
|
updateDurationTimer();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateDurationTimer() {
|
||||||
|
if (route != null) {
|
||||||
|
trackTraceController.duration = route!.totalDuration -
|
||||||
|
DateTime.now().difference(lastRouteUpdate).inSeconds;
|
||||||
}
|
}
|
||||||
// get the current GPS data from the user
|
|
||||||
// getLocationMarker().then((value) => {
|
|
||||||
// setState(() {
|
|
||||||
// _current = Marker(
|
|
||||||
// markerId: MarkerId('Huidige locatie'),
|
|
||||||
// position: LatLng(value.latitude, value.longitude),
|
|
||||||
// );
|
|
||||||
// })
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void calculateRoute() async {
|
void calculateRoute() async {
|
||||||
print('calculating route');
|
DirectionsRepository()
|
||||||
if (widget.startPosition != null && widget.destinationPosition != null) {
|
.getDirections(
|
||||||
DirectionsRepository()
|
origin: trackTraceController.start.position,
|
||||||
.getDirections(
|
destination: trackTraceController.end.position,
|
||||||
origin: widget.startPosition!.position,
|
mode: widget.travelMode,
|
||||||
destination: widget.destinationPosition!.position,
|
key: widget.googleAPIKey,
|
||||||
mode: widget.travelMode,
|
)
|
||||||
key: 'AIzaSyDaxZX8TeQeVf5tW-D6A66WLl20arbWV6c',
|
.then((value) => {
|
||||||
)
|
trackTraceController.duration = value.totalDuration,
|
||||||
.then((value) => setState(() {
|
trackTraceController.mapController.moveCamera(CameraUpdate.newCameraPosition(calculateCameraPosition(trackTraceController.start.position, trackTraceController.end.position))),
|
||||||
|
setState(() {
|
||||||
|
lastRouteUpdate = DateTime.now();
|
||||||
route = value;
|
route = value;
|
||||||
mapLoading = false;
|
})
|
||||||
}));
|
});
|
||||||
// setState(() {
|
|
||||||
// route = directions;
|
|
||||||
// mapLoading = true;
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue