Compare commits
9 commits
main
...
submission
Author | SHA1 | Date | |
---|---|---|---|
|
23c20cdfd0 | ||
|
5c2dd0395e | ||
|
9a01b1b443 | ||
|
a5090988d5 | ||
|
2e3813398e | ||
|
eaec35f730 | ||
|
92927589fb | ||
|
6d3011b706 | ||
|
1c5c6c9f11 |
15 changed files with 178 additions and 136 deletions
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
|
@ -16,7 +16,8 @@
|
||||||
"cwd": "src\\client",
|
"cwd": "src\\client",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"type": "dart",
|
"type": "dart",
|
||||||
"args": ["-d", "chrome", "--dart-define-from-file=.env"],
|
"deviceId": "chrome",
|
||||||
|
"args": ["--dart-define-from-file=.env"],
|
||||||
"program": "lib/main.dart",
|
"program": "lib/main.dart",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
BIN
demo.mp4
BIN
demo.mp4
Binary file not shown.
|
@ -1,5 +1,5 @@
|
||||||
import "package:client/data.dart";
|
import "package:client/data.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart" hide Route;
|
||||||
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("Counter")),
|
appBar: AppBar(title: const Text("Bing Maps")),
|
||||||
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,29 +52,59 @@ 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(),
|
||||||
else if (model.isSearching && model.path == null)
|
if (model.errorText != null)
|
||||||
const Text("Could not connect to API")
|
Text(model.errorText!),
|
||||||
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: model.markers,
|
markers: {
|
||||||
onTap: model.onMapTapped,
|
...model.markers,
|
||||||
|
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.paths.indexed) Polyline(
|
for (final (index, route) in model.pathStops.indexed) Polyline(
|
||||||
polylineId: PolylineId(index.toString()),
|
polylineId: PolylineId(index.toString()),
|
||||||
color: routeColors[index],
|
color: routeColors[index],
|
||||||
points: route,
|
jointType: JointType.round,
|
||||||
|
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(),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -89,4 +119,4 @@ class HomePage extends ReactiveWidget<HomeModel> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const routeColors = [Colors.blue, Colors.red, Colors.green, Colors.yellow, Colors.orange];
|
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)];
|
||||||
|
|
|
@ -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:http/http.dart" as http;
|
import "package:shared/graph.dart";
|
||||||
|
|
||||||
import "api_client.dart";
|
import "api_client.dart";
|
||||||
import "service.dart";
|
import "service.dart";
|
||||||
|
@ -19,27 +19,20 @@ 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<String?> getPath({
|
Future<List<StopState>?> getPath({
|
||||||
required Coordinates start,
|
required Coordinates start,
|
||||||
required Coordinates end,
|
required Coordinates end,
|
||||||
}) async {
|
}) => _client.getJsonList(
|
||||||
final uri = Uri.parse("/tmp-api/path").replace(
|
_base.resolve("/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(),
|
||||||
},
|
},
|
||||||
);
|
),
|
||||||
try {
|
StopState.fromJson,
|
||||||
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"),
|
||||||
|
|
|
@ -2,13 +2,16 @@ 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;
|
import "package:flutter/widgets.dart" hide Path, Route;
|
||||||
|
|
||||||
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();
|
||||||
|
@ -16,7 +19,6 @@ 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);
|
||||||
|
@ -65,18 +67,89 @@ class HomeModel extends ViewModel with HomeMarkers {
|
||||||
super.updateEnd(coordinates, marker);
|
super.updateEnd(coordinates, marker);
|
||||||
}
|
}
|
||||||
|
|
||||||
String? pathText;
|
List<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 result = await services.api.getPath(start: start as Coordinates, end: end as Coordinates);
|
final path = await services.api.getPath(start: start as Coordinates, end: end as Coordinates);
|
||||||
pathText = result ?? "An error occurred";
|
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
if (result == null) return;
|
if (path == null) {
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,15 +51,8 @@ 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) =>
|
||||||
_parseBcNumber(a.shortName).compareTo(_parseBcNumber(b.shortName));
|
int.parse(a.shortName).compareTo(int.parse(b.shortName));
|
||||||
|
|
||||||
int compareOcctRoutes(Route a, Route b) =>
|
int compareOcctRoutes(Route a, Route b) =>
|
||||||
a.shortName.compareTo(b.shortName);
|
a.shortName.compareTo(b.shortName);
|
||||||
|
|
|
@ -34,10 +34,18 @@ class Sidebar extends ReusableReactiveWidget<HomeModel> {
|
||||||
Tab(text: provider),
|
Tab(text: provider),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (!model.shouldShowMarkers && model.pathText != null)
|
if (!model.shouldShowMarkers) Expanded(
|
||||||
SingleChildScrollView(child: Center(child: Text(model.pathText!, style: context.textTheme.bodySmall))),
|
child: ListView(
|
||||||
if (!model.shouldShowMarkers && model.pathText == null)
|
children: [
|
||||||
const Center(child: Text("Choose start or end location")),
|
if (model.pathText != null) ...[
|
||||||
|
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: [
|
||||||
|
|
|
@ -12055,7 +12055,6 @@
|
||||||
"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",
|
||||||
|
@ -12070,7 +12069,6 @@
|
||||||
"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",
|
||||||
|
@ -12385,7 +12383,6 @@
|
||||||
"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",
|
||||||
|
@ -12396,7 +12393,6 @@
|
||||||
"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",
|
||||||
|
@ -12721,12 +12717,10 @@
|
||||||
"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"
|
||||||
]
|
]
|
||||||
|
@ -12739,12 +12733,10 @@
|
||||||
"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"
|
||||||
]
|
]
|
||||||
|
@ -12757,11 +12749,9 @@
|
||||||
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -12773,11 +12763,9 @@
|
||||||
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -12789,12 +12777,10 @@
|
||||||
"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"
|
||||||
]
|
]
|
||||||
|
@ -12807,11 +12793,9 @@
|
||||||
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -12823,11 +12807,9 @@
|
||||||
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -12839,11 +12821,9 @@
|
||||||
"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"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -1242,24 +1242,6 @@
|
||||||
"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",
|
||||||
|
|
|
@ -10,10 +10,12 @@ 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))
|
||||||
RouteID.fromBcCsv(csv): Route.fromBcCsv(csv),
|
if (!routesToSkip.contains(csv[0]))
|
||||||
|
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 {
|
||||||
|
|
|
@ -4,10 +4,12 @@ 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))
|
||||||
RouteID(Provider.occt, routeJson["id"]): Route.fromOcctJson(routeJson),
|
if (!routesToSkip.contains(routeJson["id"]))
|
||||||
|
RouteID(Provider.occt, routeJson["id"]): Route.fromOcctJson(routeJson),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")};
|
final routesToSkip = <RouteID>{RouteID(Provider.occt, "11"), RouteID(Provider.occt, "5")};
|
||||||
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;
|
||||||
|
|
|
@ -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: 10000);
|
final result = aStar(state, limit: 2500);
|
||||||
if (result == null) continue;
|
if (result == null) continue;
|
||||||
paths.add(result.reconstructPath());
|
paths.add(result.reconstructPath());
|
||||||
}
|
}
|
||||||
|
@ -45,16 +45,3 @@ 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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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 = 5;
|
const numStops = 3;
|
||||||
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 "${method.name}-$stop-$goal-$route";
|
return "$distanceWalked-${method.name}-$stop-$goal-$route";
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -89,7 +89,8 @@ 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 [];
|
||||||
|
@ -99,11 +100,13 @@ 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 + 1,
|
depth: depth + distanceDriven,
|
||||||
distanceWalked: distanceWalked,
|
distanceWalked: distanceWalked,
|
||||||
method: SearchMethod.bus,
|
method: SearchMethod.bus,
|
||||||
);
|
);
|
||||||
|
@ -112,13 +115,15 @@ 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 + 1,
|
depth: depth + distanceDriven,
|
||||||
distanceWalked: distanceWalked,
|
distanceWalked: distanceWalked,
|
||||||
method: SearchMethod.bus,
|
method: SearchMethod.bus,
|
||||||
);
|
);
|
||||||
|
@ -131,7 +136,7 @@ class StopState extends AStarState<StopState> with Encodable {
|
||||||
stopID: stopID,
|
stopID: stopID,
|
||||||
goalID: goalID,
|
goalID: goalID,
|
||||||
routeID: otherRouteID,
|
routeID: otherRouteID,
|
||||||
depth: depth + 2,
|
depth: depth + 5,
|
||||||
distanceWalked: distanceWalked,
|
distanceWalked: distanceWalked,
|
||||||
method: SearchMethod.transfer,
|
method: SearchMethod.transfer,
|
||||||
);
|
);
|
||||||
|
@ -146,7 +151,7 @@ class StopState extends AStarState<StopState> with Encodable {
|
||||||
stopID: otherStop.id,
|
stopID: otherStop.id,
|
||||||
goalID: goalID,
|
goalID: goalID,
|
||||||
routeID: otherRouteID,
|
routeID: otherRouteID,
|
||||||
depth: depth + 3,
|
depth: depth + 5 + 10*distanceToOther,
|
||||||
distanceWalked: distanceWalked + distanceToOther,
|
distanceWalked: distanceWalked + distanceToOther,
|
||||||
method: SearchMethod.walk,
|
method: SearchMethod.walk,
|
||||||
);
|
);
|
||||||
|
@ -157,20 +162,10 @@ 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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,11 +14,7 @@ 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) ?? [];
|
||||||
if (path == null) {
|
final jsonBody = encodeJsonList(path);
|
||||||
return Response.ok("No routes found", headers: corsHeaders);
|
return Response.ok(jsonBody, headers: corsHeaders);
|
||||||
}
|
|
||||||
final buffer = StringBuffer();
|
|
||||||
explainPath(path, buffer.writeln);
|
|
||||||
return Response.ok(buffer.toString(), headers: corsHeaders);
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue