diff --git a/example/assets/ic_custom_marker.png b/example/assets/ic_custom_marker.png new file mode 100644 index 0000000..3f43ac8 Binary files /dev/null and b/example/assets/ic_custom_marker.png differ diff --git a/example/assets/ic_location_on.png b/example/assets/ic_location_on.png new file mode 100644 index 0000000..865bc71 Binary files /dev/null and b/example/assets/ic_location_on.png differ diff --git a/example/assets/profile_picture.png b/example/assets/profile_picture.png new file mode 100644 index 0000000..2a31426 Binary files /dev/null and b/example/assets/profile_picture.png differ diff --git a/example/lib/main.dart b/example/lib/main.dart index 1a335c9..8ed427d 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,7 +1,9 @@ import 'dart:async'; import 'dart:math'; +import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart' show rootBundle; import 'package:google_track_trace/google_track_trace.dart'; class TrackTraceDemo extends StatefulWidget { @@ -16,9 +18,12 @@ class _TrackTraceDemoState extends State { int step = 1; int routeLength = 0; late final Timer timer; + BitmapDescriptor? startMarkerIcon; + BitmapDescriptor? destinationMarkerIcon; @override void initState() { + loadBitmapImages(); timer = Timer.periodic(const Duration(seconds: 2), (_) { moveAlongRoute(); }); @@ -39,43 +44,56 @@ class _TrackTraceDemoState extends State { body: GoogleTrackTraceMap( mapStylingTheme: GoogleTrackTraceMapTheme( themes: [ + GoogleMapThemeFeature( + featureType: 'all', + stylers: [ + {'saturation': '-50'}, + //{'invert_lightness': 'true'}, + ], + ), + GoogleMapThemeFeature( + featureType: 'landscape.natural.landcover', + stylers: [ + {'color': '#00ff00'}, + ], + ), GoogleMapThemeFeature( featureType: 'poi', stylers: [ {'visibility': 'off'}, ], ), + GoogleMapThemeFeature( + featureType: 'poi.park', + stylers: [ + {'visibility': 'on'}, + ], + ), GoogleMapThemeFeature( featureType: 'transit', stylers: [ {'visibility': 'off'}, ], ), - // GoogleMapThemeFeature( - // featureType: 'water', - // stylers: [ - // {'color': '#00ff00'} - // ], - // ), - // GoogleMapThemeFeature( - // featureType: 'road', - // stylers: [ - // {'color': '#000000'} - // ], - // ) ], ), - startPosition: const Marker( + startPosition: Marker( markerId: MarkerId('Start locatie'), + anchor: Offset(0.5, 0.5), position: LatLng(52.356057, 4.897540), + icon: startMarkerIcon ?? BitmapDescriptor.defaultMarker, ), - destinationPosition: const Marker( + destinationPosition: Marker( markerId: MarkerId('Bestemming Locatie'), + anchor: Offset(0.5, 0.5), position: LatLng(52.364709, 4.877157), + icon: destinationMarkerIcon ?? BitmapDescriptor.defaultMarker, ), + buildingsEnabled: false, googleAPIKey: 'AIzaSyDaxZX8TeQeVf5tW-D6A66WLl20arbWV6c', travelMode: TravelMode.walking, mapType: MapType.normal, + indoorViewEnabled: false, routeUpdateInterval: Duration(seconds: 30), timerPrecision: TimePrecision.everySecond, zoomGesturesEnabled: true, @@ -113,6 +131,35 @@ class _TrackTraceDemoState extends State { ); } + void loadBitmapImages() { + rootBundle.load('assets/profile_picture.png').then((value) { + convertBytesToCustomBitmapDescriptor( + value.buffer.asUint8List(), + size: 80, + addBorder: true, + borderColor: Colors.grey, + title: 'Alex', + titleBackgroundColor: Color(0xffff7884), + ).then((bitmap) { + startMarkerIcon = bitmap; + BitmapDescriptor.fromAssetImage( + ImageConfiguration(size: Size(100, 100)), + 'assets/ic_location_on.png', + ).then((value) { + setState(() { + destinationMarkerIcon = value; + controller?.end = Marker( + anchor: Offset(0.5, 0.5), + markerId: MarkerId('Bestemming Locatie'), + position: LatLng(52.364709, 4.877157), + icon: value, + ); + }); + }); + }); + }); + } + void getRandomPointOnMap() { // 51.989909, 6.234950 NW // 51.939909, 6.314950 SE @@ -133,10 +180,12 @@ class _TrackTraceDemoState extends State { controller!.route!.line.length > 1) { controller!.start = Marker( markerId: const MarkerId('Start Locatie'), + anchor: Offset(0.5, 0.5), position: LatLng( controller!.route!.line[step].latitude, controller!.route!.line[step].longitude, ), + icon: startMarkerIcon ?? BitmapDescriptor.defaultMarker, ); step++; routeLength = controller!.route!.distance; diff --git a/example/pubspec.lock b/example/pubspec.lock index 596e1f5..7333704 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -134,7 +134,7 @@ packages: path: ".." relative: true source: path - version: "1.0.0" + version: "1.0.1" html: dependency: transitive description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 9c9cb47..1006ee4 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -60,7 +60,8 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: + assets: + - assets/ # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg diff --git a/lib/google_track_trace.dart b/lib/google_track_trace.dart index 6467252..299169c 100644 --- a/lib/google_track_trace.dart +++ b/lib/google_track_trace.dart @@ -4,9 +4,12 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:math'; +import 'dart:typed_data'; +import 'dart:ui' as ui; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter_polyline_points/flutter_polyline_points.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:http/http.dart' as http; @@ -22,8 +25,8 @@ export 'package:google_maps_flutter/google_maps_flutter.dart' PolylineId, JointType, LatLng; - part 'src/controller.dart'; part 'src/directions_repository.dart'; part 'src/google_map.dart'; part 'src/google_map_theme.dart'; +part 'src/marker_generator.dart'; diff --git a/lib/src/google_map.dart b/lib/src/google_map.dart index 58648ad..cbf61ac 100644 --- a/lib/src/google_map.dart +++ b/lib/src/google_map.dart @@ -163,7 +163,10 @@ class _GoogleTrackTraceMapState extends State { if (mounted) { controller.mapController = ctr; if (widget.mapStylingTheme != null) { - ctr.setMapStyle(widget.mapStylingTheme!.getJson()); + ctr.setMapStyle(widget.mapStylingTheme!.getJson()).onError( + (error, stackTrace) => + throw GoogleMapsException(error.toString()), + ); } else { // No theme provided so switching to default ctr.setMapStyle( diff --git a/lib/src/google_map_theme.dart b/lib/src/google_map_theme.dart index 43ee8f5..5b72a5d 100644 --- a/lib/src/google_map_theme.dart +++ b/lib/src/google_map_theme.dart @@ -52,3 +52,7 @@ class GoogleMapThemeFeature { }; } } + +enum GoogleMapThemeFeatureType { + featureAll, +} diff --git a/lib/src/marker_generator.dart b/lib/src/marker_generator.dart new file mode 100644 index 0000000..1004c2d --- /dev/null +++ b/lib/src/marker_generator.dart @@ -0,0 +1,159 @@ +part of google_track_trace; + +Future convertBytesToCustomBitmapDescriptor( + Uint8List image, { + int size = 150, + bool addBorder = false, + Color borderColor = Colors.white, + double borderSize = 10, + String? title, + Color titleColor = Colors.white, + Color titleBackgroundColor = Colors.black, +}) async { + var pictureRecorder = ui.PictureRecorder(); + var canvas = Canvas(pictureRecorder); + var paint = Paint()..color; + var textPainter = TextPainter( + textDirection: TextDirection.ltr, + ); + var radius = size / 2; + + //make canvas clip path to prevent image drawing over the circle + var clipPath = Path() + ..addRRect( + RRect.fromRectAndRadius( + Rect.fromLTWH(0, 0, size.toDouble(), size.toDouble()), + Radius.circular(100), + ), + ) + ..addRRect( + RRect.fromRectAndRadius( + Rect.fromLTWH(0, size * 8 / 10, size.toDouble(), size * 3 / 10), + Radius.circular(100), + ), + ); + canvas.clipPath(clipPath); + + //paintImage + var imageUint8List = image; + var codec = await ui.instantiateImageCodec(imageUint8List); + var imageFI = await codec.getNextFrame(); + paintImage( + canvas: canvas, + rect: Rect.fromLTWH(0, 0, size.toDouble(), size.toDouble()), + image: imageFI.image, + ); + + if (addBorder) { + //draw Border + paint + ..color = borderColor + ..style = PaintingStyle.stroke + ..strokeWidth = borderSize; + canvas.drawCircle(Offset(radius, radius), radius, paint); + } + + if (title != null) { + var displayedTitle = ''; + if (title.length > 9) { + displayedTitle = title.substring(0, 9); + } else { + displayedTitle = title; + } + //draw Title background + paint + ..color = titleBackgroundColor + ..style = PaintingStyle.fill; + canvas.drawRRect( + RRect.fromRectAndRadius( + Rect.fromLTWH(0, size * 8 / 10, size.toDouble(), size * 3 / 10), + Radius.circular(100), + ), + paint, + ); + + //draw Title + textPainter + ..text = TextSpan( + text: displayedTitle, + style: TextStyle( + fontSize: radius / 2.5, + fontWeight: FontWeight.bold, + color: titleColor, + ), + ) + ..layout() + ..paint( + canvas, + Offset( + radius - textPainter.width / 2, + size * 9.5 / 10 - textPainter.height / 2, + ), + ); + } + + //convert canvas as PNG bytes + var _image = + await pictureRecorder.endRecording().toImage(size, (size * 1.1).toInt()); + var data = await _image.toByteData(format: ui.ImageByteFormat.png); + + //convert PNG bytes as BitmapDescriptor + return BitmapDescriptor.fromBytes(data!.buffer.asUint8List()); +} + +/// https://medium.com/@JBXBergDev/how-to-use-googlemap-markers-with-flutter-material-icons-38c4c975e928 +Future createBitmapDescriptorFromIconData( + IconData iconData, + double markerSize, + Color iconColor, + Color circleColor, + Color backgroundColor, +) async { + var pictureRecorder = ui.PictureRecorder(); + //var canvas = Canvas(pictureRecorder); + + //_paintCircleFill(canvas, backgroundColor); + //_paintCircleStroke(canvas, circleColor); + //_paintIcon(canvas, iconColor, iconData); + + var picture = pictureRecorder.endRecording(); + var image = await picture.toImage(markerSize.round(), markerSize.round()); + var bytes = await image.toByteData(format: ui.ImageByteFormat.png); + + return BitmapDescriptor.fromBytes(bytes!.buffer.asUint8List()); +} + +/// Paints the icon background + // void _paintCircleFill(Canvas canvas, Color color) { + // final paint = Paint() + // ..style = PaintingStyle.fill + // ..color = color; + // canvas.drawCircle(Offset(_circleOffset, _circleOffset), + //_fillCircleWidth, paint); + // } + + // /// Paints a circle around the icon + // void _paintCircleStroke(Canvas canvas, Color color) { + // final paint = Paint() + // ..style = PaintingStyle.stroke + // ..color = color + // ..strokeWidth = _circleStrokeWidth; + // canvas.drawCircle(Offset(_circleOffset, _circleOffset), + //_outlineCircleWidth, paint); + // } + + // /// Paints the icon + // void _paintIcon(Canvas canvas, Color color, IconData iconData) { + // final textPainter = TextPainter(textDirection: TextDirection.ltr); + // textPainter.text = TextSpan( + // text: String.fromCharCode(iconData.codePoint), + // style: TextStyle( + // letterSpacing: 0.0, + // fontSize: _iconSize, + // fontFamily: iconData.fontFamily, + // color: color, + // ) + // ); + // textPainter.layout(); + // textPainter.paint(canvas, Offset(_iconOffset, _iconOffset)); + // } diff --git a/pubspec.yaml b/pubspec.yaml index b6d9d13..4564d5d 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: 1.0.0 +version: 1.0.1 environment: sdk: ">=2.14.0 <3.0.0"