Compare commits

..

No commits in common. "23c20cdfd0f58fd48068a81d89af4c2374730305" and "6d3011b706f6af3fc5ab3265bb0da900af2e1619" have entirely different histories.

24 changed files with 150 additions and 299 deletions

3
.vscode/launch.json vendored
View file

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

41
LICENSE
View file

@ -1,41 +0,0 @@
Copyright (c) 2025 Spencer Powell & Levi Lesches
At time of writing this software is not licensed under a permissive license.
If you are Professor Steven Moore of Binghamton University you may access the code as it exists on May 5th 2025 under an MIT license for the purposes of grading.
A copy of the MIT license can be found at https://choosealicense.com/licenses/mit/ or at the bottom of this file.
If you are anyone else you ARE NOT granted permission to read, use, modify, copy, merge, publich, distribute, sublicense or sell the software or any other rights to this software.
Versions of this software written/published after May 5th 2025 may fall under a different license.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-----------------------------------------------------
Example of MIT License THIS IS NOT THE LICENSE THIS SOFTWARE FALLS UNDER barring relicensing.
Copyright (c) [year] [fullname]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,60 +1,47 @@
[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/vfKrPwQS)
# Binghamton Better Bus (BBB) v2
# << Project Title >>
## CS 445 Final Project
### Spring, 2025
### Team: Team 4
### Team: << team name >>
- Spencer Powell
- Levi Lesches
## Getting Started
This project is a website which allows for picking a source and destination and getting the ideal bus route between the 2 given static information.
<<One paragraph of project description goes here>>
### Roadmap
<<
A list of features, function or non-functional, you would like to add in the future if you had time, i.e. Phase 2 stuff
- [ ] Make use of time/direction information available in static scheduling information
- [ ] Place our hardware on OCCT buses to remove ETA Spot dependence/legal question mark
- [ ] Design/build hardware
- Keyword reminder for mobile data: IoT data plan and/or M2M
- [ ] Negotiate placing hardware on OCCT buses
- [ ] Chat with BCT about getting their live bus info
- [ ] Use machine learning to predict bus locations based on current position, current time of day and current route,
- [ ] Add Changelog
- [ ] Add back to top links
- [ ] Add Additional Templates w/ Examples
- [ ] Add "components" document to easily copy & paste sections of the readme
>>
## SRS
[doc](https://docs.google.com/document/d/1kSWMxsK0NakhvHRQnNx0wRlL6wDgYGO-tA7JB_4qldE/edit?usp=sharing)
### Prerequisites
* [Docker](https://www.docker.com/)
* Docker Compose
* [Just](https://github.com/casey/just)
* <<any additional software. Be specific about versions.>>
### Installing
cd into the `src` directory and run `just setup` then run `docker-compose build`.
To run just run `docker-compose up` in the `src` directory, the site will be up on `localhost:8080`
<<
A step by step series of examples that tell you how to get a development env running
Clearly outline each step and repeat until the environment is set up.
End with an example of getting some output from the system, such as a menu or prompt
>>
## Built With
* [Deno](https://deno.com/)
- [Acorn](https://oakserver.org/acorn)
* [Dart](https://dart.dev/)
* [Flutter](https://flutter.dev/)
- Google maps
* [Caddy](https://caddyserver.com/)
<< list all frameworks and modules used here >>
* [requests](https://docs.python-requests.org/en/latest/user/quickstart/#make-a-request) - request for humans
## License
DIY license written out which grants MIT rights to professor moore for the version of this submitted for grading and no rights to anyone else.
<< Add a [license](https://choosealicense.com/) >>
## Acknowledgments
* Claude and chatGPT were used for the creation of scripts for certain rote data conversion tasks
* This project would not exist with the data it has if not for prior instances of attempts to make this idea by US, of particular note is the usage of GTFS data which was only learned of in a prior attempt with Lucy Loerker
* Hat tip to anyone whose code was used
* Inspiration
* etc

BIN
demo.mp4

Binary file not shown.

View file

@ -1,13 +0,0 @@
setup:
# this is a hack that is done due to a lack of communication
# towards the end of this project
# a slightly better hack could be done which avoids this but I
# don't wanna write things to work that way
cp -r client shared/client-dir
cp -r shared client/shared-dir
cp -r server/data shared/server-data
cp pubspec.yaml client/root-pubspec.yaml
cp pubspec.yaml shared/root-pubspec.yaml
clean:
rm -rf client/root-pubspec.yaml shared/root-pubspec.yaml shared/client-dir client/shared-dir shared/server-data

View file

@ -45,6 +45,3 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
shared-dir
root-pubspec.yaml

View file

@ -20,15 +20,11 @@ RUN flutter upgrade
RUN flutter doctor
COPY root-pubspec.yaml /client/pubspec.yaml
COPY ./shared-dir /client/shared
COPY ./ /client/client
WORKDIR /client/client
COPY ./ /client/
RUN flutter build web --dart-define-from-file=.env
WORKDIR /client/client/build/web
WORKDIR /client/build/web
EXPOSE 80/tcp

View file

@ -1,5 +1,5 @@
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:client/view_models.dart";
@ -12,7 +12,7 @@ class HomePage extends ReactiveWidget<HomeModel> {
@override
Widget build(BuildContext context, HomeModel model) => Scaffold(
appBar: AppBar(title: const Text("Bing Maps")),
appBar: AppBar(title: const Text("Counter")),
body: Row(
children: [
SizedBox(
@ -23,12 +23,12 @@ class HomePage extends ReactiveWidget<HomeModel> {
child: Card(
child: Column(
children: [
// SwitchListTile(
// value: model.markerState == MarkerState.override,
// onChanged: model.overrideMarkers,
// title: const Text("Show stops list"),
// subtitle: const Text("To select a start or end stop, use the buttons below"),
// ),
SwitchListTile(
value: model.markerState == MarkerState.override,
onChanged: model.overrideMarkers,
title: const Text("Show stops list"),
subtitle: const Text("To select a start or end stop, use the buttons below"),
),
LatLongEditor(
latitudeController: model.startLatitudeController,
longitudeController: model.startLongitudeController,
@ -52,59 +52,29 @@ class HomePage extends ReactiveWidget<HomeModel> {
const Divider(),
const SizedBox(height: 8),
if (model.isLoading)
const LinearProgressIndicator(),
if (model.errorText != null)
Text(model.errorText!),
const LinearProgressIndicator()
else if (model.isSearching && model.path == null)
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(
child: model.isGoogleReady
? GoogleMap(
onMapCreated: (controller) => model.mapController = controller,
onTap: model.onMapTapped,
initialCameraPosition: const CameraPosition(
target: LatLng(42.10125081757972, -75.94181323552698),
zoom: 14,
),
markers: {
...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,
),
},
markers: model.markers,
onTap: model.onMapTapped,
polylines: {
for (final (index, route) in model.pathStops.indexed) Polyline(
for (final (index, route) in model.paths.indexed) Polyline(
polylineId: PolylineId(index.toString()),
color: routeColors[index],
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(),
],
points: route,
),
},
)
@ -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

@ -2,7 +2,7 @@ import "dart:async";
import "package:client/data.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:shared/graph.dart";
@ -10,8 +10,6 @@ import "package:shared/graph.dart";
import "home_markers.dart";
import "view_model.dart";
typedef PathStep = (Stop, String);
/// The view model for the home page.
class HomeModel extends ViewModel with HomeMarkers {
final startLatitudeController = TextEditingController();
@ -19,6 +17,7 @@ class HomeModel extends ViewModel with HomeMarkers {
final endLatitudeController = TextEditingController();
final endLongitudeController = TextEditingController();
Path? path;
GoogleMapController? mapController;
double? get startLatitude => double.tryParse(startLatitudeController.text);
@ -67,8 +66,8 @@ class HomeModel extends ViewModel with HomeMarkers {
super.updateEnd(coordinates, marker);
}
List<String>? pathText;
List<PathStep> pathWaypoint = [];
String? pathText;
List<Coordinates> pathWaypoint = [];
List<List<Coordinates>> pathWalking = [];
List<List<Coordinates>> pathStops = [];
@ -88,11 +87,10 @@ class HomeModel extends ViewModel with HomeMarkers {
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.bus => "Go one stop to $stop",
SearchMethod.start => "Walk to $stop\n and board the $route line",
SearchMethod.transfer => "Transfer at $stop\n to the $route line",
SearchMethod.transfer => "Transfer to the $route line",
SearchMethod.walk => "Walk to $stop\n and board the $route line"
};
}
@ -101,7 +99,6 @@ class HomeModel extends ViewModel with HomeMarkers {
final start = (lat: startLatitude, long: startLongitude);
final end = (lat: endLatitude, long: endLongitude);
pathText = null;
errorText = null;
if (start.lat == null || start.long == null) return;
if (end.lat == null || end.long == null) return;
isSearching = true;
@ -111,9 +108,6 @@ class HomeModel extends ViewModel with HomeMarkers {
if (path == null) {
errorText = "An error occcurred";
return;
} else if (path.isEmpty) {
errorText = "No path found";
return;
}
pathWalking.clear();
pathWaypoint.clear();
@ -124,32 +118,25 @@ class HomeModel extends ViewModel with HomeMarkers {
switch (step.method) {
case SearchMethod.start:
pathWalking.add([start, position]);
pathWaypoint.add( (stop, explainStep(step)) );
pathWaypoint.add(position);
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]);
pathWaypoint.add(position);
pathStops.add([]);
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)) );
pathWaypoint.add(stop.coordinates);
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);
final buffer = StringBuffer();
explainPath(path, buffer.writeln);
pathText = buffer.toString();
isSearching = false;
notifyListeners();
}

View file

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

View file

@ -6,17 +6,12 @@ services:
depends_on:
- server
- client
- hack
restart: unless-stopped
client:
build: ./client
restart: unless-stopped
hack:
build: ./shared
restart: unless-stopped
server:
build: ./server
environment:
@ -24,30 +19,29 @@ services:
#ports:
# - "127.0.0.1:8080:80"
restart: unless-stopped
# depends_on:
# neo4j:
# condition: service_healthy
#neo4j:
# image: neo4j:latest
# volumes:
# - neo4jconfig:/config
# - neo4jdata:/data
# - neo4jplugins:/plugins
# environment:
# # neo4j isn't exposed to the internet so having the password checked into version control doesn't matter
# - NEO4J_AUTH=neo4j/your_password
# ports:
# # useful for dev
# - "127.0.0.1:7474:7474"
# - "127.0.0.1:7687:7687"
# restart: unless-stopped
# healthcheck:
# test: wget http://localhost:7474 || exit 1
# interval: 1s
# timeout: 10s
# retries: 20
# start_period: 3s
depends_on:
neo4j:
condition: service_healthy
neo4j:
image: neo4j:latest
volumes:
- neo4jconfig:/config
- neo4jdata:/data
- neo4jplugins:/plugins
environment:
# neo4j isn't exposed to the internet so having the password checked into version control doesn't matter
- NEO4J_AUTH=neo4j/your_password
ports:
# useful for dev
- "127.0.0.1:7474:7474"
- "127.0.0.1:7687:7687"
restart: unless-stopped
healthcheck:
test: wget http://localhost:7474 || exit 1
interval: 1s
timeout: 10s
retries: 20
start_period: 3s
volumes:
neo4jconfig:

View file

@ -3,7 +3,4 @@
handle_path /api/* {
reverse_proxy server:80
}
handle_path /tmp-api/* {
reverse_proxy hack:8001
}
}

View file

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

View file

@ -1242,6 +1242,24 @@
"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",
"provider": "OCCT",

View file

@ -24,13 +24,13 @@ const substitute_base_name = substitute_base_name_.bind(
const { hostname, port } = { hostname: "0.0.0.0", port: 80 };
const neo4jHost = usingDocker ? "neo4j" : "127.0.0.1"; // localhost does NOT work
//const graph_driver = neo4j.driver(
// `neo4j://${neo4jHost}:7687`,
// neo4j.auth.basic("neo4j", "your_password"),
//);
//console.log("initializing graph with static data");
//await graph_setup(graph_driver);
//console.log("graph initialization complete");
const graph_driver = neo4j.driver(
`neo4j://${neo4jHost}:7687`,
neo4j.auth.basic("neo4j", "your_password"),
);
console.log("initializing graph with static data");
await graph_setup(graph_driver);
console.log("graph initialization complete");
const db = new DatabaseSync(":memory:");
db_setup(db);

View file

@ -2,8 +2,5 @@
# Created by `dart pub`
.dart_tool/
pubspec.lock
client-dir
server-data
root-pubspec.yaml
path.log
notes.md

View file

@ -1,42 +0,0 @@
FROM alpine:3.21
RUN mkdir /client
RUN apk add bash curl file git unzip which zip gcompat wget tar xz
#ENTRYPOINT bash
WORKDIR /client
RUN wget -O flutter.tar.xz https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.29.0-stable.tar.xz
RUN tar xf flutter.tar.xz
ENV PATH="/client/flutter/bin/:${PATH}"
RUN git config --global --add safe.directory /client/flutter
RUN flutter upgrade
RUN flutter doctor
COPY root-pubspec.yaml /client/pubspec.yaml
COPY ./ /client/shared
RUN mkdir /client/client
COPY ./client-dir/* /client/client
RUN mkdir /client/server
COPY server-data /client/server/data
RUN dart pub get
WORKDIR /client/shared
EXPOSE 80/tcp
CMD ["dart", "bin/server.dart"]

View file

@ -20,6 +20,6 @@ void main() async {
router.get("/path", handlers.getPath);
router.get("/stops", handlers.getStops);
router.get("/routes", handlers.getRoutes);
await io.serve(router.call, "0.0.0.0", 8001);
print("Listening on 0.0.0.0:8001");
await io.serve(router.call, "localhost", 8001);
print("Listening on localhost:8001");
}

View file

@ -10,12 +10,10 @@ class BcRoutesParser extends Parser<RouteID, Route> {
final tripsFile = File(bcDataDir / "trips.txt");
final stopParser = BcStopParser();
static final routesToSkip = ["48", "91"];
Future<Map<RouteID, Route>> getRoutes() async => {
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 {

View file

@ -4,12 +4,10 @@ import "utils.dart";
class OcctRoutesParser extends Parser<RouteID, Route> {
final routesFile = File(occtDataDir / "routes.json");
static final routesToSkip = {5};
@override
Future<Map<RouteID, Route>> parse() async => {
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 routes = await getRoutes();
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 routeID in routes[stopID]!) {
if (routesToSkip.contains(routeID)) continue;

View file

@ -32,7 +32,7 @@ Iterable<StopState>? findPath(Coordinates start, Coordinates end) {
endPoint: end,
);
log("Finding a route using ${state.hash()}");
final result = aStar(state, limit: 2500);
final result = aStar(state, limit: 10000);
if (result == null) continue;
paths.add(result.reconstructPath());
}

View file

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

View file

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