Compare commits
10 commits
6d3011b706
...
23c20cdfd0
Author | SHA1 | Date | |
---|---|---|---|
|
23c20cdfd0 | ||
|
5c2dd0395e | ||
|
9a01b1b443 | ||
|
a5090988d5 | ||
|
2e3813398e | ||
|
eaec35f730 | ||
|
92927589fb | ||
|
9558d02f39 | ||
|
94fda78bd9 | ||
8950751c82 |
24 changed files with 299 additions and 150 deletions
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
|
@ -16,7 +16,8 @@
|
|||
"cwd": "src\\client",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"args": ["-d", "chrome", "--dart-define-from-file=.env"],
|
||||
"deviceId": "chrome",
|
||||
"args": ["--dart-define-from-file=.env"],
|
||||
"program": "lib/main.dart",
|
||||
},
|
||||
]
|
||||
|
|
41
LICENSE
Normal file
41
LICENSE
Normal file
|
@ -0,0 +1,41 @@
|
|||
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.
|
57
README.md
57
README.md
|
@ -1,47 +1,60 @@
|
|||
[](https://classroom.github.com/a/vfKrPwQS)
|
||||
# << Project Title >>
|
||||
|
||||
# Binghamton Better Bus (BBB) v2
|
||||
## CS 445 Final Project
|
||||
### Spring, 2025
|
||||
|
||||
### Team: << team name >>
|
||||
### Team: Team 4
|
||||
|
||||
- Spencer Powell
|
||||
- Levi Lesches
|
||||
|
||||
## Getting Started
|
||||
<<One paragraph of project description goes here>>
|
||||
|
||||
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.
|
||||
|
||||
### 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
|
||||
- [ ] Add Changelog
|
||||
- [ ] Add back to top links
|
||||
- [ ] Add Additional Templates w/ Examples
|
||||
- [ ] Add "components" document to easily copy & paste sections of the readme
|
||||
>>
|
||||
|
||||
|
||||
- [ ] 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,
|
||||
|
||||
## SRS
|
||||
|
||||
[doc](https://docs.google.com/document/d/1kSWMxsK0NakhvHRQnNx0wRlL6wDgYGO-tA7JB_4qldE/edit?usp=sharing)
|
||||
|
||||
### Prerequisites
|
||||
|
||||
* [Docker](https://www.docker.com/)
|
||||
* <<any additional software. Be specific about versions.>>
|
||||
* Docker Compose
|
||||
* [Just](https://github.com/casey/just)
|
||||
|
||||
### Installing
|
||||
<<
|
||||
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
|
||||
>>
|
||||
|
||||
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`
|
||||
|
||||
## Built With
|
||||
<< list all frameworks and modules used here >>
|
||||
* [requests](https://docs.python-requests.org/en/latest/user/quickstart/#make-a-request) - request for humans
|
||||
|
||||
* [Deno](https://deno.com/)
|
||||
- [Acorn](https://oakserver.org/acorn)
|
||||
* [Dart](https://dart.dev/)
|
||||
* [Flutter](https://flutter.dev/)
|
||||
- Google maps
|
||||
* [Caddy](https://caddyserver.com/)
|
||||
|
||||
## License
|
||||
<< Add a [license](https://choosealicense.com/) >>
|
||||
|
||||
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.
|
||||
|
||||
## Acknowledgments
|
||||
* Hat tip to anyone whose code was used
|
||||
* Inspiration
|
||||
* etc
|
||||
|
||||
* 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
|
||||
|
|
BIN
demo.mp4
Normal file
BIN
demo.mp4
Normal file
Binary file not shown.
13
src/Justfile
Normal file
13
src/Justfile
Normal file
|
@ -0,0 +1,13 @@
|
|||
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
|
3
src/client/.gitignore
vendored
3
src/client/.gitignore
vendored
|
@ -45,3 +45,6 @@ app.*.map.json
|
|||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
|
||||
shared-dir
|
||||
root-pubspec.yaml
|
||||
|
|
|
@ -20,11 +20,15 @@ RUN flutter upgrade
|
|||
|
||||
RUN flutter doctor
|
||||
|
||||
COPY ./ /client/
|
||||
COPY root-pubspec.yaml /client/pubspec.yaml
|
||||
COPY ./shared-dir /client/shared
|
||||
COPY ./ /client/client
|
||||
|
||||
WORKDIR /client/client
|
||||
|
||||
RUN flutter build web --dart-define-from-file=.env
|
||||
|
||||
WORKDIR /client/build/web
|
||||
WORKDIR /client/client/build/web
|
||||
|
||||
EXPOSE 80/tcp
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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: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("Counter")),
|
||||
appBar: AppBar(title: const Text("Bing Maps")),
|
||||
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,29 +52,59 @@ class HomePage extends ReactiveWidget<HomeModel> {
|
|||
const Divider(),
|
||||
const SizedBox(height: 8),
|
||||
if (model.isLoading)
|
||||
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}",
|
||||
),
|
||||
const LinearProgressIndicator(),
|
||||
if (model.errorText != null)
|
||||
Text(model.errorText!),
|
||||
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,
|
||||
onTap: model.onMapTapped,
|
||||
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,
|
||||
),
|
||||
},
|
||||
polylines: {
|
||||
for (final (index, route) in model.paths.indexed) Polyline(
|
||||
for (final (index, route) in model.pathStops.indexed) Polyline(
|
||||
polylineId: PolylineId(index.toString()),
|
||||
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)];
|
||||
|
|
|
@ -2,7 +2,7 @@ import "dart:async";
|
|||
|
||||
import "package:client/data.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:shared/graph.dart";
|
||||
|
@ -10,6 +10,8 @@ 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();
|
||||
|
@ -17,7 +19,6 @@ class HomeModel extends ViewModel with HomeMarkers {
|
|||
final endLatitudeController = TextEditingController();
|
||||
final endLongitudeController = TextEditingController();
|
||||
|
||||
Path? path;
|
||||
GoogleMapController? mapController;
|
||||
|
||||
double? get startLatitude => double.tryParse(startLatitudeController.text);
|
||||
|
@ -66,8 +67,8 @@ class HomeModel extends ViewModel with HomeMarkers {
|
|||
super.updateEnd(coordinates, marker);
|
||||
}
|
||||
|
||||
String? pathText;
|
||||
List<Coordinates> pathWaypoint = [];
|
||||
List<String>? pathText;
|
||||
List<PathStep> pathWaypoint = [];
|
||||
List<List<Coordinates>> pathWalking = [];
|
||||
List<List<Coordinates>> pathStops = [];
|
||||
|
||||
|
@ -87,10 +88,11 @@ 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 => "Go one stop to $stop",
|
||||
SearchMethod.bus => "Ride the bus to $stop",
|
||||
SearchMethod.start => "Walk to $stop\n and board the $route line",
|
||||
SearchMethod.transfer => "Transfer to the $route line",
|
||||
SearchMethod.transfer => "Transfer at $stop\n to the $route line",
|
||||
SearchMethod.walk => "Walk to $stop\n and board the $route line"
|
||||
};
|
||||
}
|
||||
|
@ -99,6 +101,7 @@ 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;
|
||||
|
@ -108,6 +111,9 @@ 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();
|
||||
|
@ -118,25 +124,32 @@ class HomeModel extends ViewModel with HomeMarkers {
|
|||
switch (step.method) {
|
||||
case SearchMethod.start:
|
||||
pathWalking.add([start, position]);
|
||||
pathWaypoint.add(position);
|
||||
pathWaypoint.add( (stop, explainStep(step)) );
|
||||
pathStops.add([position]);
|
||||
case SearchMethod.bus:
|
||||
pathStops.last.add(position);
|
||||
case SearchMethod.transfer:
|
||||
pathWaypoint.add(position);
|
||||
pathStops.add([]);
|
||||
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(stop.coordinates);
|
||||
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]);
|
||||
}
|
||||
}
|
||||
final buffer = StringBuffer();
|
||||
explainPath(path, buffer.writeln);
|
||||
pathText = buffer.toString();
|
||||
pathText = [];
|
||||
explainPath(path, pathText!.add);
|
||||
isSearching = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
|
|
@ -34,10 +34,18 @@ class Sidebar extends ReusableReactiveWidget<HomeModel> {
|
|||
Tab(text: provider),
|
||||
],
|
||||
),
|
||||
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: 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) Expanded(
|
||||
child: TabBarView(
|
||||
children: [
|
||||
|
|
|
@ -6,12 +6,17 @@ 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:
|
||||
|
@ -19,29 +24,30 @@ 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:
|
||||
|
|
|
@ -3,4 +3,7 @@
|
|||
handle_path /api/* {
|
||||
reverse_proxy server:80
|
||||
}
|
||||
handle_path /tmp-api/* {
|
||||
reverse_proxy hack:8001
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12055,7 +12055,6 @@
|
|||
"Westside Inbound",
|
||||
"Downtown Center Leroy Outbound",
|
||||
"Downtown Center Leroy Inbound",
|
||||
"UCLUB",
|
||||
"Campus Shuttle",
|
||||
"Riviera Ridge - Town Square Mall",
|
||||
"Oakdale Commons",
|
||||
|
@ -12070,7 +12069,6 @@
|
|||
"OCCT_2",
|
||||
"OCCT_3",
|
||||
"OCCT_4",
|
||||
"OCCT_5",
|
||||
"OCCT_8",
|
||||
"OCCT_9",
|
||||
"OCCT_10",
|
||||
|
@ -12385,7 +12383,6 @@
|
|||
"routes": [
|
||||
"Westside Inbound",
|
||||
"Downtown Center Leroy Inbound",
|
||||
"UCLUB",
|
||||
"Campus Shuttle",
|
||||
"Riviera Ridge - Town Square Mall",
|
||||
"Oakdale Commons",
|
||||
|
@ -12396,7 +12393,6 @@
|
|||
"route_ids": [
|
||||
"OCCT_2",
|
||||
"OCCT_4",
|
||||
"OCCT_5",
|
||||
"OCCT_8",
|
||||
"OCCT_9",
|
||||
"OCCT_10",
|
||||
|
@ -12721,12 +12717,10 @@
|
|||
"longitude": -75.9657592773438,
|
||||
"provider": "OCCT",
|
||||
"routes": [
|
||||
"UCLUB",
|
||||
"Campus Shuttle",
|
||||
"ITC - UCLUB"
|
||||
],
|
||||
"route_ids": [
|
||||
"OCCT_5",
|
||||
"OCCT_8",
|
||||
"OCCT_16"
|
||||
]
|
||||
|
@ -12739,12 +12733,10 @@
|
|||
"longitude": -75.9673309326172,
|
||||
"provider": "OCCT",
|
||||
"routes": [
|
||||
"UCLUB",
|
||||
"Campus Shuttle",
|
||||
"ITC - UCLUB"
|
||||
],
|
||||
"route_ids": [
|
||||
"OCCT_5",
|
||||
"OCCT_8",
|
||||
"OCCT_16"
|
||||
]
|
||||
|
@ -12757,11 +12749,9 @@
|
|||
"longitude": -75.9564208984375,
|
||||
"provider": "OCCT",
|
||||
"routes": [
|
||||
"UCLUB",
|
||||
"ITC - UCLUB"
|
||||
],
|
||||
"route_ids": [
|
||||
"OCCT_5",
|
||||
"OCCT_16"
|
||||
]
|
||||
},
|
||||
|
@ -12773,11 +12763,9 @@
|
|||
"longitude": -75.9558868408203,
|
||||
"provider": "OCCT",
|
||||
"routes": [
|
||||
"UCLUB",
|
||||
"ITC - UCLUB"
|
||||
],
|
||||
"route_ids": [
|
||||
"OCCT_5",
|
||||
"OCCT_16"
|
||||
]
|
||||
},
|
||||
|
@ -12789,12 +12777,10 @@
|
|||
"longitude": -75.9628753662109,
|
||||
"provider": "OCCT",
|
||||
"routes": [
|
||||
"UCLUB",
|
||||
"Campus Shuttle",
|
||||
"ITC - UCLUB"
|
||||
],
|
||||
"route_ids": [
|
||||
"OCCT_5",
|
||||
"OCCT_8",
|
||||
"OCCT_16"
|
||||
]
|
||||
|
@ -12807,11 +12793,9 @@
|
|||
"longitude": -75.959098815918,
|
||||
"provider": "OCCT",
|
||||
"routes": [
|
||||
"UCLUB",
|
||||
"ITC - UCLUB"
|
||||
],
|
||||
"route_ids": [
|
||||
"OCCT_5",
|
||||
"OCCT_16"
|
||||
]
|
||||
},
|
||||
|
@ -12823,11 +12807,9 @@
|
|||
"longitude": -75.9538421630859,
|
||||
"provider": "OCCT",
|
||||
"routes": [
|
||||
"UCLUB",
|
||||
"ITC - UCLUB"
|
||||
],
|
||||
"route_ids": [
|
||||
"OCCT_5",
|
||||
"OCCT_16"
|
||||
]
|
||||
},
|
||||
|
@ -12839,11 +12821,9 @@
|
|||
"longitude": -75.9595413208008,
|
||||
"provider": "OCCT",
|
||||
"routes": [
|
||||
"UCLUB",
|
||||
"ITC - UCLUB"
|
||||
],
|
||||
"route_ids": [
|
||||
"OCCT_5",
|
||||
"OCCT_16"
|
||||
]
|
||||
},
|
||||
|
|
|
@ -1242,24 +1242,6 @@
|
|||
"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",
|
||||
|
|
|
@ -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);
|
||||
|
|
3
src/shared/.gitignore
vendored
3
src/shared/.gitignore
vendored
|
@ -2,5 +2,8 @@
|
|||
# Created by `dart pub`
|
||||
.dart_tool/
|
||||
pubspec.lock
|
||||
client-dir
|
||||
server-data
|
||||
root-pubspec.yaml
|
||||
path.log
|
||||
notes.md
|
||||
|
|
42
src/shared/Dockerfile
Normal file
42
src/shared/Dockerfile
Normal file
|
@ -0,0 +1,42 @@
|
|||
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"]
|
|
@ -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, "localhost", 8001);
|
||||
print("Listening on localhost:8001");
|
||||
await io.serve(router.call, "0.0.0.0", 8001);
|
||||
print("Listening on 0.0.0.0:8001");
|
||||
}
|
||||
|
|
|
@ -10,10 +10,12 @@ 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))
|
||||
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 {
|
||||
|
|
|
@ -4,10 +4,12 @@ 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))
|
||||
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 routes = await getRoutes();
|
||||
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 routeID in routes[stopID]!) {
|
||||
if (routesToSkip.contains(routeID)) continue;
|
||||
|
|
|
@ -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: 10000);
|
||||
final result = aStar(state, limit: 2500);
|
||||
if (result == null) continue;
|
||||
paths.add(result.reconstructPath());
|
||||
}
|
||||
|
|
|
@ -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 = 5;
|
||||
const numStops = 3;
|
||||
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 "${method.name}-$stop-$goal-$route";
|
||||
return "$distanceWalked-${method.name}-$stop-$goal-$route";
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -89,7 +89,8 @@ class StopState extends AStarState<StopState> with Encodable {
|
|||
|
||||
@override
|
||||
Iterable<StopState> expand() {
|
||||
final route = routes[routeID]!;
|
||||
final route = routes[routeID];
|
||||
if (route == null) return [];
|
||||
final stop = stops[stopID]!;
|
||||
final stopIndex = route.stops.indexOf(stopID);
|
||||
if (stopIndex == -1) return [];
|
||||
|
@ -99,11 +100,13 @@ 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 + 1,
|
||||
depth: depth + distanceDriven,
|
||||
distanceWalked: distanceWalked,
|
||||
method: SearchMethod.bus,
|
||||
);
|
||||
|
@ -112,13 +115,15 @@ 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 + 1,
|
||||
depth: depth + distanceDriven,
|
||||
distanceWalked: distanceWalked,
|
||||
method: SearchMethod.bus,
|
||||
);
|
||||
|
@ -131,7 +136,7 @@ class StopState extends AStarState<StopState> with Encodable {
|
|||
stopID: stopID,
|
||||
goalID: goalID,
|
||||
routeID: otherRouteID,
|
||||
depth: depth + 2,
|
||||
depth: depth + 5,
|
||||
distanceWalked: distanceWalked,
|
||||
method: SearchMethod.transfer,
|
||||
);
|
||||
|
@ -146,7 +151,7 @@ class StopState extends AStarState<StopState> with Encodable {
|
|||
stopID: otherStop.id,
|
||||
goalID: goalID,
|
||||
routeID: otherRouteID,
|
||||
depth: depth + 3,
|
||||
depth: depth + 5 + 10*distanceToOther,
|
||||
distanceWalked: distanceWalked + distanceToOther,
|
||||
method: SearchMethod.walk,
|
||||
);
|
||||
|
@ -160,6 +165,7 @@ class StopState extends AStarState<StopState> with Encodable {
|
|||
@override
|
||||
Json toJson() => {
|
||||
"stop_id": stopID,
|
||||
"goal_id": goalID,
|
||||
"route_id": routeID,
|
||||
"method": method.name,
|
||||
};
|
||||
|
|
|
@ -14,10 +14,7 @@ Response getPath(Request request) {
|
|||
|
||||
final start = (lat: startLat, long: startLong);
|
||||
final end = (lat: endLat, long: endLong);
|
||||
final path = findPath(start, end);
|
||||
if (path == null) {
|
||||
return Response.ok("No routes found", headers: corsHeaders);
|
||||
}
|
||||
final path = findPath(start, end) ?? [];
|
||||
final jsonBody = encodeJsonList(path);
|
||||
return Response.ok(jsonBody, headers: corsHeaders);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue