diff --git a/example/.flutter-plugins-dependencies b/example/.flutter-plugins-dependencies index 1a9a108..d8aa977 100644 --- a/example/.flutter-plugins-dependencies +++ b/example/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"google_maps_flutter","path":"/Users/dammyololade/.pub-cache/hosted/pub.dartlang.org/google_maps_flutter-1.2.0/","dependencies":[]}],"android":[{"name":"flutter_plugin_android_lifecycle","path":"/Users/dammyololade/.pub-cache/hosted/pub.dartlang.org/flutter_plugin_android_lifecycle-1.0.11/","dependencies":[]},{"name":"google_maps_flutter","path":"/Users/dammyololade/.pub-cache/hosted/pub.dartlang.org/google_maps_flutter-1.2.0/","dependencies":["flutter_plugin_android_lifecycle"]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"google_maps_flutter","dependencies":["flutter_plugin_android_lifecycle"]}],"date_created":"2021-04-16 23:27:17.648271","version":"2.0.1"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"google_maps_flutter","path":"C:\\\\Users\\\\aaqib\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dartlang.org\\\\google_maps_flutter-1.2.0\\\\","dependencies":[]}],"android":[{"name":"flutter_plugin_android_lifecycle","path":"C:\\\\Users\\\\aaqib\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dartlang.org\\\\flutter_plugin_android_lifecycle-1.0.11\\\\","dependencies":[]},{"name":"google_maps_flutter","path":"C:\\\\Users\\\\aaqib\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dartlang.org\\\\google_maps_flutter-1.2.0\\\\","dependencies":["flutter_plugin_android_lifecycle"]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"google_maps_flutter","dependencies":["flutter_plugin_android_lifecycle"]}],"date_created":"2021-07-01 12:41:50.745027","version":"2.3.0-24.1.pre"} \ No newline at end of file diff --git a/example/.gitignore b/example/.gitignore index 07488ba..a300c32 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -22,6 +22,7 @@ **/doc/api/ .dart_tool/ .flutter-plugins +.flutter-plugins-dependencies .packages .pub-cache/ .pub/ diff --git a/example/ios/Flutter/flutter_export_environment.sh b/example/ios/Flutter/flutter_export_environment.sh index 205fdb4..be38926 100755 --- a/example/ios/Flutter/flutter_export_environment.sh +++ b/example/ios/Flutter/flutter_export_environment.sh @@ -1,10 +1,10 @@ #!/bin/sh # This is a generated file; do not edit or check into version control. -export "FLUTTER_ROOT=/Users/dammyololade/fvm/versions/2.0.1" -export "FLUTTER_APPLICATION_PATH=/Users/dammyololade/StudioProjects/novu/flutter_polyline_points/example" -export "FLUTTER_TARGET=lib/main.dart" +export "FLUTTER_ROOT=C:\flutter" +export "FLUTTER_APPLICATION_PATH=D:\Programming\halal_hacks\flutter_polyline_points\example" +export "COCOAPODS_PARALLEL_CODE_SIGN=true" +export "FLUTTER_TARGET=lib\main.dart" export "FLUTTER_BUILD_DIR=build" -export "SYMROOT=${SOURCE_ROOT}/../build/ios" export "FLUTTER_BUILD_NAME=1.0.0" export "FLUTTER_BUILD_NUMBER=1" export "DART_OBFUSCATION=false" diff --git a/example/lib/main.dart b/example/lib/main.dart index 54640fa..ea6df20 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -100,12 +100,12 @@ class _MapScreenState extends State { _getPolyline() async { PolylineResult result = await polylinePoints.getRouteBetweenCoordinates( googleAPiKey, - PointLatLng(_originLatitude, _originLongitude), - PointLatLng(_destLatitude, _destLongitude), + origin: PointLatLng(_originLatitude, _originLongitude), + destination: PointLatLng(_destLatitude, _destLongitude), travelMode: TravelMode.driving, wayPoints: [PolylineWayPoint(location: "Sabo, Yaba Lagos Nigeria")]); - if (result.points.isNotEmpty) { - result.points.forEach((PointLatLng point) { + if (result.routes[0].points.isNotEmpty) { + result.routes[0].points.forEach((PointLatLng point) { polylineCoordinates.add(LatLng(point.latitude, point.longitude)); }); } diff --git a/example/pubspec.lock b/example/pubspec.lock index 8e6a75d..e5f0f31 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0" + version: "2.7.0" boolean_selector: dependency: transitive description: @@ -75,7 +75,7 @@ packages: path: ".." relative: true source: path - version: "0.2.6" + version: "1.0.1" flutter_test: dependency: "direct dev" description: flutter @@ -101,7 +101,7 @@ packages: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.13.1" + version: "0.13.3" http_parser: dependency: transitive description: @@ -122,7 +122,7 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.4.0" path: dependency: transitive description: @@ -155,7 +155,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" stack_trace: dependency: transitive description: @@ -197,7 +197,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19" + version: "0.4.0" typed_data: dependency: transitive description: diff --git a/lib/flutter_polyline_points.dart b/lib/flutter_polyline_points.dart index f0c4327..4279405 100644 --- a/lib/flutter_polyline_points.dart +++ b/lib/flutter_polyline_points.dart @@ -11,6 +11,11 @@ export 'src/utils/polyline_waypoint.dart'; export 'src/network_util.dart'; export 'src/PointLatLng.dart'; export 'src/utils/polyline_result.dart'; +export 'src/utils/bounds.dart'; +export 'src/utils/geocoded_waypoint.dart'; +export 'src/utils/leg.dart'; +export 'src/utils/route.dart'; +export 'src/utils/status_code.dart'; class PolylinePoints { NetworkUtil util = NetworkUtil(); @@ -19,23 +24,49 @@ class PolylinePoints { /// which can be used to draw polyline between this two positions /// Future getRouteBetweenCoordinates( - String googleApiKey, PointLatLng origin, PointLatLng destination, - {TravelMode travelMode = TravelMode.driving, - List wayPoints = const [], - bool avoidHighways = false, - bool avoidTolls = false, - bool avoidFerries = true, - bool optimizeWaypoints = false}) async { - return await util.getRouteBetweenCoordinates( - googleApiKey, - origin, - destination, - travelMode, - wayPoints, - avoidHighways, - avoidTolls, - avoidFerries, - optimizeWaypoints); + String googleApiKey, { + PointLatLng? origin, + PointLatLng? destination, + String? originPlaceId, + String? destinationPlaceId, + TravelMode travelMode = TravelMode.driving, + List wayPoints = const [], + bool avoidHighways = false, + bool avoidTolls = false, + bool avoidFerries = true, + bool optimizeWaypoints = false, + bool alternatives = false, + }) { + assert( + (origin != null || originPlaceId != null), + "origin or originPlaceId must be specified", + ); + assert( + (destination != null || destinationPlaceId != null), + "destination or destinationPlaceId must be specified", + ); + return util.getRouteBetweenCoordinates( + googleApiKey, + origin, + destination, + originPlaceId, + destinationPlaceId, + travelMode, + wayPoints, + avoidHighways, + avoidTolls, + avoidFerries, + optimizeWaypoints, + alternatives, + ); + } + + /// Decode the json body returned by the Directions API. + /// + /// This is useful if you want to call the API on your own server + /// instead of on the client. + PolylineResult parseJson(dynamic json) { + return util.parseJson(json); } /// Decode and encoded google polyline diff --git a/lib/src/PointLatLng.dart b/lib/src/PointLatLng.dart index 2b0364e..4ac3758 100644 --- a/lib/src/PointLatLng.dart +++ b/lib/src/PointLatLng.dart @@ -1,14 +1,9 @@ - - /// A pair of latitude and longitude coordinates, stored as degrees. class PointLatLng { - /// Creates a geographical location specified in degrees [latitude] and /// [longitude]. /// - const PointLatLng(double latitude, double longitude) - : this.latitude = latitude, - this.longitude = longitude; + const PointLatLng(this.latitude, this.longitude); /// The latitude in degrees. final double latitude; @@ -16,8 +11,19 @@ class PointLatLng { /// The longitude in degrees final double longitude; + @override + bool operator ==(other) { + if (identical(this, other)) return true; + return (other is PointLatLng) && + other.latitude == latitude && + other.longitude == longitude; + } + + @override + int get hashCode => latitude.hashCode ^ longitude.hashCode; + @override String toString() { return "lat: $latitude / longitude: $longitude"; } -} \ No newline at end of file +} diff --git a/lib/src/network_util.dart b/lib/src/network_util.dart index 20bc0df..2c772eb 100644 --- a/lib/src/network_util.dart +++ b/lib/src/network_util.dart @@ -1,5 +1,9 @@ import 'dart:convert'; +import 'package:flutter_polyline_points/src/utils/bounds.dart'; +import 'package:flutter_polyline_points/src/utils/leg.dart'; +import 'package:flutter_polyline_points/src/utils/route.dart'; +import 'package:flutter_polyline_points/src/utils/status_code.dart'; import 'package:http/http.dart' as http; import '../src/PointLatLng.dart'; @@ -8,56 +12,120 @@ import '../src/utils/request_enums.dart'; import 'utils/polyline_result.dart'; class NetworkUtil { - static const String STATUS_OK = "ok"; - ///Get the encoded string from google directions api /// Future getRouteBetweenCoordinates( - String googleApiKey, - PointLatLng origin, - PointLatLng destination, - TravelMode travelMode, - List wayPoints, - bool avoidHighways, - bool avoidTolls, - bool avoidFerries, - bool optimizeWaypoints) async { - String mode = travelMode.toString().replaceAll('TravelMode.', ''); - PolylineResult result = PolylineResult(); - var params = { - "origin": "${origin.latitude},${origin.longitude}", - "destination": "${destination.latitude},${destination.longitude}", + String googleApiKey, + PointLatLng? origin, + PointLatLng? destination, + String? originPlaceId, + String? destinationPlaceId, + TravelMode travelMode, + List wayPoints, + bool avoidHighways, + bool avoidTolls, + bool avoidFerries, + bool optimizeWaypoints, + bool alternatives, + ) async { + + final mode = travelMode.toString().replaceAll('TravelMode.', ''); + final result = PolylineResult(); + + final params = { "mode": mode, - "avoidHighways": "$avoidHighways", - "avoidFerries": "$avoidFerries", - "avoidTolls": "$avoidTolls", + "alternatives": "$alternatives", "key": googleApiKey }; + + if (origin != null) { + params["origin"] = "${origin.latitude},${origin.longitude}"; + } else { + params["origin"] = 'place_id:${originPlaceId!}'; + } + + if (destination != null) { + params["destination"] = + "${destination.latitude},${destination.longitude}"; + } else { + params["destination"] = 'place_id:${destinationPlaceId!}'; + } + if (wayPoints.isNotEmpty) { - List wayPointsArray = []; - wayPoints.forEach((point) => wayPointsArray.add(point.location)); + final wayPointsArray = wayPoints.map((point) => point.location); String wayPointsString = wayPointsArray.join('|'); if (optimizeWaypoints) { wayPointsString = 'optimize:true|$wayPointsString'; } - params.addAll({"waypoints": wayPointsString}); + params["waypoints"] = wayPointsString; } + final avoidences = []; + if (avoidHighways) avoidences.add("highways"); + if (avoidTolls) avoidences.add("tolls"); + if (avoidFerries) avoidences.add("ferries"); + if (avoidences.isNotEmpty) params["avoid"] = avoidences.join('|'); + Uri uri = Uri.https("maps.googleapis.com", "maps/api/directions/json", params); - // print('GOOGLE MAPS URL: ' + url); var response = await http.get(uri); if (response.statusCode == 200) { var parsedJson = json.decode(response.body); - result.status = parsedJson["status"]; - if (parsedJson["status"]?.toLowerCase() == STATUS_OK && - parsedJson["routes"] != null && - parsedJson["routes"].isNotEmpty) { - result.points = decodeEncodedPolyline( - parsedJson["routes"][0]["overview_polyline"]["points"]); - } else { - result.errorMessage = parsedJson["error_message"]; + return parseJson(parsedJson); + } + return result; + } + + PolylineResult parseJson(dynamic parsedJson) { + final result = PolylineResult(); + result.status = StatusCode(parsedJson["status"]); + if (result.status == StatusCode.OK && + parsedJson["routes"] != null && + parsedJson["routes"].isNotEmpty) { + final routes = []; + + for (final route in parsedJson["routes"]) { + final bounds = Bounds( + PointLatLng( + route["bounds"]["northeast"]["lat"], + route["bounds"]["northeast"]["lng"], + ), + PointLatLng( + route["bounds"]["southwest"]["lat"], + route["bounds"]["southwest"]["lng"], + ), + ); + + final legs = []; + + for (final leg in route["legs"]) { + legs.add(Leg( + leg["distance"]["value"], + leg["distance"]["text"], + Duration(seconds: leg["duration"]["value"]), + leg["duration"]["text"], + leg["end_address"], + PointLatLng( + leg["end_location"]["lat"], + leg["end_location"]["lng"], + ), + leg["start_address"], + PointLatLng( + leg["start_location"]["lat"], + leg["start_location"]["lng"], + ), + )); + } + + final points = decodeEncodedPolyline( + route["overview_polyline"]["points"], + ); + + routes.add(Route(bounds, legs, points)); } + result.routes = routes; + } else { + result.errorMessage = parsedJson["error_message"]; } return result; } diff --git a/lib/src/utils/bounds.dart b/lib/src/utils/bounds.dart new file mode 100644 index 0000000..f84f2f7 --- /dev/null +++ b/lib/src/utils/bounds.dart @@ -0,0 +1,23 @@ +import 'package:flutter_polyline_points/flutter_polyline_points.dart'; + +/// Northeast and southwest bounds for a route. +class Bounds { + final PointLatLng northeast; + final PointLatLng southwest; + + const Bounds(this.northeast, this.southwest); + + @override + bool operator ==(other) { + if (identical(this, other)) return true; + return (other is Bounds) && + other.northeast == northeast && + other.southwest == southwest; + } + + @override + int get hashCode => northeast.hashCode ^ southwest.hashCode; + + @override + String toString() => 'Bounds($northeast, $southwest)'; +} diff --git a/lib/src/utils/geocoded_waypoint.dart b/lib/src/utils/geocoded_waypoint.dart new file mode 100644 index 0000000..3844a4d --- /dev/null +++ b/lib/src/utils/geocoded_waypoint.dart @@ -0,0 +1,22 @@ +import 'package:flutter_polyline_points/src/utils/status_code.dart'; + +class GeocodedWaypoint { + /// Status of operation. + final StatusCode geocoderStatus; + + /// Google Maps Identifier for a location. + final String placeId; + + GeocodedWaypoint(this.geocoderStatus, this.placeId); + + @override + bool operator ==(other) { + if (identical(this, other)) return true; + return (other is GeocodedWaypoint) && + other.geocoderStatus == geocoderStatus && + other.placeId == placeId; + } + + @override + int get hashCode => geocoderStatus.hashCode ^ placeId.hashCode; +} diff --git a/lib/src/utils/leg.dart b/lib/src/utils/leg.dart new file mode 100644 index 0000000..449a927 --- /dev/null +++ b/lib/src/utils/leg.dart @@ -0,0 +1,63 @@ +import 'package:flutter_polyline_points/flutter_polyline_points.dart'; + +class Leg { + /// Distance in meters. + final int distance; + + /// Readable text for the distance. + final String distanceString; + + /// Duration in seconds. + final Duration duration; + + /// Readable text for the duration. + final String durationString; + + /// Geocoded text of the [endLocation]. + final String endAddress; + + /// Latitude/Longitude of where the leg ends. + final PointLatLng endLocation; + + /// Geocoded text of the [startLocation]. + final String startAddress; + + /// Latitude/Longitude of where the leg begins. + final PointLatLng startLocation; + + Leg( + this.distance, + this.distanceString, + this.duration, + this.durationString, + this.endAddress, + this.endLocation, + this.startAddress, + this.startLocation, + ); + + @override + bool operator ==(other) { + if (identical(this, other)) return true; + return (other is Leg) && + other.distance == distance && + other.distanceString == distanceString && + other.duration == duration && + other.durationString == durationString && + other.endAddress == endAddress && + other.endLocation == endLocation && + other.startAddress == startAddress && + other.startLocation == startLocation; + } + + @override + int get hashCode => + distance.hashCode ^ + distanceString.hashCode ^ + duration.hashCode ^ + durationString.hashCode ^ + endAddress.hashCode ^ + endLocation.hashCode ^ + startAddress.hashCode ^ + startLocation.hashCode; +} diff --git a/lib/src/utils/polyline_result.dart b/lib/src/utils/polyline_result.dart index d3efc56..d2fa2a6 100644 --- a/lib/src/utils/polyline_result.dart +++ b/lib/src/utils/polyline_result.dart @@ -1,24 +1,32 @@ -import '../../flutter_polyline_points.dart'; +import 'package:flutter_polyline_points/src/utils/geocoded_waypoint.dart'; +import 'package:flutter_polyline_points/src/utils/route.dart'; +import 'package:flutter_polyline_points/src/utils/status_code.dart'; /// description: /// project: flutter_polyline_points -/// @package: +/// @package: /// @author: dammyololade /// created on: 13/05/2020 class PolylineResult { - /// the api status retuned from google api /// /// returns OK if the api call is successful - String? status; + StatusCode status; /// list of decoded points - List points; + List routes; /// the error message returned from google, if none, the result will be empty - String? errorMessage; - - PolylineResult({this.status, this.points = const [], this.errorMessage = ""}); + String errorMessage; + /// Correspond to the origin, the waypoints in the order they are specified, + /// and the destination. + List geocodedWaypoints; -} \ No newline at end of file + PolylineResult({ + this.status = StatusCode.OK, + this.routes = const [], + this.errorMessage = "", + this.geocodedWaypoints = const [], + }); +} diff --git a/lib/src/utils/route.dart b/lib/src/utils/route.dart new file mode 100644 index 0000000..279647d --- /dev/null +++ b/lib/src/utils/route.dart @@ -0,0 +1,51 @@ +import 'package:flutter_polyline_points/flutter_polyline_points.dart'; +import 'package:flutter_polyline_points/src/utils/bounds.dart'; +import 'package:flutter_polyline_points/src/utils/leg.dart'; + +class Route { + /// Coordinates of Northeast and Southwest bounds + final Bounds? bounds; + + /// Each leg represents the journey from one waypoint to another starting + /// from the origin to the final destination. + final List legs; + + /// Decoded list of waypoints from the overview polylines. + final List points; + + const Route(this.bounds, this.legs, this.points); + + /// Total distance of all legs in meters. + int get totalDistance { + int distance = 0; + for (final leg in legs) { + distance += leg.distance; + } + return distance; + } + + /// Total duration of all legs. + Duration get totalDuration { + Duration duration = Duration(); + for (final leg in legs) { + duration += leg.duration; + } + return duration; + } + + + @override + bool operator ==(other) { + if (identical(this, other)) return true; + return (other is Route) && + other.bounds == bounds && + other.points == points && + other.legs == legs; + } + + @override + int get hashCode => bounds.hashCode ^ points.hashCode ^ legs.hashCode; + + @override + String toString() => 'Route($bounds, $points, $legs)'; +} diff --git a/lib/src/utils/status_code.dart b/lib/src/utils/status_code.dart new file mode 100644 index 0000000..ec6f2fe --- /dev/null +++ b/lib/src/utils/status_code.dart @@ -0,0 +1,32 @@ +/// Information for each code can be found at: https://developers.google.com/maps/documentation/directions/get-directions#StatusCodes +class StatusCode { + final String _value; + + const StatusCode(String value) : _value = value; + + String get value => _value; + + static const OK = StatusCode("OK"); + static const NOT_FOUND = StatusCode("NOT_FOUND"); + static const ZERO_RESULTS = StatusCode("ZERO_RESULTS"); + static const MAX_WAYPOINTS_EXCEEDED = StatusCode("MAX_WAYPOINTS_EXCEEDED"); + static const MAX_ROUTE_LENGTH_EXCEEDED = + StatusCode("MAX_ROUTE_LENGTH_EXCEEDED"); + static const INVALID_REQUEST = StatusCode("INVALID_REQUEST"); + static const OVER_DAILY_LIMIT = StatusCode("OVER_DAILY_LIMIT"); + static const OVER_QUERY_LIMIT = StatusCode("OVER_QUERY_LIMIT"); + static const REQUEST_DENIED = StatusCode("REQUEST_DENIED"); + static const UNKNOWN_ERROR = StatusCode("UNKNOWN_ERROR"); + + @override + bool operator ==(other) { + if (identical(this, other)) return true; + return other is StatusCode && other._value == _value; + } + + @override + int get hashCode => _value.hashCode; + + @override + String toString() => _value; +} diff --git a/pubspec.yaml b/pubspec.yaml index 86dbf82..a2cefe3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_polyline_points description: A flutter package to get polyline points by either passing the coordinates or google encoded polyline string -version: 1.0.0 +version: 1.0.1 homepage: https://github.com/Dammyololade/flutter_polyline_points environment: @@ -9,7 +9,7 @@ environment: dependencies: flutter: sdk: flutter - http: ^0.13.1 + http: ^0.13.3 dev_dependencies: flutter_test: diff --git a/test/flutter_polyline_points_test.dart b/test/flutter_polyline_points_test.dart index 117bc40..ffae860 100644 --- a/test/flutter_polyline_points_test.dart +++ b/test/flutter_polyline_points_test.dart @@ -8,10 +8,12 @@ void main() { test('get list of coordinates from two geographical positions', () async { final polylinePoints = PolylinePoints(); PolylineResult result = await polylinePoints.getRouteBetweenCoordinates( - Constants.API_KEY, PointLatLng(6.5212402, 3.3679965), - PointLatLng(6.595680, 3.337030), - travelMode: TravelMode.driving); - assert(result.points.isNotEmpty == true); + Constants.API_KEY, + origin: PointLatLng(6.5212402, 3.3679965), + destination: PointLatLng(6.595680, 3.337030), + travelMode: TravelMode.driving, + ); + assert(result.routes.first.points.isNotEmpty == true); }); test('get list of coordinates from an encoded String', () {