From ca0c86a3f65d997ae569f8c087354ed198daf494 Mon Sep 17 00:00:00 2001
From: alper-savas <alpersavas1998@gmail.com>
Date: Tue, 28 Mar 2023 22:24:43 +0200
Subject: [PATCH] Implement conversion from Google maps to Flutter map.

---
 lib/config/config.dart          |   1 +
 lib/main.dart                   | 295 ++++++++++++++++++++++----------
 lib/widgets/directions.dart     |  82 ++++-----
 lib/widgets/directionsRepo.dart |  53 +++---
 4 files changed, 268 insertions(+), 163 deletions(-)

diff --git a/lib/config/config.dart b/lib/config/config.dart
index a62b33a..5c7118b 100644
--- a/lib/config/config.dart
+++ b/lib/config/config.dart
@@ -1,4 +1,5 @@
 class Config {
   final String GOOGLE_API = '';
   final String WEATHER_API = '';
+  final String OSM_API = '';
 }
diff --git a/lib/main.dart b/lib/main.dart
index f08931d..4608290 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,17 +1,19 @@
-// ignore_for_file: prefer_const_constructors, sort_child_properties_last, import_of_legacy_library_into_null_safe, prefer_is_not_empty, unnecessary_null_comparison, deprecated_member_use, depend_on_referenced_packages, avoid_function_literals_in_foreach_calls, prefer_final_fields
+// ignore_for_file: prefer_const_constructors, sort_child_properties_last, import_of_legacy_library_into_null_safe, prefer_is_not_empty, unnecessary_null_comparison, deprecated_member_use, depend_on_referenced_packages, avoid_function_literals_in_foreach_calls, prefer_final_fields, prefer_typing_uninitialized_variables
 import 'package:flutter/material.dart';
-import 'package:google_maps_flutter/google_maps_flutter.dart';
+import 'package:flutter_map/flutter_map.dart';
 import 'package:intl/intl.dart';
 import 'widgets/returnOrigin.dart';
 import 'widgets/returnDestination.dart';
-import 'widgets/directionsRepo.dart';
-import 'widgets/directions.dart';
 import 'package:geocoding/geocoding.dart';
 import 'package:geolocator/geolocator.dart';
 import 'package:http/http.dart' as http;
 import 'dart:convert';
 import 'package:weather_icons/weather_icons.dart';
 import 'config/config.dart';
+import 'package:latlong/latlong.dart' as latlng;
+// import 'widgets/directionsRepo.dart';
+// import 'package:google_maps_flutter/google_maps_flutter.dart';
+// import 'widgets/directions.dart';
 
 void main() => runApp(MyApp());
 
@@ -35,6 +37,12 @@ class MyApp extends StatelessWidget {
   }
 }
 
+// Helper class
+class LineString {
+  LineString(this.lineString);
+  List<dynamic> lineString;
+}
+
 class MyHomePage extends StatefulWidget {
   const MyHomePage({super.key});
 
@@ -45,21 +53,24 @@ class MyHomePage extends StatefulWidget {
 class _MyHomePageState extends State<MyHomePage> {
   Config config = Config();
   // Initial map coords and map controller to control camera movements.
-  static const _initialCameraPosition =
-      CameraPosition(target: LatLng(52.5163, 13.3777), zoom: 12);
-  late GoogleMapController _mapController;
+  late final MapController _mapController = MapController();
+  // static const _initialCameraPosition =
+  //     CameraPosition(target: LatLng(52.5163, 13.3777), zoom: 12);
+  // late GoogleMapController _mapController;
 
   // Input field texts for origin/dest points.
   String originText = 'Starting Point...';
   String destinationText = 'Destination...';
 
   // Set origin/dest coords, respective markers, lat/lng coords between poits and polyline corresponding to coords.
-  late LatLng _originCoordinates;
-  late LatLng _destCoordinates;
+  late latlng.LatLng _originCoordinates;
+  late latlng.LatLng _destCoordinates;
   int _markerCounter = 0;
   Set<Marker> _markers = {};
-  late Directions _info;
-  Set<Polyline> _polyline = {};
+  var _data;
+  List<latlng.LatLng> _polyline = <latlng.LatLng>[];
+  // late Directions _info;
+  // Set<Polyline> _polyline = {};
   String _totalDuration = '';
   String _totalDistance = '';
 
@@ -102,17 +113,13 @@ class _MyHomePageState extends State<MyHomePage> {
       setState(() {
         originText = result;
         _originCoordinates =
-            LatLng(originLoc[0].latitude, originLoc[0].longitude);
+            latlng.LatLng(originLoc[0].latitude, originLoc[0].longitude);
       });
     }
     // Toggle keyboard
     FocusManager.instance.primaryFocus?.unfocus();
     // Animate camera to the starting point.
-    _mapController.animateCamera(
-      CameraUpdate.newCameraPosition(
-        CameraPosition(target: _originCoordinates, zoom: 14),
-      ),
-    );
+    _mapController.move(_originCoordinates, 13);
     setState(() {
       _markers = {};
     });
@@ -133,18 +140,15 @@ class _MyHomePageState extends State<MyHomePage> {
       setState(
         () {
           destinationText = result;
-          _destCoordinates = LatLng(destLoc[0].latitude, destLoc[0].longitude);
+          _destCoordinates =
+              latlng.LatLng(destLoc[0].latitude, destLoc[0].longitude);
         },
       );
     }
     // Toggle keyboard
     FocusManager.instance.primaryFocus?.unfocus();
-    // Animate camera to the destination point.
-    _mapController.animateCamera(
-      CameraUpdate.newCameraPosition(
-        CameraPosition(target: _destCoordinates, zoom: 14),
-      ),
-    );
+    // Animate camera to the destionation point.
+    _mapController.move(_destCoordinates, 13);
     if (_markers.length > 1) {
       _markers.remove(_markers.last);
     }
@@ -160,17 +164,20 @@ class _MyHomePageState extends State<MyHomePage> {
   void _getShortestPath() async {
     _getWeather();
     _resetPolyline();
-    _drawPolyline();
+    _getDirections();
   }
 
   // Add markers for origin/dest coords.
   void _addOriginMarker() {
     _markers.add(
       Marker(
-        markerId: MarkerId('origin'),
-        infoWindow: InfoWindow(title: 'Origin'),
-        icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueGreen),
-        position: _originCoordinates,
+        width: 140,
+        height: 140,
+        point: _originCoordinates,
+        builder: (ctx) => Icon(
+          Icons.location_on,
+          color: Color.fromRGBO(0, 128, 0, 1),
+        ),
       ),
     );
     setState(() {
@@ -182,10 +189,13 @@ class _MyHomePageState extends State<MyHomePage> {
   void _addDestinationMarker() {
     _markers.add(
       Marker(
-        markerId: MarkerId('destination'),
-        infoWindow: InfoWindow(title: 'Destination'),
-        icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueRed),
-        position: _destCoordinates,
+        width: 100,
+        height: 100,
+        point: _destCoordinates,
+        builder: (ctx) => Icon(
+          Icons.location_on,
+          color: Colors.red,
+        ),
       ),
     );
     setState(() {
@@ -194,47 +204,122 @@ class _MyHomePageState extends State<MyHomePage> {
     });
   }
 
-  // Get coords between origin/dest points corresponding to shortest path and update _info.
-  // This part is going to be updated to take directions from backend instead of Direction API.
-  Future<Directions> _getDirections() async {
-    final directions = await DirectionsRepo().getDirections(
-        origin: _originCoordinates, destination: _destCoordinates);
+  // Get coords between origin/dest points corresponding to shortest path.
+  void _getDirections() async {
+    _resetPolyline();
+    try {
+      var originlat = _originCoordinates.latitude;
+      var originlon = _originCoordinates.longitude;
+      var destlat = _destCoordinates.latitude;
+      var destlon = _destCoordinates.longitude;
 
-    setState(() {
-      _info = directions;
-    });
+      // Get direction data
+      final response = await http.get(
+        Uri.parse(
+          'https://api.openrouteservice.org/v2/directions/driving-car?api_key=${config.OSM_API}&start=$originlon,$originlat&end=$destlon,$destlat',
+        ),
+      );
+      _data = jsonDecode(response.body);
+      var coordinates = _data['features'][0]['geometry']['coordinates'];
+      double duration =
+          _data['features'][0]['properties']['segments'][0]['duration'];
+      double distance =
+          _data['features'][0]['properties']['segments'][0]['distance'];
 
-    return _info;
-  }
+      // Format duration and distance
+      _totalDuration = getTotalDuration(duration);
+      _totalDistance = getTotalDistance(distance);
+
+      // Draw polyline
+      drawPolyline(coordinates);
+
+      // Find bound and move camera
+      final bounds = LatLngBounds.fromPoints([
+        _originCoordinates,
+        _destCoordinates,
+      ]);
+      _mapController.fitBounds(
+        bounds,
+        options: const FitBoundsOptions(
+          padding: EdgeInsets.only(left: 30, right: 30),
+        ),
+      );
 
-  // Draw Polyline between given list of coordinates and update _polyline
-  void _drawPolyline() {
-    _getDirections().then((value) {
-      _polyline.add(Polyline(
-        polylineId: PolylineId("polylineId"),
-        color: Theme.of(context).primaryColor,
-        width: 4,
-        points: value.polylinePoints
-            .map((e) => LatLng(e.latitude, e.longitude))
-            .toList(),
-      ));
-      _totalDuration = value.totalDuration;
-      _totalDistance = value.totalDistance;
       setState(() {
-        _polyline;
         _totalDuration;
         _totalDistance;
+        _polyline;
       });
-      // Animate camera to the shortest path.
-      _mapController.animateCamera(
-        CameraUpdate.newLatLngBounds(_info.bounds, 110.0),
-      );
-    });
+    } catch (e) {
+      print(e);
+    }
   }
 
+  void drawPolyline(coordinates) {
+    LineString ls = LineString(coordinates);
+
+    // Draw polyline based on given coordinates
+    for (int i = 0; i < ls.lineString.length; i++) {
+      _polyline.add(latlng.LatLng(ls.lineString[i][1], ls.lineString[i][0]));
+    }
+  }
+
+  String getTotalDuration(double duration) {
+    double durationInMins = duration / 60;
+    int hours = (durationInMins / 60).floor();
+    int min = (durationInMins % 60).floor();
+    if (hours != 0) {
+      return '$hours hours $min mins';
+    }
+    return '$min mins';
+  }
+
+  String getTotalDistance(double distance) {
+    int distanceInKm = (distance / 1000).ceil();
+    return '$distanceInKm km';
+  }
+
+  // Get coords between origin/dest points corresponding to shortest path and update _info.
+  // This part is going to be updated to take directions from backend instead of Direction API.
+  // Future<Directions> _getDirections() async {
+  //   final directions = await DirectionsRepo().getDirections(
+  //       origin: _originCoordinates, destination: _destCoordinates);
+
+  //   setState(() {
+  //     _info = directions;
+  //   });
+
+  //   return _info;
+  // }
+
+  // Draw Polyline between given list of coordinates and update _polyline
+  // void _drawPolyline() {
+  //   _getDirections().then((value) {
+  //     _polyline.add(Polyline(
+  //       polylineId: PolylineId("polylineId"),
+  //       color: Theme.of(context).primaryColor,
+  //       width: 4,
+  //       points: value.polylinePoints
+  //           .map((e) => LatLng(e.latitude, e.longitude))
+  //           .toList(),
+  //     ));
+  //     _totalDuration = value.totalDuration;
+  //     _totalDistance = value.totalDistance;
+  //     setState(() {
+  //       _polyline;
+  //       _totalDuration;
+  //       _totalDistance;
+  //     });
+  //     // Animate camera to the shortest path.
+  //     // _mapController.animateCamera(
+  //     //   CameraUpdate.newLatLngBounds(_info.bounds, 110.0),
+  //     // );
+  //   });
+  // }
+
   // Helper function to reset polyline before calculating new polyline for another route.
   void _resetPolyline() {
-    _polyline = {};
+    _polyline = [];
     setState(() {
       _polyline;
     });
@@ -264,14 +349,11 @@ class _MyHomePageState extends State<MyHomePage> {
               originText =
                   json.decode(response.body)['results'][0]['formatted_address'];
               _originCoordinates =
-                  LatLng(position.latitude, position.longitude);
+                  latlng.LatLng(position.latitude, position.longitude);
             });
-            _mapController.animateCamera(
-              CameraUpdate.newCameraPosition(
-                CameraPosition(target: _destCoordinates, zoom: 14),
-              ),
-            );
-            _addDestinationMarker();
+            _mapController.move(_originCoordinates, 13);
+            _resetOriginMarker();
+            _addOriginMarker();
             _resetPolyline();
           },
         ).catchError(
@@ -284,15 +366,18 @@ class _MyHomePageState extends State<MyHomePage> {
   }
 
   // Add origin marker on touch.
-  void _appearOriginMarkerOnTouch(LatLng pos) async {
+  void _appearOriginMarkerOnTouch(latlng.LatLng pos) async {
     _resetPolyline();
     _resetOriginMarker();
     _markers.add(
       Marker(
-        markerId: MarkerId('origin'),
-        infoWindow: InfoWindow(title: 'Origin'),
-        icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueGreen),
-        position: pos,
+        width: 100,
+        height: 100,
+        point: pos,
+        builder: (ctx) => Icon(
+          Icons.location_on,
+          color: Color.fromRGBO(0, 128, 0, 1),
+        ),
       ),
     );
     final url = Uri.parse(
@@ -302,21 +387,26 @@ class _MyHomePageState extends State<MyHomePage> {
     setState(() {
       _markers;
       originText;
-      _originCoordinates = LatLng(pos.latitude, pos.longitude);
+      _originCoordinates = latlng.LatLng(pos.latitude, pos.longitude);
       _markerCounter = 1;
     });
   }
 
   // Add destination marker on touch.
-  void _appearDestMarkerOnTouch(LatLng pos) async {
+  void _appearDestMarkerOnTouch(latlng.LatLng pos) async {
     _resetPolyline();
-
+    if (_markers.length > 1) {
+      _markers.remove(_markers.elementAt(1));
+    }
     _markers.add(
       Marker(
-        markerId: MarkerId('dest'),
-        infoWindow: InfoWindow(title: 'Dest'),
-        icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueRed),
-        position: pos,
+        width: 100,
+        height: 100,
+        point: pos,
+        builder: (ctx) => Icon(
+          Icons.location_on,
+          color: Colors.red,
+        ),
       ),
     );
     final url = Uri.parse(
@@ -327,7 +417,7 @@ class _MyHomePageState extends State<MyHomePage> {
     setState(() {
       _markers;
       destinationText;
-      _destCoordinates = LatLng(pos.latitude, pos.longitude);
+      _destCoordinates = latlng.LatLng(pos.latitude, pos.longitude);
       _markerCounter = 0;
     });
   }
@@ -607,16 +697,37 @@ class _MyHomePageState extends State<MyHomePage> {
     return Scaffold(
       body: Stack(
         children: [
-          GoogleMap(
-              initialCameraPosition: _initialCameraPosition,
-              myLocationButtonEnabled: false,
-              zoomControlsEnabled: false,
-              onMapCreated: (controller) => _mapController = controller,
-              markers: _markers,
-              polylines: _polyline,
-              onLongPress: _markerCounter == 0
+          FlutterMap(
+            mapController: _mapController,
+            options: MapOptions(
+              center: latlng.LatLng(52.5163, 13.3777),
+              zoom: 12,
+              onTap: _markerCounter == 0
                   ? _appearOriginMarkerOnTouch
-                  : _appearDestMarkerOnTouch),
+                  : _appearDestMarkerOnTouch,
+            ),
+            layers: [
+              TileLayerOptions(
+                urlTemplate:
+                    'https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png',
+                subdomains: ['a', 'b', 'c'],
+              ),
+              MarkerLayerOptions(
+                markers: [
+                  ..._markers,
+                ],
+              ),
+              PolylineLayerOptions(
+                polylines: [
+                  Polyline(
+                    points: _polyline,
+                    color: Color.fromRGBO(9, 89, 95, 1), // Set the line's color
+                    strokeWidth: 5.0, // Set the line's width
+                  ),
+                ],
+              )
+            ],
+          ),
           SafeArea(
             child: Column(
               children: [
diff --git a/lib/widgets/directions.dart b/lib/widgets/directions.dart
index 6ad96b6..2e66c6f 100644
--- a/lib/widgets/directions.dart
+++ b/lib/widgets/directions.dart
@@ -1,48 +1,48 @@
-// ignore_for_file: import_of_legacy_library_into_null_safe
+// // ignore_for_file: import_of_legacy_library_into_null_safe
 
-import 'package:flutter_polyline_points/flutter_polyline_points.dart';
-import 'package:google_maps_flutter/google_maps_flutter.dart';
+// import 'package:flutter_polyline_points/flutter_polyline_points.dart';
+// import 'package:google_maps_flutter/google_maps_flutter.dart';
 
-class Directions {
-  final LatLngBounds bounds;
-  final List<PointLatLng> polylinePoints;
-  final String totalDistance;
-  final String totalDuration;
+// class Directions {
+//   final LatLngBounds bounds;
+//   final List<PointLatLng> polylinePoints;
+//   final String totalDistance;
+//   final String totalDuration;
 
-  const Directions({
-    required this.bounds,
-    required this.polylinePoints,
-    required this.totalDistance,
-    required this.totalDuration,
-  });
+//   const Directions({
+//     required this.bounds,
+//     required this.polylinePoints,
+//     required this.totalDistance,
+//     required this.totalDuration,
+//   });
 
-  factory Directions.fromMap(Map<String, dynamic> map) {
-    // Get route information
-    final data = Map<String, dynamic>.from(map['routes'][0]);
+//   factory Directions.fromMap(Map<String, dynamic> map) {
+//     // Get route information
+//     final data = Map<String, dynamic>.from(map['routes'][0]);
 
-    // Bounds
-    final northeast = data['bounds']['northeast'];
-    final southwest = data['bounds']['southwest'];
-    final bounds = LatLngBounds(
-      northeast: LatLng(northeast['lat'], northeast['lng']),
-      southwest: LatLng(southwest['lat'], southwest['lng']),
-    );
+//     // Bounds
+//     final northeast = data['bounds']['northeast'];
+//     final southwest = data['bounds']['southwest'];
+//     final bounds = LatLngBounds(
+//       northeast: LatLng(northeast['lat'], northeast['lng']),
+//       southwest: LatLng(southwest['lat'], southwest['lng']),
+//     );
 
-    // Distance and Duration
-    String distance = '';
-    String duration = '';
-    if ((data['legs'] as List).isNotEmpty) {
-      final leg = data['legs'][0];
-      distance = leg['distance']['text'];
-      duration = leg['duration']['text'];
-    }
+//     // Distance and Duration
+//     String distance = '';
+//     String duration = '';
+//     if ((data['legs'] as List).isNotEmpty) {
+//       final leg = data['legs'][0];
+//       distance = leg['distance']['text'];
+//       duration = leg['duration']['text'];
+//     }
 
-    return Directions(
-      bounds: bounds,
-      polylinePoints:
-          PolylinePoints().decodePolyline(data['overview_polyline']['points']),
-      totalDistance: distance,
-      totalDuration: duration,
-    );
-  }
-}
+//     return Directions(
+//       bounds: bounds,
+//       polylinePoints:
+//           PolylinePoints().decodePolyline(data['overview_polyline']['points']),
+//       totalDistance: distance,
+//       totalDuration: duration,
+//     );
+//   }
+// }
diff --git a/lib/widgets/directionsRepo.dart b/lib/widgets/directionsRepo.dart
index b9ec6bf..feb6c53 100644
--- a/lib/widgets/directionsRepo.dart
+++ b/lib/widgets/directionsRepo.dart
@@ -1,35 +1,28 @@
 // ignore_for_file: file_names
 
-import 'package:dio/dio.dart';
-import 'package:google_maps_flutter/google_maps_flutter.dart';
-import './directions.dart';
-import '../config/config.dart';
+// // import 'package:google_maps_flutter/google_maps_flutter.dart';
+// import '../config/config.dart';
+// import 'package:latlong/latlong.dart' as latlng;
+// import 'package:http/http.dart' as http;
 
-class DirectionsRepo {
-  Config config = Config();
-  static const String _baseUrl =
-      'https://maps.googleapis.com/maps/api/directions/json?';
-  final Dio _dio;
-  DirectionsRepo({Dio? dio}) : _dio = dio ?? Dio();
+// class DirectionsRepo {
+//   Config config = Config();
+//   latlng.LatLng origin;
+//   latlng.LatLng destination;
+//   DirectionsRepo(this.origin, this.destination);
 
-  Future<Directions> getDirections({
-    required LatLng origin,
-    required LatLng destination,
-  }) async {
-    final response = await _dio.get(
-      _baseUrl,
-      queryParameters: {
-        'origin': '${origin.latitude},${origin.longitude}',
-        'destination': '${destination.latitude},${destination.longitude}',
-        'key': config.GOOGLE_API,
-      },
-    );
+//   Future<http.Response> getDirections() async {
+//     var originlat = origin.latitude;
+//     var originlon = origin.longitude;
+//     var destlat = destination.latitude;
+//     var destlon = destination.longitude;
+//     final response = await http.get(
+//       Uri.parse(
+//         'https://api.openrouteservice.org/v2/directions/driving-car?api_key=${config.OSM_API}&start=$originlon,$originlat&end=$destlon,$destlat',
+//       ),
+//     );
 
-    // Check if response is successful
-    if (response.statusCode == 200) {
-      return Directions.fromMap(response.data);
-    } else {
-      throw Error();
-    }
-  }
-}
+//     // Check if response is successful
+//     return response;
+//   }
+// }
-- 
GitLab