From 7b6a276c21833b9c98d2b45a50bfd568b4a2f58d Mon Sep 17 00:00:00 2001 From: Michael Thomas Date: Thu, 31 Aug 2023 09:39:24 -0400 Subject: [PATCH] wip: support saferide shuttle --- lib/src/services/transportation/route.dart | 61 +++++++++++++++++++ .../transportation/saferide_shuttle.dart | 49 ++++++++++++++- lib/src/services/transportation/vehicle.dart | 20 ++++++ lib/src/utils/decode_polyline.dart | 34 +++++++++++ .../transportation/saferide_shuttle_test.dart | 22 +++++++ 5 files changed, 184 insertions(+), 2 deletions(-) create mode 100644 lib/src/services/transportation/route.dart create mode 100644 lib/src/services/transportation/vehicle.dart create mode 100644 lib/src/utils/decode_polyline.dart create mode 100644 test/service_tests/transportation/saferide_shuttle_test.dart diff --git a/lib/src/services/transportation/route.dart b/lib/src/services/transportation/route.dart new file mode 100644 index 0000000..1f9f8a1 --- /dev/null +++ b/lib/src/services/transportation/route.dart @@ -0,0 +1,61 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:furman_now/src/widgets/map/route_marker.dart'; +import 'package:latlong2/latlong.dart'; + +class TransportationRoute { + final Polyline route; + final List stops; + + TransportationRoute({ + required this.route, + required this.stops, + }); + + factory TransportationRoute.fromPoints({ + required List points, + required Color color, + required List stops, + }) { + var route = Polyline( + points: points, + color: color, + strokeWidth: 4, + ); + return TransportationRoute( + route: route, + stops: stops, + ); + } +} + +class Stop { + int id; + String name; + LatLng location; + + Stop({ + required this.id, + required this.name, + required this.location, + }); + + factory Stop.fromLiveSafeJson(Map json) { + return Stop( + id: json["AddressID"], + name: json["Description"], + location: LatLng(json["Latitude"], json["Longitude"]), + ); + } + + Marker toMarker({required Color color}) { + return Marker( + point: location, + height: 100, + width: 100, + builder: (context) => RouteMarker(color: color), + ); + } +} diff --git a/lib/src/services/transportation/saferide_shuttle.dart b/lib/src/services/transportation/saferide_shuttle.dart index 01688c1..11a6b3e 100644 --- a/lib/src/services/transportation/saferide_shuttle.dart +++ b/lib/src/services/transportation/saferide_shuttle.dart @@ -1,3 +1,48 @@ -class TransportationSafeRideShuttleService { +import 'dart:convert'; -} \ No newline at end of file +import 'package:flutter/material.dart'; +import 'package:furman_now/src/utils/decode_polyline.dart'; +import 'package:http/http.dart' as http; + +import 'route.dart'; + +class TransportationSafeRideShuttleService { + static const service = + "https://furmansaferide.ridesystems.net/Services/JSONPRelay.svc"; + + static const apiKey = "8882812681"; + + static Future _serviceRequest( + String endpoint, Map? queryParameters) async { + Uri serviceUri = Uri.https( + 'furmansaferide.ridesystems.net', + "/Services/JSONPRelay.svc/$endpoint", + {...?queryParameters, 'apiKey': apiKey}); + return http.get(serviceUri); + } + + static Future fetchShuttleRoute() async { + final response = await _serviceRequest( + 'GetRoutesForMapWithScheduleWithEncodedLine', {'isDispatch': 'false'}); + + if (response.statusCode == 200) { + // If the server did return a 200 OK response, + // then parse the JSON. + final json = jsonDecode(response.body); + String encodedPolyline = json[0]["EncodedPolyline"]; + final points = decodeEncodedPolyline(encodedPolyline); + var stops = (json[0]["Stops"] as List) + .map((e) => Stop.fromLiveSafeJson(e)) + .toList(); + return TransportationRoute.fromPoints( + points: points, + color: Colors.red.shade300, + stops: stops, + ); + } else { + // If the server did not return a 200 OK response, + // then throw an exception. + throw Exception('Failed to load SafeRide shuttle route.'); + } + } +} diff --git a/lib/src/services/transportation/vehicle.dart b/lib/src/services/transportation/vehicle.dart new file mode 100644 index 0000000..c9a31c9 --- /dev/null +++ b/lib/src/services/transportation/vehicle.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + +abstract class TransportationVehicle { + // name of the vehicle + String get name; + + // is the vehicle currently running? + VehicleStatus get status; + + // name of vehicle's next stop + String get nextStop; + + // vehicle icon + IconData get icon; + + // vehicle route + TransitionRoute get route; +} + +enum VehicleStatus { running, stopped } diff --git a/lib/src/utils/decode_polyline.dart b/lib/src/utils/decode_polyline.dart new file mode 100644 index 0000000..87916f1 --- /dev/null +++ b/lib/src/utils/decode_polyline.dart @@ -0,0 +1,34 @@ +import 'package:latlong2/latlong.dart'; + +/// Decodes the an encoded polyline using the Encoded Polyline Algorithm Format +/// for more info about the algorithm check +/// https://developers.google.com/maps/documentation/utilities/polylinealgorithm +List decodeEncodedPolyline(String encoded) { + List poly = []; + int index = 0, len = encoded.length; + int lat = 0, lng = 0; + + while (index < len) { + int b, shift = 0, result = 0; + do { + b = encoded.codeUnitAt(index++) - 63; + result |= (b & 0x1f) << shift; + shift += 5; + } while (b >= 0x20); + int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1)); + lat += dlat; + + shift = 0; + result = 0; + do { + b = encoded.codeUnitAt(index++) - 63; + result |= (b & 0x1f) << shift; + shift += 5; + } while (b >= 0x20); + int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1)); + lng += dlng; + LatLng p = LatLng((lat / 1E5).toDouble(), (lng / 1E5).toDouble()); + poly.add(p); + } + return poly; +} diff --git a/test/service_tests/transportation/saferide_shuttle_test.dart b/test/service_tests/transportation/saferide_shuttle_test.dart new file mode 100644 index 0000000..5a6ed65 --- /dev/null +++ b/test/service_tests/transportation/saferide_shuttle_test.dart @@ -0,0 +1,22 @@ +import "package:furman_now/src/services/transportation/saferide_shuttle.dart"; +import "package:test/test.dart"; + +void main() { + test("shuttle status is fetched successfully", () async { + var route = await TransportationSafeRideShuttleService.fetchShuttleRoute(); + + // ensure polyline has points + expect(route.route.points.length, isNonZero); + // ensure stops are listed + expect(route.stops.length, isNonZero); + }); + + test("shuttle route is fetched successfully", () async { + var route = await TransportationSafeRideShuttleService.fetchShuttleRoute(); + + // ensure polyline has points + expect(route.route.points.length, isNonZero); + // ensure stops are listed + expect(route.stops.length, isNonZero); + }); +}