Compare commits

..

No commits in common. "submission" and "main" have entirely different histories.

15 changed files with 136 additions and 178 deletions

3
.vscode/launch.json vendored
View file

@ -16,8 +16,7 @@
"cwd": "src\\client", "cwd": "src\\client",
"request": "launch", "request": "launch",
"type": "dart", "type": "dart",
"deviceId": "chrome", "args": ["-d", "chrome", "--dart-define-from-file=.env"],
"args": ["--dart-define-from-file=.env"],
"program": "lib/main.dart", "program": "lib/main.dart",
}, },
] ]

BIN
demo.mp4

Binary file not shown.

View file

@ -1,5 +1,5 @@
import "package:client/data.dart"; import "package:client/data.dart";
import "package:flutter/material.dart" hide Route; import "package:flutter/material.dart";
import "package:google_maps_flutter/google_maps_flutter.dart"; import "package:google_maps_flutter/google_maps_flutter.dart";
import "package:client/view_models.dart"; import "package:client/view_models.dart";
@ -12,7 +12,7 @@ class HomePage extends ReactiveWidget<HomeModel> {
@override @override
Widget build(BuildContext context, HomeModel model) => Scaffold( Widget build(BuildContext context, HomeModel model) => Scaffold(
appBar: AppBar(title: const Text("Bing Maps")), appBar: AppBar(title: const Text("Counter")),
body: Row( body: Row(
children: [ children: [
SizedBox( SizedBox(
@ -23,12 +23,12 @@ class HomePage extends ReactiveWidget<HomeModel> {
child: Card( child: Card(
child: Column( child: Column(
children: [ children: [
// SwitchListTile( SwitchListTile(
// value: model.markerState == MarkerState.override, value: model.markerState == MarkerState.override,
// onChanged: model.overrideMarkers, onChanged: model.overrideMarkers,
// title: const Text("Show stops list"), title: const Text("Show stops list"),
// subtitle: const Text("To select a start or end stop, use the buttons below"), subtitle: const Text("To select a start or end stop, use the buttons below"),
// ), ),
LatLongEditor( LatLongEditor(
latitudeController: model.startLatitudeController, latitudeController: model.startLatitudeController,
longitudeController: model.startLongitudeController, longitudeController: model.startLongitudeController,
@ -52,59 +52,29 @@ class HomePage extends ReactiveWidget<HomeModel> {
const Divider(), const Divider(),
const SizedBox(height: 8), const SizedBox(height: 8),
if (model.isLoading) if (model.isLoading)
const LinearProgressIndicator(), const LinearProgressIndicator()
if (model.errorText != null) else if (model.isSearching && model.path == null)
Text(model.errorText!), const Text("Could not connect to API")
else
for (final step in model.path ?? <PathStep>[]) Text(
"Get on at ${step.enter.lat}, ${step.enter.long}\n"
"Get off at ${step.exit.lat}, ${step.exit.long}",
),
Expanded( Expanded(
child: model.isGoogleReady child: model.isGoogleReady
? GoogleMap( ? GoogleMap(
onMapCreated: (controller) => model.mapController = controller, onMapCreated: (controller) => model.mapController = controller,
onTap: model.onMapTapped,
initialCameraPosition: const CameraPosition( initialCameraPosition: const CameraPosition(
target: LatLng(42.10125081757972, -75.94181323552698), target: LatLng(42.10125081757972, -75.94181323552698),
zoom: 14, zoom: 14,
), ),
markers: { markers: model.markers,
...model.markers, onTap: model.onMapTapped,
for (final (stop, reason) in model.pathWaypoint) Marker(
markerId: MarkerId(stop.id.id),
position: stop.coordinates.toLatLng(),
flat: true,
infoWindow: InfoWindow(
title: reason,
snippet: stop.description,
),
),
},
circles: {
for (final route in model.pathStops)
for (final stop in route) Circle(
circleId: CircleId(stop.hashCode.toString()),
center: stop.toLatLng(),
fillColor: Colors.white,
zIndex: 1,
radius: 2,
),
},
polylines: { polylines: {
for (final (index, route) in model.pathStops.indexed) Polyline( for (final (index, route) in model.paths.indexed) Polyline(
polylineId: PolylineId(index.toString()), polylineId: PolylineId(index.toString()),
color: routeColors[index], color: routeColors[index],
jointType: JointType.round, points: route,
points: [
for (final stop in route)
stop.toLatLng(),
],
),
for (final (index, walkingPath) in model.pathWalking.indexed) Polyline(
polylineId: PolylineId((model.pathStops.length + index).toString()),
jointType: JointType.round,
width: 8,
patterns: List.filled(20, PatternItem.gap(3)),
points: [
for (final step in walkingPath)
step.toLatLng(),
],
), ),
}, },
) )
@ -119,4 +89,4 @@ class HomePage extends ReactiveWidget<HomeModel> {
); );
} }
final routeColors = [Colors.red, Colors.orange, Colors.amber, Colors.green, Colors.blue, Colors.deepPurple, Colors.grey, Colors.pink, Colors.teal, ...List.filled(10, Colors.black)]; const routeColors = [Colors.blue, Colors.red, Colors.green, Colors.yellow, Colors.orange];

View file

@ -1,7 +1,7 @@
import "package:flutter/foundation.dart" show debugPrint, kDebugMode; import "package:flutter/foundation.dart" show debugPrint, kDebugMode;
import "package:client/data.dart"; import "package:client/data.dart";
import "package:shared/graph.dart"; import "package:http/http.dart" as http;
import "api_client.dart"; import "api_client.dart";
import "service.dart"; import "service.dart";
@ -19,20 +19,27 @@ class ApiService extends Service {
? Uri(path: "api/") ? Uri(path: "api/")
: Uri(scheme: "http", host: "localhost", port: 8001); : Uri(scheme: "http", host: "localhost", port: 8001);
Future<List<StopState>?> getPath({ Future<String?> getPath({
required Coordinates start, required Coordinates start,
required Coordinates end, required Coordinates end,
}) => _client.getJsonList( }) async {
_base.resolve("/path").replace( final uri = Uri.parse("/tmp-api/path").replace(
queryParameters: { queryParameters: {
"start_lat": start.lat.toString(), "start_lat": start.lat.toString(),
"start_lon": start.long.toString(), "start_lon": start.long.toString(),
"end_lat": end.lat.toString(), "end_lat": end.lat.toString(),
"end_lon": end.long.toString(), "end_lon": end.long.toString(),
}, },
), );
StopState.fromJson, try {
); final response = await http.get(uri);
return response.body;
// Any error should show null
// ignore: avoid_catches_without_on_clauses
} catch (error) {
return null;
}
}
Future<List<Route>?> getRoutes() => _client.getJsonList( Future<List<Route>?> getRoutes() => _client.getJsonList(
_base.resolve("/routes"), _base.resolve("/routes"),

View file

@ -2,16 +2,13 @@ import "dart:async";
import "package:client/data.dart"; import "package:client/data.dart";
import "package:client/services.dart"; import "package:client/services.dart";
import "package:flutter/widgets.dart" hide Path, Route; import "package:flutter/widgets.dart" hide Path;
import "package:google_maps_flutter/google_maps_flutter.dart"; import "package:google_maps_flutter/google_maps_flutter.dart";
import "package:shared/graph.dart";
import "home_markers.dart"; import "home_markers.dart";
import "view_model.dart"; import "view_model.dart";
typedef PathStep = (Stop, String);
/// The view model for the home page. /// The view model for the home page.
class HomeModel extends ViewModel with HomeMarkers { class HomeModel extends ViewModel with HomeMarkers {
final startLatitudeController = TextEditingController(); final startLatitudeController = TextEditingController();
@ -19,6 +16,7 @@ class HomeModel extends ViewModel with HomeMarkers {
final endLatitudeController = TextEditingController(); final endLatitudeController = TextEditingController();
final endLongitudeController = TextEditingController(); final endLongitudeController = TextEditingController();
Path? path;
GoogleMapController? mapController; GoogleMapController? mapController;
double? get startLatitude => double.tryParse(startLatitudeController.text); double? get startLatitude => double.tryParse(startLatitudeController.text);
@ -67,89 +65,18 @@ class HomeModel extends ViewModel with HomeMarkers {
super.updateEnd(coordinates, marker); super.updateEnd(coordinates, marker);
} }
List<String>? pathText; String? pathText;
List<PathStep> pathWaypoint = [];
List<List<Coordinates>> pathWalking = [];
List<List<Coordinates>> pathStops = [];
void explainPath(Iterable<StopState> path, void Function(String) printer) {
var index = 0;
for (final step in path) {
index++;
final stop = stops[step.stopID]!;
if (index == path.length) {
printer("$index. Get off at ${stop.name}");
} else {
printer("$index. ${explainStep(step)}");
}
}
}
String explainStep(StopState step) {
final route = routes[step.routeID]!;
final stop = stops[step.stopID]!;
if (step.isGoal()) return "Get off at $stop";
return switch (step.method) {
SearchMethod.bus => "Ride the bus to $stop",
SearchMethod.start => "Walk to $stop\n and board the $route line",
SearchMethod.transfer => "Transfer at $stop\n to the $route line",
SearchMethod.walk => "Walk to $stop\n and board the $route line"
};
}
Future<void> search() async { Future<void> search() async {
final start = (lat: startLatitude, long: startLongitude); final start = (lat: startLatitude, long: startLongitude);
final end = (lat: endLatitude, long: endLongitude); final end = (lat: endLatitude, long: endLongitude);
pathText = null;
errorText = null;
if (start.lat == null || start.long == null) return; if (start.lat == null || start.long == null) return;
if (end.lat == null || end.long == null) return; if (end.lat == null || end.long == null) return;
isSearching = true; isSearching = true;
isLoading = true; isLoading = true;
final path = await services.api.getPath(start: start as Coordinates, end: end as Coordinates); final result = await services.api.getPath(start: start as Coordinates, end: end as Coordinates);
pathText = result ?? "An error occurred";
isLoading = false; isLoading = false;
if (path == null) { if (result == null) return;
errorText = "An error occcurred";
return;
} else if (path.isEmpty) {
errorText = "No path found";
return;
}
pathWalking.clear();
pathWaypoint.clear();
pathStops.clear();
for (final (index, step) in path.enumerate) {
final stop = stops[step.stopID]!;
final position = stop.coordinates;
switch (step.method) {
case SearchMethod.start:
pathWalking.add([start, position]);
pathWaypoint.add( (stop, explainStep(step)) );
pathStops.add([position]);
case SearchMethod.bus:
pathStops.last.add(position);
case SearchMethod.transfer:
pathWaypoint.add( (stop, explainStep(step)) );
pathStops.last.add(position);
pathStops.add([position]);
case SearchMethod.walk:
final prevStep = path[index - 1];
final prevStopID = prevStep.stopID;
final prevStop = stops[prevStopID]!;
pathWalking.add([prevStop.coordinates, position]);
pathWaypoint.add( (prevStop, explainStep(prevStep)) );
pathWaypoint.add( (stop, explainStep(step)) );
pathStops.add([position]);
}
if (index == (path.length - 1)) {
if (step.method == SearchMethod.bus) {
pathWaypoint.add( (stop, explainStep(step)) );
}
pathWalking.add([stop.coordinates, end]);
}
}
pathText = [];
explainPath(path, pathText!.add);
isSearching = false; isSearching = false;
notifyListeners(); notifyListeners();
} }

View file

@ -51,8 +51,15 @@ mixin HomeMarkers on ChangeNotifier {
("BC Transit", bcRouteNames), ("BC Transit", bcRouteNames),
]; ];
int _parseBcNumber(String routeName) {
// eg, "53)" --> 53
final first = routeName.split(" ").first;
final withoutParen = first.substring(0, first.length - 1);
return int.parse(withoutParen);
}
int compareBcRoutes(Route a, Route b) => int compareBcRoutes(Route a, Route b) =>
int.parse(a.shortName).compareTo(int.parse(b.shortName)); _parseBcNumber(a.shortName).compareTo(_parseBcNumber(b.shortName));
int compareOcctRoutes(Route a, Route b) => int compareOcctRoutes(Route a, Route b) =>
a.shortName.compareTo(b.shortName); a.shortName.compareTo(b.shortName);

View file

@ -34,18 +34,10 @@ class Sidebar extends ReusableReactiveWidget<HomeModel> {
Tab(text: provider), Tab(text: provider),
], ],
), ),
if (!model.shouldShowMarkers) Expanded( if (!model.shouldShowMarkers && model.pathText != null)
child: ListView( SingleChildScrollView(child: Center(child: Text(model.pathText!, style: context.textTheme.bodySmall))),
children: [ if (!model.shouldShowMarkers && model.pathText == null)
if (model.pathText != null) ...[ const Center(child: Text("Choose start or end location")),
for (final line in model.pathText!)
Text(line, style: context.textTheme.bodySmall),
]
else
const Center(child: Text("Choose start or end location")),
],
),
),
if (model.shouldShowMarkers) Expanded( if (model.shouldShowMarkers) Expanded(
child: TabBarView( child: TabBarView(
children: [ children: [

View file

@ -12055,6 +12055,7 @@
"Westside Inbound", "Westside Inbound",
"Downtown Center Leroy Outbound", "Downtown Center Leroy Outbound",
"Downtown Center Leroy Inbound", "Downtown Center Leroy Inbound",
"UCLUB",
"Campus Shuttle", "Campus Shuttle",
"Riviera Ridge - Town Square Mall", "Riviera Ridge - Town Square Mall",
"Oakdale Commons", "Oakdale Commons",
@ -12069,6 +12070,7 @@
"OCCT_2", "OCCT_2",
"OCCT_3", "OCCT_3",
"OCCT_4", "OCCT_4",
"OCCT_5",
"OCCT_8", "OCCT_8",
"OCCT_9", "OCCT_9",
"OCCT_10", "OCCT_10",
@ -12383,6 +12385,7 @@
"routes": [ "routes": [
"Westside Inbound", "Westside Inbound",
"Downtown Center Leroy Inbound", "Downtown Center Leroy Inbound",
"UCLUB",
"Campus Shuttle", "Campus Shuttle",
"Riviera Ridge - Town Square Mall", "Riviera Ridge - Town Square Mall",
"Oakdale Commons", "Oakdale Commons",
@ -12393,6 +12396,7 @@
"route_ids": [ "route_ids": [
"OCCT_2", "OCCT_2",
"OCCT_4", "OCCT_4",
"OCCT_5",
"OCCT_8", "OCCT_8",
"OCCT_9", "OCCT_9",
"OCCT_10", "OCCT_10",
@ -12717,10 +12721,12 @@
"longitude": -75.9657592773438, "longitude": -75.9657592773438,
"provider": "OCCT", "provider": "OCCT",
"routes": [ "routes": [
"UCLUB",
"Campus Shuttle", "Campus Shuttle",
"ITC - UCLUB" "ITC - UCLUB"
], ],
"route_ids": [ "route_ids": [
"OCCT_5",
"OCCT_8", "OCCT_8",
"OCCT_16" "OCCT_16"
] ]
@ -12733,10 +12739,12 @@
"longitude": -75.9673309326172, "longitude": -75.9673309326172,
"provider": "OCCT", "provider": "OCCT",
"routes": [ "routes": [
"UCLUB",
"Campus Shuttle", "Campus Shuttle",
"ITC - UCLUB" "ITC - UCLUB"
], ],
"route_ids": [ "route_ids": [
"OCCT_5",
"OCCT_8", "OCCT_8",
"OCCT_16" "OCCT_16"
] ]
@ -12749,9 +12757,11 @@
"longitude": -75.9564208984375, "longitude": -75.9564208984375,
"provider": "OCCT", "provider": "OCCT",
"routes": [ "routes": [
"UCLUB",
"ITC - UCLUB" "ITC - UCLUB"
], ],
"route_ids": [ "route_ids": [
"OCCT_5",
"OCCT_16" "OCCT_16"
] ]
}, },
@ -12763,9 +12773,11 @@
"longitude": -75.9558868408203, "longitude": -75.9558868408203,
"provider": "OCCT", "provider": "OCCT",
"routes": [ "routes": [
"UCLUB",
"ITC - UCLUB" "ITC - UCLUB"
], ],
"route_ids": [ "route_ids": [
"OCCT_5",
"OCCT_16" "OCCT_16"
] ]
}, },
@ -12777,10 +12789,12 @@
"longitude": -75.9628753662109, "longitude": -75.9628753662109,
"provider": "OCCT", "provider": "OCCT",
"routes": [ "routes": [
"UCLUB",
"Campus Shuttle", "Campus Shuttle",
"ITC - UCLUB" "ITC - UCLUB"
], ],
"route_ids": [ "route_ids": [
"OCCT_5",
"OCCT_8", "OCCT_8",
"OCCT_16" "OCCT_16"
] ]
@ -12793,9 +12807,11 @@
"longitude": -75.959098815918, "longitude": -75.959098815918,
"provider": "OCCT", "provider": "OCCT",
"routes": [ "routes": [
"UCLUB",
"ITC - UCLUB" "ITC - UCLUB"
], ],
"route_ids": [ "route_ids": [
"OCCT_5",
"OCCT_16" "OCCT_16"
] ]
}, },
@ -12807,9 +12823,11 @@
"longitude": -75.9538421630859, "longitude": -75.9538421630859,
"provider": "OCCT", "provider": "OCCT",
"routes": [ "routes": [
"UCLUB",
"ITC - UCLUB" "ITC - UCLUB"
], ],
"route_ids": [ "route_ids": [
"OCCT_5",
"OCCT_16" "OCCT_16"
] ]
}, },
@ -12821,9 +12839,11 @@
"longitude": -75.9595413208008, "longitude": -75.9595413208008,
"provider": "OCCT", "provider": "OCCT",
"routes": [ "routes": [
"UCLUB",
"ITC - UCLUB" "ITC - UCLUB"
], ],
"route_ids": [ "route_ids": [
"OCCT_5",
"OCCT_16" "OCCT_16"
] ]
}, },

View file

@ -1242,6 +1242,24 @@
"OCCT_1" "OCCT_1"
] ]
}, },
{
"id": "OCCT_5",
"provider": "OCCT",
"full_name": "UCLUB",
"short_name": "UC",
"stops": [
"OCCT_61",
"OCCT_63",
"OCCT_64",
"OCCT_65",
"OCCT_66",
"OCCT_67",
"OCCT_68",
"OCCT_69",
"OCCT_134",
"OCCT_1"
]
},
{ {
"id": "OCCT_8", "id": "OCCT_8",
"provider": "OCCT", "provider": "OCCT",

View file

@ -10,12 +10,10 @@ class BcRoutesParser extends Parser<RouteID, Route> {
final tripsFile = File(bcDataDir / "trips.txt"); final tripsFile = File(bcDataDir / "trips.txt");
final stopParser = BcStopParser(); final stopParser = BcStopParser();
static final routesToSkip = ["48", "91"];
Future<Map<RouteID, Route>> getRoutes() async => { Future<Map<RouteID, Route>> getRoutes() async => {
for (final csv in await readCsv(routesFile)) for (final csv in await readCsv(routesFile))
if (!routesToSkip.contains(csv[0])) RouteID.fromBcCsv(csv): Route.fromBcCsv(csv),
RouteID.fromBcCsv(csv): Route.fromBcCsv(csv),
}; };
Future<Map<RouteID, TripID>> getTripForRoutes(Map<TripID, List<StopID>> trips) async { Future<Map<RouteID, TripID>> getTripForRoutes(Map<TripID, List<StopID>> trips) async {

View file

@ -4,12 +4,10 @@ import "utils.dart";
class OcctRoutesParser extends Parser<RouteID, Route> { class OcctRoutesParser extends Parser<RouteID, Route> {
final routesFile = File(occtDataDir / "routes.json"); final routesFile = File(occtDataDir / "routes.json");
static final routesToSkip = {5};
@override @override
Future<Map<RouteID, Route>> parse() async => { Future<Map<RouteID, Route>> parse() async => {
for (final routeJson in await readJson(routesFile)) for (final routeJson in await readJson(routesFile))
if (!routesToSkip.contains(routeJson["id"])) RouteID(Provider.occt, routeJson["id"]): Route.fromOcctJson(routeJson),
RouteID(Provider.occt, routeJson["id"]): Route.fromOcctJson(routeJson),
}; };
} }

View file

@ -38,7 +38,7 @@ class OcctStopParser extends Parser<StopID, Stop> {
final stops = await getStops(); final stops = await getStops();
final routes = await getRoutes(); final routes = await getRoutes();
final routeNames = await getRouteNames(); final routeNames = await getRouteNames();
final routesToSkip = <RouteID>{RouteID(Provider.occt, "11"), RouteID(Provider.occt, "5")}; final routesToSkip = <RouteID>{RouteID(Provider.occt, "11")};
for (final (stopID, stop) in stops.records) { for (final (stopID, stop) in stops.records) {
for (final routeID in routes[stopID]!) { for (final routeID in routes[stopID]!) {
if (routesToSkip.contains(routeID)) continue; if (routesToSkip.contains(routeID)) continue;

View file

@ -32,7 +32,7 @@ Iterable<StopState>? findPath(Coordinates start, Coordinates end) {
endPoint: end, endPoint: end,
); );
log("Finding a route using ${state.hash()}"); log("Finding a route using ${state.hash()}");
final result = aStar(state, limit: 2500); final result = aStar(state, limit: 10000);
if (result == null) continue; if (result == null) continue;
paths.add(result.reconstructPath()); paths.add(result.reconstructPath());
} }
@ -45,3 +45,16 @@ Iterable<StopState>? findPath(Coordinates start, Coordinates end) {
double getTotalDistance(Iterable<StopState> path) => double getTotalDistance(Iterable<StopState> path) =>
path.sum((step) => step.distanceWalked); path.sum((step) => step.distanceWalked);
void explainPath(Iterable<StopState> path, void Function(String) printer) {
var index = 0;
for (final step in path) {
index++;
final stop = StopState.stops[step.stopID]!;
if (index == path.length) {
printer("$index. Get off at ${stop.name}");
} else {
printer("$index. ${step.explanation}");
}
}
}

View file

@ -15,7 +15,7 @@ class StopState extends AStarState<StopState> with Encodable {
static late final Map<StopID, Stop> stops; static late final Map<StopID, Stop> stops;
static Iterable<Stop> findStopsNear(Coordinates location) { static Iterable<Stop> findStopsNear(Coordinates location) {
const numStops = 3; const numStops = 5;
log("Finding stops near $location..."); log("Finding stops near $location...");
final stopDistances = [ final stopDistances = [
for (final stop in stops.values) for (final stop in stops.values)
@ -62,11 +62,11 @@ class StopState extends AStarState<StopState> with Encodable {
factory StopState.fromJson(Json json) => StopState( factory StopState.fromJson(Json json) => StopState(
stopID: StopID.fromJson(json["stop_id"]), stopID: StopID.fromJson(json["stop_id"]),
goalID: StopID.fromJson(json["goal_id"]),
routeID: RouteID.fromJson(json["route_id"]), routeID: RouteID.fromJson(json["route_id"]),
method: SearchMethod.fromJson(json["method"]), method: SearchMethod.fromJson(json["method"]),
depth: 0, depth: 0,
distanceWalked: 0, distanceWalked: 0,
goalID: StopID.fromJson("N/A"),
); );
@override @override
@ -74,7 +74,7 @@ class StopState extends AStarState<StopState> with Encodable {
final stop = stops[stopID]; final stop = stops[stopID];
final goal = stops[goalID]; final goal = stops[goalID];
final route = routes[routeID]; final route = routes[routeID];
return "$distanceWalked-${method.name}-$stop-$goal-$route"; return "${method.name}-$stop-$goal-$route";
} }
@override @override
@ -89,8 +89,7 @@ class StopState extends AStarState<StopState> with Encodable {
@override @override
Iterable<StopState> expand() { Iterable<StopState> expand() {
final route = routes[routeID]; final route = routes[routeID]!;
if (route == null) return [];
final stop = stops[stopID]!; final stop = stops[stopID]!;
final stopIndex = route.stops.indexOf(stopID); final stopIndex = route.stops.indexOf(stopID);
if (stopIndex == -1) return []; if (stopIndex == -1) return [];
@ -100,13 +99,11 @@ class StopState extends AStarState<StopState> with Encodable {
final nextIndex = stopIndex + 1; final nextIndex = stopIndex + 1;
if (nextIndex < route.stops.length) { if (nextIndex < route.stops.length) {
final nextStopID = route.stops[nextIndex]; final nextStopID = route.stops[nextIndex];
final nextStop = stops[nextStopID]!;
final distanceDriven = stop.coordinates.distanceTo(nextStop.coordinates);
final state = StopState( final state = StopState(
stopID: nextStopID, stopID: nextStopID,
goalID: goalID, goalID: goalID,
routeID: routeID, routeID: routeID,
depth: depth + distanceDriven, depth: depth + 1,
distanceWalked: distanceWalked, distanceWalked: distanceWalked,
method: SearchMethod.bus, method: SearchMethod.bus,
); );
@ -115,15 +112,13 @@ class StopState extends AStarState<StopState> with Encodable {
// 2) Go back one stop on the same route // 2) Go back one stop on the same route
final prevIndex = stopIndex - 1; final prevIndex = stopIndex - 1;
if (prevIndex >= 0) { if (prevIndex > 0) {
final prevStopID = route.stops[prevIndex]; final prevStopID = route.stops[prevIndex];
final prevStop = stops[prevStopID]!;
final distanceDriven = stop.coordinates.distanceTo(prevStop.coordinates);
final state = StopState( final state = StopState(
stopID: prevStopID, stopID: prevStopID,
goalID: goalID, goalID: goalID,
routeID: routeID, routeID: routeID,
depth: depth + distanceDriven, depth: depth + 1,
distanceWalked: distanceWalked, distanceWalked: distanceWalked,
method: SearchMethod.bus, method: SearchMethod.bus,
); );
@ -136,7 +131,7 @@ class StopState extends AStarState<StopState> with Encodable {
stopID: stopID, stopID: stopID,
goalID: goalID, goalID: goalID,
routeID: otherRouteID, routeID: otherRouteID,
depth: depth + 5, depth: depth + 2,
distanceWalked: distanceWalked, distanceWalked: distanceWalked,
method: SearchMethod.transfer, method: SearchMethod.transfer,
); );
@ -151,7 +146,7 @@ class StopState extends AStarState<StopState> with Encodable {
stopID: otherStop.id, stopID: otherStop.id,
goalID: goalID, goalID: goalID,
routeID: otherRouteID, routeID: otherRouteID,
depth: depth + 5 + 10*distanceToOther, depth: depth + 3,
distanceWalked: distanceWalked + distanceToOther, distanceWalked: distanceWalked + distanceToOther,
method: SearchMethod.walk, method: SearchMethod.walk,
); );
@ -162,10 +157,20 @@ class StopState extends AStarState<StopState> with Encodable {
return result; return result;
} }
String get explanation {
final route = routes[routeID]!;
final stop = stops[stopID]!;
return switch (method) {
SearchMethod.bus => "Go one stop to $stop",
SearchMethod.start => "Walk to $stop\n and board the $route line",
SearchMethod.transfer => "Transfer to the $route line",
SearchMethod.walk => "Walk to $stop\n and board the $route line"
};
}
@override @override
Json toJson() => { Json toJson() => {
"stop_id": stopID, "stop_id": stopID,
"goal_id": goalID,
"route_id": routeID, "route_id": routeID,
"method": method.name, "method": method.name,
}; };

View file

@ -14,7 +14,11 @@ Response getPath(Request request) {
final start = (lat: startLat, long: startLong); final start = (lat: startLat, long: startLong);
final end = (lat: endLat, long: endLong); final end = (lat: endLat, long: endLong);
final path = findPath(start, end) ?? []; final path = findPath(start, end);
final jsonBody = encodeJsonList(path); if (path == null) {
return Response.ok(jsonBody, headers: corsHeaders); return Response.ok("No routes found", headers: corsHeaders);
}
final buffer = StringBuffer();
explainPath(path, buffer.writeln);
return Response.ok(buffer.toString(), headers: corsHeaders);
} }