Compare commits
	
		
			1 Commits
		
	
	
		
			v0.0.17
			...
			c612bf13f5
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c612bf13f5 | 
| @@ -29,9 +29,11 @@ def new_trip(preferences: Preferences, start: tuple[float, float], end: tuple[fl | ||||
|     :return: the uuid of the first landmark in the optimized route | ||||
|     ''' | ||||
|     if preferences is None: | ||||
|         raise ValueError("Please provide preferences in the form of a 'Preference' BaseModel class.") | ||||
|         raise HTTPException(status_code=406, detail="Preferences not provided") | ||||
|     if preferences.shopping.score == 0 and preferences.culture.score == 0 and preferences.nature.score == 0: | ||||
|         raise HTTPException(status_code=406, detail="All preferences are 0.") | ||||
|     if start is None: | ||||
|         raise ValueError("Please provide the starting coordinates as a tuple of floats.") | ||||
|         raise HTTPException(status_code=406, detail="Start coordinates not provided") | ||||
|     if end is None: | ||||
|         end = start | ||||
|         logger.info("No end coordinates provided. Using start=end.") | ||||
| @@ -50,7 +52,12 @@ def new_trip(preferences: Preferences, start: tuple[float, float], end: tuple[fl | ||||
|     landmarks_short.append(end_landmark) | ||||
|      | ||||
|     # First stage optimization | ||||
|     base_tour = optimizer.solve_optimization(preferences.max_time_minute, landmarks_short) | ||||
|     try: | ||||
|         base_tour = optimizer.solve_optimization(preferences.max_time_minute, landmarks_short) | ||||
|     except ArithmeticError: | ||||
|         raise HTTPException(status_code=500, detail="No solution found") | ||||
|     except TimeoutError: | ||||
|         raise HTTPException(status_code=500, detail="Optimzation took too long") | ||||
|      | ||||
|     # Second stage optimization | ||||
|     refined_tour = refiner.refine_optimization(landmarks, base_tour, preferences.max_time_minute, preferences.detour_tolerance_minute) | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| city_bbox_side: 5000 #m | ||||
| radius_close_to: 50 | ||||
| church_coeff: 0.8 | ||||
| park_coeff: 1.2 | ||||
| park_coeff: 1.0 | ||||
| tag_coeff: 10 | ||||
| N_important: 40 | ||||
|   | ||||
| @@ -21,7 +21,6 @@ class LandmarkManager: | ||||
|  | ||||
|     logger = logging.getLogger(__name__) | ||||
|  | ||||
|     city_bbox_side: int     # bbox side in meters | ||||
|     radius_close_to: int    # radius in meters | ||||
|     church_coeff: float     # coeff to adjsut score of churches | ||||
|     park_coeff: float       # coeff to adjust score of parks | ||||
| @@ -36,13 +35,18 @@ class LandmarkManager: | ||||
|  | ||||
|         with constants.LANDMARK_PARAMETERS_PATH.open('r') as f: | ||||
|             parameters = yaml.safe_load(f) | ||||
|             self.city_bbox_side = parameters['city_bbox_side'] | ||||
|             self.max_bbox_side = parameters['city_bbox_side'] | ||||
|             self.radius_close_to = parameters['radius_close_to'] | ||||
|             self.church_coeff = parameters['church_coeff'] | ||||
|             self.park_coeff = parameters['park_coeff'] | ||||
|             self.tag_coeff = parameters['tag_coeff'] | ||||
|             self.N_important = parameters['N_important'] | ||||
|              | ||||
|         with constants.OPTIMIZER_PARAMETERS_PATH.open('r') as f: | ||||
|             parameters = yaml.safe_load(f) | ||||
|             self.walking_speed = parameters['average_walking_speed'] | ||||
|             self.detour_factor = parameters['detour_factor'] | ||||
|  | ||||
|         self.overpass = Overpass() | ||||
|         CachingStrategy.use(JSON, cacheDir=constants.OSM_CACHE_DIR) | ||||
|  | ||||
| @@ -65,23 +69,26 @@ class LandmarkManager: | ||||
|                 - A list of the most important landmarks based on the user's preferences. | ||||
|         """ | ||||
|  | ||||
|         max_walk_dist = (preferences.max_time_minute/2)/60*self.walking_speed*1000/self.detour_factor | ||||
|         reachable_bbox_side = min(max_walk_dist, self.max_bbox_side) | ||||
|  | ||||
|         L = [] | ||||
|         bbox = self.create_bbox(center_coordinates) | ||||
|         bbox = self.create_bbox(center_coordinates, reachable_bbox_side) | ||||
|         # list for sightseeing | ||||
|         if preferences.sightseeing.score != 0: | ||||
|             score_function = lambda loc, n_tags: int((self.count_elements_close_to(loc) + ((n_tags**1.2)*self.tag_coeff) )*self.church_coeff)   | ||||
|             score_function = lambda loc, n_tags: int((((n_tags**1.2)*self.tag_coeff) )*self.church_coeff)   # self.count_elements_close_to(loc) + | ||||
|             L1 = self.fetch_landmarks(bbox, self.amenity_selectors['sightseeing'], preferences.sightseeing.type, score_function) | ||||
|             L += L1 | ||||
|  | ||||
|         # list for nature | ||||
|         if preferences.nature.score != 0: | ||||
|             score_function = lambda loc, n_tags: int((self.count_elements_close_to(loc) + ((n_tags**1.2)*self.tag_coeff) )*self.park_coeff)   | ||||
|             score_function = lambda loc, n_tags: int((((n_tags**1.2)*self.tag_coeff) )*self.park_coeff)   # self.count_elements_close_to(loc) + | ||||
|             L2 = self.fetch_landmarks(bbox, self.amenity_selectors['nature'], preferences.nature.type, score_function) | ||||
|             L += L2 | ||||
|  | ||||
|         # list for shopping | ||||
|         if preferences.shopping.score != 0: | ||||
|             score_function = lambda loc, n_tags: int(self.count_elements_close_to(loc) + ((n_tags**1.2)*self.tag_coeff)) | ||||
|             score_function = lambda loc, n_tags: int(((n_tags**1.2)*self.tag_coeff)) # self.count_elements_close_to(loc) + | ||||
|             L3 = self.fetch_landmarks(bbox, self.amenity_selectors['shopping'], preferences.shopping.type, score_function) | ||||
|             L += L3 | ||||
|  | ||||
| @@ -183,12 +190,13 @@ class LandmarkManager: | ||||
|             return 0 | ||||
|  | ||||
|  | ||||
|     def create_bbox(self, coordinates: tuple[float, float]) -> tuple[float, float, float, float]: | ||||
|     def create_bbox(self, coordinates: tuple[float, float], reachable_bbox_side: int) -> tuple[float, float, float, float]: | ||||
|         """ | ||||
|         Create a bounding box around the given coordinates. | ||||
|  | ||||
|         Args: | ||||
|             coordinates (tuple[float, float]): The latitude and longitude of the center of the bounding box. | ||||
|             reachable_bbox_side (int): The side length of the bounding box in meters. | ||||
|  | ||||
|         Returns: | ||||
|             tuple[float, float, float, float]: The minimum latitude, minimum longitude, maximum latitude, and maximum longitude | ||||
| @@ -199,7 +207,7 @@ class LandmarkManager: | ||||
|         lon = coordinates[1] | ||||
|  | ||||
|         # Half the side length in km (since it's a square bbox) | ||||
|         half_side_length_km = self.city_bbox_side / 2 / 1000 | ||||
|         half_side_length_km = reachable_bbox_side / 2 / 1000 | ||||
|  | ||||
|         # Convert distance to degrees | ||||
|         lat_diff = half_side_length_km / 111  # 1 degree latitude is approximately 111 km | ||||
| @@ -288,20 +296,25 @@ class LandmarkManager: | ||||
|                         break | ||||
|  | ||||
|                     if "wikipedia" in tag: | ||||
|                         n_tags += 3             # wikipedia entries count more | ||||
|                         n_tags += 1             # wikipedia entries count more | ||||
|  | ||||
|                     if tag == "wikidata": | ||||
|                         Q = elem.tag('wikidata') | ||||
|                         site = Site("wikidata", "wikidata") | ||||
|                         item = ItemPage(site, Q) | ||||
|                         item.get() | ||||
|                         n_languages = len(item.labels) | ||||
|                         n_tags += n_languages/10 | ||||
|                     # if tag == "wikidata": | ||||
|                     #     Q = elem.tag('wikidata') | ||||
|                     #     site = Site("wikidata", "wikidata") | ||||
|                     #     item = ItemPage(site, Q) | ||||
|                     #     item.get() | ||||
|                     #     n_languages = len(item.labels) | ||||
|                     #     n_tags += n_languages/10 | ||||
|                     if "viewpoint" in tag: | ||||
|                         n_tags += 10 | ||||
|  | ||||
|                     if elem_type != "nature": | ||||
|                         if "leisure" in tag and elem.tag('leisure') == "park": | ||||
|                             elem_type = "nature" | ||||
|                      | ||||
|                     if elem_type == "nature": | ||||
|                         n_tags += 1  | ||||
|  | ||||
|                     if landmarktype != "shopping": | ||||
|                         if "shop" in tag: | ||||
|                             skip = True | ||||
| @@ -310,7 +323,6 @@ class LandmarkManager: | ||||
|                         if tag == "building" and elem.tag('building') in ['retail', 'supermarket', 'parking']: | ||||
|                             skip = True | ||||
|                             break | ||||
|  | ||||
|                 if skip: | ||||
|                     continue | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| const String APP_NAME = 'AnyWay'; | ||||
|  | ||||
| const String API_URL_BASE = 'https://anyway.kluster.moll.re'; | ||||
| String API_URL_BASE = 'https://anyway.kluster.moll.re'; | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| import 'dart:developer'; | ||||
|  | ||||
| import 'package:anyway/structs/trip.dart'; | ||||
| import 'package:auto_size_text/auto_size_text.dart'; | ||||
|  | ||||
| @@ -15,12 +17,15 @@ class Greeter extends StatefulWidget { | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| class _GreeterState extends State<Greeter> { | ||||
|    | ||||
|   Widget greeterBuilder (BuildContext context, Widget? child) { | ||||
|     ThemeData theme = Theme.of(context); | ||||
|     TextStyle greeterStyle = TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 24); | ||||
|  | ||||
|     Widget topGreeter; | ||||
|     if (widget.trip.landmarks.length > 1) { | ||||
|  | ||||
|     if (widget.trip.uuid != 'pending') { | ||||
|       topGreeter = FutureBuilder( | ||||
|         future: widget.trip.cityName, | ||||
|         builder: (BuildContext context, AsyncSnapshot<String> snapshot) { | ||||
| @@ -28,17 +33,20 @@ class _GreeterState extends State<Greeter> { | ||||
|             return AutoSizeText( | ||||
|               maxLines: 1, | ||||
|               'Welcome to ${snapshot.data}!', | ||||
|               style: TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 24), | ||||
|               style: greeterStyle | ||||
|             ); | ||||
|           } else if (snapshot.hasError) { | ||||
|             return const AutoSizeText( | ||||
|             log('Error while fetching city name'); | ||||
|             return AutoSizeText( | ||||
|               maxLines: 1, | ||||
|               'Welcome to your trip!' | ||||
|               'Welcome to your trip!', | ||||
|               style: greeterStyle | ||||
|             ); | ||||
|           } else { | ||||
|             return const AutoSizeText( | ||||
|             return AutoSizeText( | ||||
|               maxLines: 1, | ||||
|               'Welcome to ...' | ||||
|               'Welcome to ...', | ||||
|               style: greeterStyle | ||||
|             ); | ||||
|           } | ||||
|         } | ||||
| @@ -54,14 +62,24 @@ class _GreeterState extends State<Greeter> { | ||||
|             future: widget.trip.cityName, | ||||
|             builder: (BuildContext context, AsyncSnapshot<String> snapshot) { | ||||
|               if (snapshot.hasData) { | ||||
|                 return Text( | ||||
|                 return AutoSizeText( | ||||
|                   maxLines: 1, | ||||
|                   'Generating your trip to ${snapshot.data}...', | ||||
|                   style: TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 24), | ||||
|                   style: greeterStyle | ||||
|                 ); | ||||
|               } else if (snapshot.hasError) { | ||||
|                 return const Text('Error while fetching city name'); | ||||
|                 // the exact error is shown in the central part of the trip overview. No need to show it here | ||||
|                 return AutoSizeText( | ||||
|                   maxLines: 1, | ||||
|                   'Error while loading trip.', | ||||
|                   style: greeterStyle | ||||
|                   ); | ||||
|               } | ||||
|               return const Text('Generating your trip...'); | ||||
|               return AutoSizeText( | ||||
|                   maxLines: 1, | ||||
|                   'Generating your trip...', | ||||
|                   style: greeterStyle | ||||
|                   ); | ||||
|             } | ||||
|           ), | ||||
|           Padding( | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| import 'dart:collection'; | ||||
| import 'dart:developer'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:shared_preferences/shared_preferences.dart'; | ||||
| @@ -19,32 +18,19 @@ class LandmarksOverview extends StatefulWidget { | ||||
| } | ||||
|  | ||||
| class _LandmarksOverviewState extends State<LandmarksOverview> { | ||||
|   // final Future<List<Landmark>> _landmarks = fetchLandmarks(); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return ListenableBuilder(//<LinkedList<Landmark>> | ||||
|     return ListenableBuilder( | ||||
|       listenable: widget.trip!, | ||||
|       builder: (BuildContext context, Widget? child) { | ||||
|         Trip trip = widget.trip!; | ||||
|         log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks"); | ||||
|          | ||||
|         List<Widget> children; | ||||
|         if (trip.uuid == 'pending') { | ||||
|           // the trip is still being fetched from the api | ||||
|           children = [Center(child: CircularProgressIndicator())]; | ||||
|         } else if (trip.uuid == 'error') { | ||||
|             children = [ | ||||
|               const Icon( | ||||
|                 Icons.error_outline, | ||||
|                 color: Colors.red, | ||||
|                 size: 60, | ||||
|               ), | ||||
|               Padding( | ||||
|                 padding: const EdgeInsets.only(top: 16), | ||||
|                 child: Text('Error: ${trip.cityName}'), | ||||
|               ), | ||||
|             ]; | ||||
|         } else { | ||||
|          | ||||
|         if (trip.uuid != 'pending' && trip.uuid != 'error') { | ||||
|           log("Trip ${trip.uuid} ${trip.landmarks.length} landmarks"); | ||||
|           if (trip.landmarks.length <= 1) { | ||||
|             children = [ | ||||
|               const Text("No landmarks in this trip"), | ||||
| @@ -55,7 +41,26 @@ class _LandmarksOverviewState extends State<LandmarksOverview> { | ||||
|               saveButton(), | ||||
|             ]; | ||||
|           } | ||||
|         } else if(trip.uuid == 'pending') { | ||||
|           // the trip is still being fetched from the api | ||||
|           children = [Center(child: CircularProgressIndicator())]; | ||||
|         } else { | ||||
|             // trip.uuid == 'error' | ||||
|             // show the error raised by the api | ||||
|             // String error =  | ||||
|             children = [ | ||||
|               const Icon( | ||||
|                 Icons.error_outline, | ||||
|                 color: Colors.red, | ||||
|                 size: 60, | ||||
|               ), | ||||
|               Padding( | ||||
|                 padding: const EdgeInsets.only(top: 16), | ||||
|                 child: Text('Error: ${trip.errorDescription}'), | ||||
|               ), | ||||
|             ]; | ||||
|         } | ||||
|  | ||||
|         return Column( | ||||
|           children: children, | ||||
|         ); | ||||
| @@ -119,11 +124,6 @@ class _LandmarksOverviewState extends State<LandmarksOverview> { | ||||
|  | ||||
|  | ||||
| Widget stepBetweenLandmarks(Landmark current, Landmark next) { | ||||
|   // This is a simple widget that draws a line between landmark-cards | ||||
|   // It's a vertical dotted line | ||||
|   // Next to the line is the icon for the mode of transport (walking for now) and the estimated time | ||||
|   // There is also a button to open the navigation instructions as a new intent | ||||
|   // next landmark is not actually required, but it ensures that the widget is deleted when the next landmark is removed (which makes sense, because then there will be another step) | ||||
|   int timeRounded = 5 * (current.tripTime?.inMinutes ?? 0) ~/ 5; | ||||
|   // ~/ is integer division (rounding) | ||||
|   return Container( | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import 'package:anyway/constants.dart'; | ||||
| import 'package:anyway/structs/preferences.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| @@ -24,6 +25,32 @@ class _ProfilePageState extends State<ProfilePage> { | ||||
|             onChanged: (bool? newValue) { | ||||
|               setState(() { | ||||
|                 debugMode = newValue!; | ||||
|                 showDialog( | ||||
|                   context: context, | ||||
|                   builder: (BuildContext context) { | ||||
|                     return AlertDialog( | ||||
|                       title: Text('Debug mode - custom API'), | ||||
|                       content: TextField( | ||||
|                         decoration: InputDecoration( | ||||
|                           hintText: 'http://localhost:8000' | ||||
|                         ), | ||||
|                       onChanged: (value) { | ||||
|                         setState(() { | ||||
|                           API_URL_BASE = value; | ||||
|                         }); | ||||
|                       }, | ||||
|                     ), | ||||
|                     actions: [ | ||||
|                       TextButton( | ||||
|                         child: Text('OK'), | ||||
|                         onPressed: () { | ||||
|                           Navigator.of(context).pop(); | ||||
|                         }, | ||||
|                       ), | ||||
|                     ], | ||||
|                     ); | ||||
|                   } | ||||
|                 ); | ||||
|               }); | ||||
|             } | ||||
|           ) | ||||
|   | ||||
| @@ -14,6 +14,7 @@ class Trip with ChangeNotifier { | ||||
|   int totalTime; | ||||
|   LinkedList<Landmark> landmarks; | ||||
|   // could be empty as well | ||||
|   String? errorDescription; | ||||
|  | ||||
|   Future<String> get cityName async { | ||||
|     List<double>? location = landmarks.firstOrNull?.location;  | ||||
| @@ -65,6 +66,11 @@ class Trip with ChangeNotifier { | ||||
|     notifyListeners(); | ||||
|   } | ||||
|    | ||||
|   void updateError(String error) { | ||||
|     errorDescription = error; | ||||
|     notifyListeners(); | ||||
|   } | ||||
|  | ||||
|   factory Trip.fromPrefs(SharedPreferences prefs, String uuid) { | ||||
|     String? content = prefs.getString('trip_$uuid'); | ||||
|     Map<String, dynamic> json = jsonDecode(content!); | ||||
|   | ||||
| @@ -11,9 +11,11 @@ import "package:anyway/structs/preferences.dart"; | ||||
| Dio dio = Dio( | ||||
|     BaseOptions( | ||||
|       baseUrl: API_URL_BASE, | ||||
|       // baseUrl: 'http://localhost:8000', | ||||
|       connectTimeout: const Duration(seconds: 5), | ||||
|       receiveTimeout: const Duration(seconds: 120), | ||||
|       // also accept 500 errors, since we cannot rule out that the server is at fault. We still want to gracefully handle these errors | ||||
|       validateStatus: (status) => status! <= 500, | ||||
|       receiveDataWhenStatusError: true, | ||||
|       // api is notoriously slow | ||||
|       // headers: { | ||||
|       //   HttpHeaders.userAgentHeader: 'dio', | ||||
| @@ -45,24 +47,20 @@ fetchTrip( | ||||
|   // handle errors | ||||
|   if (response.statusCode != 200) { | ||||
|     trip.updateUUID("error"); | ||||
|     throw Exception('Failed to load trip'); | ||||
|   } | ||||
|   if (response.data["error"] != null) { | ||||
|     trip.updateUUID("error"); | ||||
|     throw Exception(response.data["error"]); | ||||
|     if (response.data["detail"] != null) { | ||||
|       trip.updateError(response.data["detail"]); | ||||
|       // throw Exception(response.data["detail"]); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   log(response.data.toString()); | ||||
|   Map<String, dynamic> json = response.data; | ||||
|  | ||||
|   // only fill in the trip "meta" data for now | ||||
|   trip.loadFromJson(json); | ||||
|  | ||||
|  | ||||
|   // now fill the trip with landmarks | ||||
|   // if (trip.landmarks.isNotEmpty) { | ||||
|   //   trip.landmarks.clear(); | ||||
|   // } | ||||
|   // we are going to recreate all the landmarks from the information given by the api | ||||
|   // we are going to recreate ALL the landmarks from the information given by the api | ||||
|   trip.landmarks.remove(trip.landmarks.first); | ||||
|   String? nextUUID = json["first_landmark_uuid"]; | ||||
|   while (nextUUID != null) { | ||||
| @@ -83,8 +81,8 @@ Future<(Landmark, String?)> fetchLandmark(String uuid) async { | ||||
|   if (response.statusCode != 200) { | ||||
|     throw Exception('Failed to load landmark'); | ||||
|   } | ||||
|   if (response.data["error"] != null) { | ||||
|     throw Exception(response.data["error"]); | ||||
|   if (response.data["detail"] != null) { | ||||
|     throw Exception(response.data["detail"]); | ||||
|   } | ||||
|   log(response.data.toString()); | ||||
|   Map<String, dynamic> json = response.data; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user