diff --git a/Public/routes-data/fall22-event.gpx b/Public/routes-data/fall22-event.gpx index 592410d..a42aeb8 100644 --- a/Public/routes-data/fall22-event.gpx +++ b/Public/routes-data/fall22-event.gpx @@ -13,48 +13,63 @@ Student Union + No Request Academy Hall + Request Only Tibbits Avenue + Request Only Polytechnic Residence Commons + No Request City Station + No Request Blitman Residence Commons + No Request Heffner Alumni House + No Request Barton Hall + No Request B-Lot + Request Only E-Lot + No Request Stacwyck + No Request Bryckwyck + No Request RAHP-B + No Request Georgian Court + Request Only Colonie + No Request Special Event West Route diff --git a/Public/routes-data/fall22.gpx b/Public/routes-data/fall22.gpx index 6954dac..999cb2c 100644 --- a/Public/routes-data/fall22.gpx +++ b/Public/routes-data/fall22.gpx @@ -13,51 +13,67 @@ Student Union + No Request Academy Hall + Request Only Tibbits Avenue + Request Only Polytechnic Residence Commons + No Request City Station + No Request Blitman Residence Commons + No Request West Hall + No Request ’87 Gymnasium + No Request Barton Hall + Request Only B-Lot + Request Only E-Lot + No Request Stacwyck + No Request Bryckwyck + No Request RAHP-B + No Request Georgian Court + Request Only Colonie + No Request West Route diff --git a/Public/routes-data/fall23.gpx b/Public/routes-data/fall23.gpx index 8a0d4b7..9e1f973 100644 --- a/Public/routes-data/fall23.gpx +++ b/Public/routes-data/fall23.gpx @@ -13,45 +13,59 @@ Student Union + No Request Academy Hall + Request Only Tibbits Avenue + Request Only Polytechnic Residence Commons + No Request City Station + No Request Blitman Residence Commons + No Request West Hall + No Request ’87 Gymnasium + No Request Barton Hall + Request Only B-Lot + Request Only Stacwyck + No Request Bryckwyck + No Request RAHP-B + No Request Colonie + No Request West Route diff --git a/Public/routes-data/spring22.gpx b/Public/routes-data/spring22.gpx index c2fb5c7..f8e6d27 100644 --- a/Public/routes-data/spring22.gpx +++ b/Public/routes-data/spring22.gpx @@ -13,48 +13,63 @@ Student Union + No Request Academy Hall + Request Only Tibbits Avenue + Request Only Polytechnic Residence Commons + No Request City Station + No Request Blitman Residence Commons + No Request West Hall + No Request ’87 Gymnasium + No Request Barton Hall + Request Only B-Lot + Request Only ECAV Arena + No Request Stacwyck + No Request Bryckwyck + No Request Beman Lane + No Request The Armory + No Request Route diff --git a/Public/routes-data/spring23.gpx b/Public/routes-data/spring23.gpx index a1c3306..65ee78b 100644 --- a/Public/routes-data/spring23.gpx +++ b/Public/routes-data/spring23.gpx @@ -13,51 +13,67 @@ Student Union + No Request Academy Hall + Request Only Tibbits Avenue + Request Only Polytechnic Residence Commons + No Request City Station + No Request Blitman Residence Commons + No Request West Hall + No Request ’87 Gymnasium + No Request Barton Hall + Request Only B-Lot + Request Only E-Lot + Request Only Stacwyck + No Request Bryckwyck + No Request RAHP-B + No Request Georgian Court + Request Only Colonie + No Request West Route diff --git a/Public/routes-data/summer22-weekday-testing.gpx b/Public/routes-data/summer22-weekday-testing.gpx index a47febc..330e532 100644 --- a/Public/routes-data/summer22-weekday-testing.gpx +++ b/Public/routes-data/summer22-weekday-testing.gpx @@ -13,24 +13,31 @@ Student Union + No Request Academy Hall + Request Only West Hall + No Request ’87 Gymnasium + No Request Barton Hall + Request Only East Campus Athletic Village + No Request Burdett Avenue + No Request Weekday Testing Route diff --git a/Public/routes-data/summer22-weekday.gpx b/Public/routes-data/summer22-weekday.gpx index 2ad5093..cd406a7 100644 --- a/Public/routes-data/summer22-weekday.gpx +++ b/Public/routes-data/summer22-weekday.gpx @@ -13,21 +13,27 @@ Student Union + No Request Academy Hall + Request Only West Hall + No Request ’87 Gymnasium + No Request Barton Hall + Request Only Burdett Avenue + No Request Weekday Route diff --git a/Public/routes-data/summer22-weekend.gpx b/Public/routes-data/summer22-weekend.gpx index b1ec5a9..3cb186a 100644 --- a/Public/routes-data/summer22-weekend.gpx +++ b/Public/routes-data/summer22-weekend.gpx @@ -13,24 +13,31 @@ Student Union + No Request Academy Hall + Request Only Blitman Residence Commons + No Request West Hall + No Request ’87 Gymnasium + No Request Barton Hall + Request Only Burdett Avenue + No Request Weekend Route diff --git a/Sources/App/Jobs/GPXImportingJob.swift b/Sources/App/Jobs/GPXImportingJob.swift index 67b2353..5f312cb 100644 --- a/Sources/App/Jobs/GPXImportingJob.swift +++ b/Sources/App/Jobs/GPXImportingJob.swift @@ -55,18 +55,21 @@ struct GPXImportingJob: AsyncScheduledJob { errorPrint("Couldn’t parse GPX file “\(routesFileURL.lastPathComponent)”") continue } + var allRoutes: Set = [] for gpxRoute in gpx.routes { do { - try await Route(from: gpxRoute, schedule: schedule) + let route = Route(from: gpxRoute, schedule: schedule) + allRoutes.insert(route) + try await route .save(on: context.application.db(.sqlite)) - for gpxWaypoint in gpx.waypoints { - try await Stop(from: gpxWaypoint, withSchedule: schedule)! - .save(on: context.application.db(.sqlite)) - } } catch { errorPrint("Couldn’t import GPX route from file “\(routesFileURL.lastPathComponent)”: \(error)") } } + for gpxWaypoint in gpx.waypoints { + try await Stop(from: gpxWaypoint, withSchedule: schedule, selectingRoutesFrom: allRoutes)! + .save(on: context.application.db(.sqlite)) + } } } diff --git a/Sources/App/Migrations/CreateStops.swift b/Sources/App/Migrations/CreateStops.swift index b24f647..0f4d2a3 100644 --- a/Sources/App/Migrations/CreateStops.swift +++ b/Sources/App/Migrations/CreateStops.swift @@ -17,6 +17,8 @@ struct CreateStops: AsyncMigration { .field("name", .string, .required) .field("coordinate", .dictionary, .required) .field("schedule", .dictionary, .required) + .field("is_by_request", .string, .required) + .field("route_id", .array(of: .uuid), .required) .create() } diff --git a/Sources/App/Models/Route.swift b/Sources/App/Models/Route.swift index 2f79dee..33fafc2 100644 --- a/Sources/App/Models/Route.swift +++ b/Sources/App/Models/Route.swift @@ -14,7 +14,7 @@ import Vapor /// A representation of a shuttle route. /// /// A route is represented as a sequence of geospatial coordinates. -final class Route: Model, Content, Collection { +final class Route: Equatable, Hashable, Model, Content, Collection { static let schema = "routes" @@ -118,6 +118,14 @@ final class Route: Model, Content, Collection { return self.coordinates[index] } + func hash(into hasher: inout Hasher) { + hasher.combine(self.id) + } + + static func == (lhs: Route, rhs: Route) -> Bool { + return lhs.id == rhs.id && lhs.name == rhs.name + } + func index(after oldIndex: Int) -> Int { return oldIndex + 1 } @@ -135,5 +143,16 @@ final class Route: Model, Content, Collection { } return distance < Constants.isOnRouteThreshold } + + func checkStopIsOnRoute(Coordinate: Coordinate) -> Bool { + let distance = LineString(self.coordinates) + .closestCoordinate(to: Coordinate)? + .coordinate + .distance(to: Coordinate) + guard let distance else { + return false + } + return distance < Constants.isStopOnRouteThreshold + } } diff --git a/Sources/App/Models/Stop.swift b/Sources/App/Models/Stop.swift index 90c28a0..13329cb 100644 --- a/Sources/App/Models/Stop.swift +++ b/Sources/App/Models/Stop.swift @@ -28,23 +28,40 @@ final class Stop: Equatable, Hashable, Model, Content { @Field(key: "schedule") var schedule: MapSchedule - + + @Field(key: "is_by_request") + var isByRequest: Bool + + @Field(key: "route_id") + var routeID: Set + init() { } /// Creates a stop object from a GPX waypoint. /// - Parameters: /// - gpxWaypoint: The GPX waypoint from which to create a stop object. /// - schedule: The schedule for when the stop will be active. + /// - route: The set of routes to associate each stop object with a routeID /// - Note: This initializer fails and returns `nil` if the provided GPX waypoint doesn’t contain sufficient information to create a stop object. - init?(from gpxWaypoint: any GPXWaypointProtocol, withSchedule schedule: MapSchedule) { - guard let name = gpxWaypoint.name, let coordinate = Coordinate(from: gpxWaypoint) else { + init?(from gpxWaypoint: any GPXWaypointProtocol, withSchedule schedule: MapSchedule, selectingRoutesFrom allRoutes: Set) { + guard let name = gpxWaypoint.name, let coordinate = Coordinate(from: gpxWaypoint), let isByRequest = gpxWaypoint.desc + else { return nil } self.name = name self.coordinate = coordinate self.schedule = schedule + self.isByRequest = (isByRequest == "Request Only") + self.routeID = [] + for route in allRoutes { + if let id = route.id{ + if (route.checkStopIsOnRoute(Coordinate: self.coordinate)) { + self.routeID.insert(id) + } + } + } } - + func hash(into hasher: inout Hasher) { hasher.combine(self.name) // Hashing the ID could potentially violate Hashable’s invariants when the ID is determined by the database, so we hash the name instead. This means that stop names must be globally unique, which doesn’t seem to be too far-fetched as an assumption/requirement. } @@ -52,5 +69,4 @@ final class Stop: Equatable, Hashable, Model, Content { static func == (lhs: Stop, rhs: Stop) -> Bool { return lhs.name == rhs.name && lhs.coordinate == rhs.coordinate } - } diff --git a/Sources/App/Utilities.swift b/Sources/App/Utilities.swift index d89428f..e248ac8 100644 --- a/Sources/App/Utilities.swift +++ b/Sources/App/Utilities.swift @@ -121,6 +121,8 @@ enum Constants { /// The maximum perpendicular distance, in meters, away from a route at which a coordinate is considered to be “on” that route. static let isOnRouteThreshold: Double = 5 + static let isStopOnRouteThreshold: Double = 20 + static let apnsTopic = "com.gerzer.shuttletracker" }