@transportme/load-ptv-gtfs 1.4.2 → 1.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/gtfs-parser/GTFSRoute.mjs +22 -4
- package/lib/gtfs-parser/GTFSTrip.mjs +23 -1
- package/lib/gtfs-parser/readers/GTFSCalendarReader.mjs +1 -0
- package/lib/gtfs-parser/readers/line-reader.mjs +1 -6
- package/lib/loader/RouteLoader.mjs +4 -2
- package/lib/loader/ShapeLoader.mjs +3 -2
- package/lib/loader/StopsLoader.mjs +4 -2
- package/lib/loader/TripLoader.mjs +13 -4
- package/lib/post-processing/set-route-stops.mjs +12 -3
- package/package.json +1 -1
- package/.github/workflows/unit-test.yml +0 -46
- package/test/gtfs-calendar.test.mjs +0 -182
- package/test/gtfs-route-loader.test.mjs +0 -208
- package/test/gtfs-routes.test.mjs +0 -77
- package/test/gtfs-shapes-loader.test.mjs +0 -105
- package/test/gtfs-shapes.test.mjs +0 -23
- package/test/gtfs-stop-loader.test.mjs +0 -415
- package/test/gtfs-stop-times.test.mjs +0 -37
- package/test/gtfs-stops.test.mjs +0 -311
- package/test/gtfs-trip-loader.test.mjs +0 -188
- package/test/gtfs-trips.test.mjs +0 -108
- package/test/line-reader.test.mjs +0 -120
- package/test/merge-stops-full.test.mjs +0 -118
- package/test/merge-stops.test.mjs +0 -376
- package/test/new-metro-loader-test.test.mjs +0 -244
- package/test/new-tram-format.test.mjs +0 -49
- package/test/newer-metro-loader-test.test.mjs +0 -80
- package/test/newer-tram-format.test.mjs +0 -67
- package/test/ptv-gtfs.test.mjs +0 -106
- package/test/sample-data/calendar/calendar.txt +0 -34
- package/test/sample-data/calendar/calendar_dates.txt +0 -26
- package/test/sample-data/gtfs-splitting/force_unique_stops_bus.txt +0 -7
- package/test/sample-data/gtfs-splitting/force_unique_stops_tram.txt +0 -3
- package/test/sample-data/gtfs-splitting/general-stores.txt +0 -19
- package/test/sample-data/gtfs-splitting/line_reader_test.txt +0 -8
- package/test/sample-data/gtfs-splitting/name_overrides.txt +0 -4
- package/test/sample-data/gtfs-splitting/sample_stops.txt +0 -20
- package/test/sample-data/gtfs-splitting/special_chars_stops_csv.txt +0 -6
- package/test/sample-data/gtfs-splitting/stops_without_secondary.txt +0 -6
- package/test/sample-data/gtfs-splitting/union_with_spaces.txt +0 -5
- package/test/sample-data/gtfs-unzipping/gtfs.zip +0 -0
- package/test/sample-data/merge/shapes.txt +0 -1224
- package/test/sample-data/merge/stop_times.txt +0 -103
- package/test/sample-data/merge/stops.txt +0 -39
- package/test/sample-data/merge/trips.txt +0 -7
- package/test/sample-data/mtm-rail/stops.txt +0 -4
- package/test/sample-data/new-metro/calendar.txt +0 -13
- package/test/sample-data/new-metro/calendar_dates.txt +0 -1
- package/test/sample-data/new-metro/routes.txt +0 -18
- package/test/sample-data/new-metro/simple_stop_test.txt +0 -6
- package/test/sample-data/new-metro/stop_times.txt +0 -64
- package/test/sample-data/new-metro/stops.txt +0 -139
- package/test/sample-data/new-metro/trips.txt +0 -6
- package/test/sample-data/new-metro-2/calendar.txt +0 -13
- package/test/sample-data/new-metro-2/calendar_dates.txt +0 -1
- package/test/sample-data/new-metro-2/routes.txt +0 -2
- package/test/sample-data/new-metro-2/stop_times.txt +0 -5
- package/test/sample-data/new-metro-2/stops.txt +0 -9
- package/test/sample-data/new-metro-2/trips.txt +0 -2
- package/test/sample-data/new-tram/routes.txt +0 -25
- package/test/sample-data/new-tram/stops.txt +0 -5
- package/test/sample-data/new-tram-2/routes.txt +0 -25
- package/test/sample-data/new-tram-2/stops.txt +0 -8
- package/test/sample-data/one_line.csv +0 -2
- package/test/sample-data/parent-stop/multi_stop.txt +0 -4
- package/test/sample-data/parent-stop/stops.txt +0 -15
- package/test/sample-data/routes/agency.txt +0 -12
- package/test/sample-data/routes/bad_routes.txt +0 -4
- package/test/sample-data/routes/metro_bus_routes.txt +0 -4
- package/test/sample-data/routes/metro_lines.txt +0 -8
- package/test/sample-data/routes/regional_bus_routes.txt +0 -7
- package/test/sample-data/routes/vline.txt +0 -7
- package/test/sample-data/routes/west_gipp_transit.txt +0 -20
- package/test/sample-data/shapes/shapes.txt +0 -89
- package/test/sample-data/shapes/stop_times.txt +0 -9
- package/test/sample-data/shapes/stops.txt +0 -4
- package/test/sample-data/shapes/trips.txt +0 -4
- package/test/sample-data/stop-data/name-overrides.json +0 -25
- package/test/sample-data/stop-data/unique-stops.json +0 -1
- package/test/sample-data/suburbs.json +0 -77
- package/test/sample-data/trips/bus_trips.txt +0 -5
- package/test/sample-data/trips/stop_times.txt +0 -29
- package/test/sample-data/trips/stops.txt +0 -93
- package/test/sample-data/trips/trips.txt +0 -5
- package/test/utils-test.mjs +0 -12
|
@@ -23,7 +23,7 @@ export default class GTFSRoute {
|
|
|
23
23
|
this.agencyID = agencyID
|
|
24
24
|
this.routeName = routeName
|
|
25
25
|
|
|
26
|
-
if (routeNumber
|
|
26
|
+
if (!routeNumber || routeNumber.toString().trim().length === 0) routeNumber = null
|
|
27
27
|
this.routeNumber = routeNumber
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -57,7 +57,7 @@ export default class GTFSRoute {
|
|
|
57
57
|
*/
|
|
58
58
|
static create(data, mode) {
|
|
59
59
|
let { routeGTFSID } = data
|
|
60
|
-
let routeTypes = [ RailGTFSRoute, NewGTFSRoute, SmartrakGTFSRoute, GTFSRoute ]
|
|
60
|
+
let routeTypes = [ RailGTFSRoute, NewGTFSRoute, SmartrakGTFSRoute, MTMRailRoute, GTFSRoute ]
|
|
61
61
|
for (let type of routeTypes) {
|
|
62
62
|
if (type.canProcess(routeGTFSID, mode)) return new type(data)
|
|
63
63
|
}
|
|
@@ -78,7 +78,7 @@ class SmartrakGTFSRoute extends GTFSRoute {
|
|
|
78
78
|
parseRouteID(routeGTFSID) {
|
|
79
79
|
let [ _, routeNumber ] = routeGTFSID.match(/^\d+\-(\w+)/)
|
|
80
80
|
return `4-${routeNumber}`
|
|
81
|
-
}
|
|
81
|
+
}
|
|
82
82
|
|
|
83
83
|
static canProcess(routeGTFSID, mode) {
|
|
84
84
|
return routeGTFSID.includes('-aus-')
|
|
@@ -100,7 +100,7 @@ class NewGTFSRoute extends GTFSRoute {
|
|
|
100
100
|
parseRouteID(routeGTFSID) {
|
|
101
101
|
let [ _, modeID, routeNumber ] = routeGTFSID.match(/vic-0*(\d+)\-(\w+)/)
|
|
102
102
|
return `${modeID}-${routeNumber}`
|
|
103
|
-
}
|
|
103
|
+
}
|
|
104
104
|
|
|
105
105
|
}
|
|
106
106
|
|
|
@@ -122,3 +122,21 @@ class RailGTFSRoute extends NewGTFSRoute {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
}
|
|
125
|
+
|
|
126
|
+
class MTMRailRoute extends GTFSRoute {
|
|
127
|
+
|
|
128
|
+
constructor(data) {
|
|
129
|
+
super(data)
|
|
130
|
+
this.routeName = 'Rail Replacement Bus'
|
|
131
|
+
this.routeNumber = null
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
static canProcess(routeGTFSID, mode) {
|
|
135
|
+
return !routeGTFSID.match(/^\d+-\w{1,3}/) && mode === 'metro train'
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
parseRouteID(routeGTFSID) {
|
|
139
|
+
return '2-RRB'
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
}
|
|
@@ -48,7 +48,7 @@ export default class GTFSTrip {
|
|
|
48
48
|
* @returns {GTFSTrip} A trip instance representing the data being passed in
|
|
49
49
|
*/
|
|
50
50
|
static create(data, mode) {
|
|
51
|
-
let types = [ SmartrakTrip, MetroTrip, GTFSTrip ]
|
|
51
|
+
let types = [ SmartrakTrip, MetroTrip, MTMRailTrip, GTFSTrip ]
|
|
52
52
|
for (let type of types) {
|
|
53
53
|
if (type.canProcess(data, mode)) return new type(data)
|
|
54
54
|
}
|
|
@@ -189,4 +189,26 @@ export class MetroRRBTrip extends MetroTrip {
|
|
|
189
189
|
return super.canProcess(data, mode) && data.id.match(/^02-\w{3}-R-/)
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export class MTMRailTrip extends GTFSTrip {
|
|
195
|
+
|
|
196
|
+
constructor(data) {
|
|
197
|
+
if (data.headsign && data.headsign.length === 6 && data.headsign.match(/[A-Z]{3}\d{3}/)) {
|
|
198
|
+
data.block = data.headsign
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
super(data)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
static canProcess(data, mode) {
|
|
205
|
+
return mode === TRANSIT_MODES.metroTrain && data.routeGTFSID === '2-RRB'
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
getTripData() {
|
|
209
|
+
return {
|
|
210
|
+
...super.getTripData(),
|
|
211
|
+
isRailReplacementBus: true
|
|
212
|
+
}
|
|
213
|
+
}
|
|
192
214
|
}
|
|
@@ -21,6 +21,7 @@ export default class GTFSCalendarReader extends GTFSReader {
|
|
|
21
21
|
async open() {
|
|
22
22
|
await super.open()
|
|
23
23
|
|
|
24
|
+
if (!this.#calendarDateFile) return
|
|
24
25
|
let calendarDateReader = new GTFSCalendarDateReader(this.#calendarDateFile)
|
|
25
26
|
await calendarDateReader.open()
|
|
26
27
|
while (calendarDateReader.available()) {
|
|
@@ -58,12 +58,7 @@ export default class CSVLineReader {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
#setHeaders(line) {
|
|
61
|
-
let parts
|
|
62
|
-
if (line.includes('"')) {
|
|
63
|
-
parts = line.match(/"([^"]*)"/g).map(f => f.slice(1, -1))
|
|
64
|
-
} else {
|
|
65
|
-
parts = line.split(',')
|
|
66
|
-
}
|
|
61
|
+
let parts = [ ...line.matchAll(/(?:,"|^")(""|[\w\W]*?)(?=",|"$)|(?:,(?!")|^(?!"))([^,]*?)(?=$|,)|(\r\n|\n)/gm) ].map(match => match[1] || match[2])
|
|
67
62
|
|
|
68
63
|
return this.#headers.reduce((acc, header, i) => {
|
|
69
64
|
acc[header] = parts[i]
|
|
@@ -21,10 +21,12 @@ export default class RouteLoader {
|
|
|
21
21
|
*
|
|
22
22
|
* @param {string} routesFile The path for the routes.txt file
|
|
23
23
|
* @param {string} agencyFile The path for the agency.txt file
|
|
24
|
+
* @param {string} mode The route's mode
|
|
24
25
|
* @param {DatabaseConnection} database A database connection
|
|
26
|
+
* @param {string} collectionName The collection to store the routes in. Defaults to gtfs-routes
|
|
25
27
|
*/
|
|
26
|
-
constructor(routesFile, agencyFile, mode, database) {
|
|
27
|
-
this.#routes = database.getCollection(
|
|
28
|
+
constructor(routesFile, agencyFile, mode, database, collectionName = 'gtfs-routes') {
|
|
29
|
+
this.#routes = database.getCollection(collectionName)
|
|
28
30
|
this.#mode = mode
|
|
29
31
|
this.#routesReader = new GTFSRouteReader(routesFile, mode)
|
|
30
32
|
this.#agencyFile = agencyFile
|
|
@@ -13,9 +13,10 @@ export default class ShapeLoader {
|
|
|
13
13
|
*
|
|
14
14
|
* @param {string} shapeFile The path for the shapes.txt file
|
|
15
15
|
* @param {DatabaseConnection} database A database connection
|
|
16
|
+
* @param {string} collectionName The collection to store the routes in. Defaults to gtfs-routes
|
|
16
17
|
*/
|
|
17
|
-
constructor(shapeFile, database) {
|
|
18
|
-
this.#routes = database.getCollection(
|
|
18
|
+
constructor(shapeFile, database, collectionName = 'gtfs-routes') {
|
|
19
|
+
this.#routes = database.getCollection(collectionName)
|
|
19
20
|
this.#shapeReader = new GTFSShapeReader(shapeFile)
|
|
20
21
|
}
|
|
21
22
|
|
|
@@ -20,9 +20,11 @@ export default class StopsLoader {
|
|
|
20
20
|
* @param {string} mode The transit mode being loaded
|
|
21
21
|
* @param {DatabaseConnection} database A database connection
|
|
22
22
|
* @param {function} [suburbHook] A hook that returns the stop's suburb, or null if unavailable. This function is called before processing from the stop's name or location
|
|
23
|
+
* @param {string} collectionName The collection to store the stops in. Defaults to gtfs-stops
|
|
24
|
+
*
|
|
23
25
|
*/
|
|
24
|
-
constructor(stopsFile, suburbs, mode, database, suburbHook) {
|
|
25
|
-
this.#stops = database.getCollection(
|
|
26
|
+
constructor(stopsFile, suburbs, mode, database, suburbHook, collectionName = 'gtfs-stops') {
|
|
27
|
+
this.#stops = database.getCollection(collectionName)
|
|
26
28
|
this.#mode = mode
|
|
27
29
|
this.#stopsReader = new GTFSStopsReader(stopsFile, suburbs, suburbHook)
|
|
28
30
|
}
|
|
@@ -42,11 +42,19 @@ export default class TripLoader {
|
|
|
42
42
|
* @param {string} paths.calendarDatesFile The path for the calendar_dates.txt file
|
|
43
43
|
* @param {string} mode The transit mode being loaded
|
|
44
44
|
* @param {DatabaseConnection} database A database connection
|
|
45
|
+
* @param {Object} colls Used to specify the collection names to write into
|
|
46
|
+
* @param {string} colls.routeColl The collection to store the routes in. Defaults to gtfs-routes
|
|
47
|
+
* @param {string} colls.stopColl The collection to store the stops in. Defaults to gtfs-stops
|
|
48
|
+
* @param {string} colls.timetableColl The collection to store the timetables in. Defaults to gtfs-gtfs timetables
|
|
45
49
|
*/
|
|
46
|
-
constructor({ tripsFile, stopTimesFile, calendarFile, calendarDatesFile }, mode, database
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
constructor({ tripsFile, stopTimesFile, calendarFile, calendarDatesFile }, mode, database, {
|
|
51
|
+
routeColl = 'gtfs-routes',
|
|
52
|
+
stopColl = 'gtfs-stops',
|
|
53
|
+
timetableColl = 'gtfs-gtfs timetables'
|
|
54
|
+
} = {}) {
|
|
55
|
+
this.#routes = database.getCollection(routeColl)
|
|
56
|
+
this.#stops = database.getCollection(stopColl)
|
|
57
|
+
this.#trips = database.getCollection(timetableColl)
|
|
50
58
|
|
|
51
59
|
this.#mode = mode
|
|
52
60
|
|
|
@@ -105,6 +113,7 @@ export default class TripLoader {
|
|
|
105
113
|
|
|
106
114
|
let stopTimings = await async.map(stops, async stop => {
|
|
107
115
|
let stopData = await this.findMatchingStop(stop.stopID)
|
|
116
|
+
if (!stopData) throw new Error(`Failed to match stop ${stop.stopID} in trip ${tripData.getTripID()}`)
|
|
108
117
|
let bay = stopData.bays.find(bay => bay.stopGTFSID === stop.stopID)
|
|
109
118
|
|
|
110
119
|
let data = {
|
|
@@ -7,10 +7,19 @@ import { TRANSIT_MODES } from '../constants.mjs'
|
|
|
7
7
|
* Loads route stop data into the database. This is generated based off the timetable data.
|
|
8
8
|
*
|
|
9
9
|
* @param {DatabaseConnection} database The database connection to use
|
|
10
|
+
* @param {Object} directionsMap Used to store PTV API direction data
|
|
11
|
+
* @param {function} onMilestone Called for every 25 routes processed
|
|
12
|
+
* @param {Object} colls Used to specify the collection names to write into
|
|
13
|
+
* @param {string} colls.routeColl The collection to store the routes in. Defaults to gtfs-routes
|
|
14
|
+
* @param {string} colls.stopColl The collection to store the stops in. Defaults to gtfs-stops
|
|
15
|
+
* @param {string} colls.timetableColl The collection to store the timetables in. Defaults to gtfs-gtfs timetables
|
|
10
16
|
*/
|
|
11
|
-
export default async function setRouteStops(database, directionsMap, onMilestone = () => {}
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
export default async function setRouteStops(database, directionsMap, onMilestone = () => {}, {
|
|
18
|
+
routeColl = 'gtfs-routes',
|
|
19
|
+
timetableColl = 'gtfs-gtfs timetables'
|
|
20
|
+
} = {}) {
|
|
21
|
+
let gtfsTimetables = database.getCollection(timetableColl)
|
|
22
|
+
let routes = database.getCollection(routeColl)
|
|
14
23
|
|
|
15
24
|
let allRoutes = await routes.distinct('routeGTFSID')
|
|
16
25
|
|
package/package.json
CHANGED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
|
2
|
-
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
|
|
3
|
-
|
|
4
|
-
name: Node.js CI
|
|
5
|
-
|
|
6
|
-
on:
|
|
7
|
-
push:
|
|
8
|
-
branches: [ "master" ]
|
|
9
|
-
pull_request:
|
|
10
|
-
branches: [ "master" ]
|
|
11
|
-
|
|
12
|
-
jobs:
|
|
13
|
-
build:
|
|
14
|
-
|
|
15
|
-
runs-on: ubuntu-latest
|
|
16
|
-
|
|
17
|
-
steps:
|
|
18
|
-
- uses: actions/checkout@v4
|
|
19
|
-
with:
|
|
20
|
-
submodules: recursive
|
|
21
|
-
|
|
22
|
-
- name: Cache node modules
|
|
23
|
-
id: cache-npm
|
|
24
|
-
uses: actions/cache@v4
|
|
25
|
-
env:
|
|
26
|
-
cache-name: cache-node-modules
|
|
27
|
-
with:
|
|
28
|
-
# npm cache files are stored in `~/.npm` on Linux/macOS
|
|
29
|
-
path: ~/.npm
|
|
30
|
-
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
|
31
|
-
restore-keys: |
|
|
32
|
-
${{ runner.os }}-build-${{ env.cache-name }}-
|
|
33
|
-
${{ runner.os }}-build-
|
|
34
|
-
${{ runner.os }}-
|
|
35
|
-
|
|
36
|
-
- name: Use Node.js 20.x
|
|
37
|
-
uses: actions/setup-node@v4
|
|
38
|
-
with:
|
|
39
|
-
node-version: '20.x'
|
|
40
|
-
cache: 'npm'
|
|
41
|
-
|
|
42
|
-
- name: Install dependencies
|
|
43
|
-
run: npm install
|
|
44
|
-
|
|
45
|
-
- name: Run test
|
|
46
|
-
run: npm test
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai'
|
|
2
|
-
import GTFSCalendarReader from '../lib/gtfs-parser/readers/GTFSCalendarReader.mjs'
|
|
3
|
-
import GTFSCalendarDateReader from '../lib/gtfs-parser/readers/GTFSCalendarDatesReader.mjs'
|
|
4
|
-
import GTFSCalendar from '../lib/gtfs-parser/GTFSCalendar.mjs'
|
|
5
|
-
import GTFSCalendarDate from '../lib/gtfs-parser/GTFSCalendarDate.mjs'
|
|
6
|
-
import path from 'path'
|
|
7
|
-
import url from 'url'
|
|
8
|
-
import { CALENDAR_DATES } from '../lib/constants.mjs'
|
|
9
|
-
import { dateUtils } from '@transportme/transportvic-utils'
|
|
10
|
-
|
|
11
|
-
const __filename = url.fileURLToPath(import.meta.url)
|
|
12
|
-
const __dirname = path.dirname(__filename)
|
|
13
|
-
|
|
14
|
-
const calendarFile = path.join(__dirname, 'sample-data', 'calendar', 'calendar.txt')
|
|
15
|
-
const calendarDatesFile = path.join(__dirname, 'sample-data', 'calendar', 'calendar_dates.txt')
|
|
16
|
-
|
|
17
|
-
describe('The GTFSCalendar class', () => {
|
|
18
|
-
it('Should process the calendar data to a list of operation days', () => {
|
|
19
|
-
expect(new GTFSCalendar({
|
|
20
|
-
id: 'T1',
|
|
21
|
-
startDay: '20241122',
|
|
22
|
-
endDay: '20241129',
|
|
23
|
-
daysOfWeek: ["0","0","0","0","1","1","0"]
|
|
24
|
-
}).getOperationDays()).to.have.members([
|
|
25
|
-
"20241122", "20241123", "20241129"
|
|
26
|
-
])
|
|
27
|
-
|
|
28
|
-
expect(new GTFSCalendar({
|
|
29
|
-
id: 'T2',
|
|
30
|
-
startDay: '20241123',
|
|
31
|
-
endDay: '20241130',
|
|
32
|
-
daysOfWeek: ["1","1","1","1","1","0","0"]
|
|
33
|
-
}).getOperationDays()).to.have.members([
|
|
34
|
-
"20241125", "20241126", "20241127", "20241128", "20241129"
|
|
35
|
-
])
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('Be inclusive of the end date', () => {
|
|
39
|
-
expect(new GTFSCalendar({
|
|
40
|
-
id: 'T1',
|
|
41
|
-
startDay: '20241122',
|
|
42
|
-
endDay: '20241122',
|
|
43
|
-
daysOfWeek: ["1","1","1","1","1","1","1"]
|
|
44
|
-
}).getOperationDays()).to.have.members([
|
|
45
|
-
"20241122"
|
|
46
|
-
])
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('Should allow adding of an extra date into the calendar', () => {
|
|
50
|
-
let cal = new GTFSCalendar({
|
|
51
|
-
id: 'T1',
|
|
52
|
-
startDay: '20241122',
|
|
53
|
-
endDay: '20241122',
|
|
54
|
-
daysOfWeek: ["1","1","1","1","1","1","1"]
|
|
55
|
-
}, [new GTFSCalendarDate({
|
|
56
|
-
id: 'T1',
|
|
57
|
-
date: '20241125',
|
|
58
|
-
type: CALENDAR_DATES.ADDED
|
|
59
|
-
})])
|
|
60
|
-
|
|
61
|
-
expect(cal.getOperationDays()).to.have.members([
|
|
62
|
-
"20241122", "20241125"
|
|
63
|
-
])
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it('Should allow removing of a specific date from the calendar', () => {
|
|
67
|
-
let cal = new GTFSCalendar({
|
|
68
|
-
id: 'T1',
|
|
69
|
-
startDay: '20241224',
|
|
70
|
-
endDay: '20241226',
|
|
71
|
-
daysOfWeek: ["1","1","1","1","1","1","1"]
|
|
72
|
-
}, [new GTFSCalendarDate({
|
|
73
|
-
id: 'T1',
|
|
74
|
-
date: '20241225',
|
|
75
|
-
type: CALENDAR_DATES.REMOVED
|
|
76
|
-
})])
|
|
77
|
-
|
|
78
|
-
expect(cal.getOperationDays()).to.deep.equal([
|
|
79
|
-
"20241224", "20241226"
|
|
80
|
-
])
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
it('Should ensure that adding an extra date into the calendar does not introduce duplicates', () => {
|
|
84
|
-
let cal = new GTFSCalendar({
|
|
85
|
-
id: 'T1',
|
|
86
|
-
startDay: '20241122',
|
|
87
|
-
endDay: '20241122',
|
|
88
|
-
daysOfWeek: ["1","1","1","1","1","1","1"]
|
|
89
|
-
}, [new GTFSCalendarDate({
|
|
90
|
-
id: 'T1',
|
|
91
|
-
date: '20241122',
|
|
92
|
-
type: CALENDAR_DATES.ADDED
|
|
93
|
-
})])
|
|
94
|
-
|
|
95
|
-
expect(cal.getOperationDays()).to.deep.equal([
|
|
96
|
-
"20241122"
|
|
97
|
-
])
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it('Should ensure that duplicate extra dates do not introduce duplicates', () => {
|
|
101
|
-
let calendarDate = new GTFSCalendarDate({
|
|
102
|
-
id: 'T1',
|
|
103
|
-
date: '20241122',
|
|
104
|
-
type: CALENDAR_DATES.ADDED
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
let cal = new GTFSCalendar({
|
|
108
|
-
id: 'T1',
|
|
109
|
-
startDay: '20241122',
|
|
110
|
-
endDay: '20241122',
|
|
111
|
-
daysOfWeek: ["1","1","1","1","1","1","1"]
|
|
112
|
-
}, [calendarDate, calendarDate, calendarDate])
|
|
113
|
-
|
|
114
|
-
expect(cal.getOperationDays()).to.deep.equal([
|
|
115
|
-
"20241122"
|
|
116
|
-
])
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
it('Should ensure that the date exists when removing a specific date from the calendar, and not do anything otherwise', () => {
|
|
120
|
-
let cal = new GTFSCalendar({
|
|
121
|
-
id: 'T1',
|
|
122
|
-
startDay: '20241224',
|
|
123
|
-
endDay: '20241226',
|
|
124
|
-
daysOfWeek: ["1","1","1","1","1","1","1"]
|
|
125
|
-
}, [new GTFSCalendarDate({
|
|
126
|
-
id: 'T1',
|
|
127
|
-
date: '20241227',
|
|
128
|
-
type: CALENDAR_DATES.REMOVED
|
|
129
|
-
})])
|
|
130
|
-
|
|
131
|
-
expect(cal.getOperationDays()).to.deep.equal([
|
|
132
|
-
"20241224", "20241225", "20241226"
|
|
133
|
-
])
|
|
134
|
-
})
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
describe('The GTFSCalendarDate class', () => {
|
|
138
|
-
it('Should convert the CSV data into a proper date object', () => {
|
|
139
|
-
expect(new GTFSCalendarDate({
|
|
140
|
-
id: 'T6',
|
|
141
|
-
date: '20241125',
|
|
142
|
-
type: CALENDAR_DATES.ADDED
|
|
143
|
-
}).date.toUTC().toISO()).to.equal('2024-11-24T13:00:00.000Z')
|
|
144
|
-
})
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
describe('The GTFSCalendarDatesReader class', () => {
|
|
148
|
-
it('Should read a calendar dates and return a GTFSCalendarDate object', async () => {
|
|
149
|
-
let reader = new GTFSCalendarDateReader(calendarDatesFile)
|
|
150
|
-
await reader.open()
|
|
151
|
-
|
|
152
|
-
let date = await reader.getNextEntity()
|
|
153
|
-
expect(date.id).to.equal('T3_2')
|
|
154
|
-
expect(dateUtils.toGTFSDate(date.date)).to.equal('20250126')
|
|
155
|
-
expect(date.type).to.equal(CALENDAR_DATES.REMOVED)
|
|
156
|
-
|
|
157
|
-
let date2 = await reader.getNextEntity()
|
|
158
|
-
expect(date2.id).to.equal('T5_2')
|
|
159
|
-
expect(dateUtils.toGTFSDate(date2.date)).to.equal('20241225')
|
|
160
|
-
expect(date2.type).to.equal(CALENDAR_DATES.REMOVED)
|
|
161
|
-
})
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
describe('The GTFSCalendarReader class', () => {
|
|
165
|
-
it('Should read both a calendar dates and calendar file and return a GTFSCalendar object with the appropriate exceptions filled', async () => {
|
|
166
|
-
let reader = new GTFSCalendarReader(calendarFile, calendarDatesFile)
|
|
167
|
-
await reader.open()
|
|
168
|
-
|
|
169
|
-
let allCalendars = {}
|
|
170
|
-
while (reader.available()) {
|
|
171
|
-
let calendar = await reader.getNextEntity()
|
|
172
|
-
allCalendars[calendar.id] = calendar
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
expect(allCalendars['T2'].getOperationDays()).to.deep.equal(["20241109"])
|
|
176
|
-
|
|
177
|
-
expect(allCalendars['T3_2'].getOperationDays()).to.contain("20241124")
|
|
178
|
-
expect(allCalendars['T3_2'].getOperationDays()).to.contain("20241201")
|
|
179
|
-
expect(allCalendars['T3_2'].getOperationDays()).to.contain("20250209")
|
|
180
|
-
expect(allCalendars['T3_2'].getOperationDays()).to.not.contain("20250126")
|
|
181
|
-
})
|
|
182
|
-
})
|
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import { LokiDatabaseConnection } from '@transportme/database'
|
|
2
|
-
import RouteLoader from '../lib/loader/RouteLoader.mjs'
|
|
3
|
-
import GTFSAgencyReader from '../lib/gtfs-parser/readers/GTFSAgencyReader.mjs'
|
|
4
|
-
|
|
5
|
-
import { TRANSIT_MODES } from '../lib/constants.mjs'
|
|
6
|
-
|
|
7
|
-
import path from 'path'
|
|
8
|
-
import url from 'url'
|
|
9
|
-
import { expect } from 'chai'
|
|
10
|
-
import GTFSAgency from '../lib/gtfs-parser/GTFSAgency.mjs'
|
|
11
|
-
|
|
12
|
-
const __filename = url.fileURLToPath(import.meta.url)
|
|
13
|
-
const __dirname = path.dirname(__filename)
|
|
14
|
-
|
|
15
|
-
const regionalRoutesFile = path.join(__dirname, 'sample-data', 'routes', 'regional_bus_routes.txt')
|
|
16
|
-
const metroRoutesFile = path.join(__dirname, 'sample-data', 'routes', 'metro_bus_routes.txt')
|
|
17
|
-
const metroLinesFile = path.join(__dirname, 'sample-data', 'routes', 'metro_lines.txt')
|
|
18
|
-
const agencyFile = path.join(__dirname, 'sample-data', 'routes', 'agency.txt')
|
|
19
|
-
const wgtFile = path.join(__dirname, 'sample-data', 'routes', 'west_gipp_transit.txt')
|
|
20
|
-
const vlineFile = path.join(__dirname, 'sample-data', 'routes', 'vline.txt')
|
|
21
|
-
|
|
22
|
-
const badRoutesFile_NoAgency = path.join(__dirname, 'sample-data', 'routes', 'bad_routes.txt')
|
|
23
|
-
|
|
24
|
-
describe('The GTFS Agency Reader', () => {
|
|
25
|
-
it('Should read the agencies one line at a time', async () => {
|
|
26
|
-
let reader = new GTFSAgencyReader(agencyFile)
|
|
27
|
-
await reader.open()
|
|
28
|
-
let operator = await reader.getNextEntity()
|
|
29
|
-
|
|
30
|
-
expect(operator.id).to.equal('43')
|
|
31
|
-
expect(operator.name).to.equal('CDC Ballarat')
|
|
32
|
-
expect(operator.website).to.equal('https://www.ptv.vic.gov.au/footer/customer-service/operator-contact-details/?utm_source=open_data_click_agency&utm_medium=open_data_agency_click&utm_campaign=open_data_click')
|
|
33
|
-
expect(operator.phone).to.equal('1800 800 007')
|
|
34
|
-
})
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
describe('The GTFS Routes Loader', () => {
|
|
38
|
-
it('Should process the routes and add them to the database', async () => {
|
|
39
|
-
let database = new LokiDatabaseConnection('test-db')
|
|
40
|
-
let routes = await database.createCollection('gtfs-routes')
|
|
41
|
-
|
|
42
|
-
let loader = new RouteLoader(regionalRoutesFile, agencyFile, TRANSIT_MODES.bus, database)
|
|
43
|
-
await loader.loadRoutes()
|
|
44
|
-
|
|
45
|
-
let bal10 = await routes.findDocument({
|
|
46
|
-
routeGTFSID: '6-10x'
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
expect(bal10).to.not.be.null
|
|
50
|
-
expect(bal10.routeNumber).to.equal('10')
|
|
51
|
-
expect(bal10.operators).to.deep.equal(['CDC Ballarat'])
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it('Should open the agency.txt file and load all operators', async () => {
|
|
55
|
-
let database = new LokiDatabaseConnection('test-db')
|
|
56
|
-
await database.createCollection('gtfs-routes')
|
|
57
|
-
|
|
58
|
-
let loader = new RouteLoader(regionalRoutesFile, agencyFile, TRANSIT_MODES.bus, database)
|
|
59
|
-
await loader.loadAgencies()
|
|
60
|
-
|
|
61
|
-
let paynesville = loader.getOperator('99')
|
|
62
|
-
|
|
63
|
-
expect(paynesville).to.not.be.undefined
|
|
64
|
-
expect(paynesville.name).to.equal('Paynesville Bus Lines')
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
it('Should handle smartrak routes', async () => {
|
|
68
|
-
let database = new LokiDatabaseConnection('test-db')
|
|
69
|
-
let routes = await database.createCollection('gtfs-routes')
|
|
70
|
-
|
|
71
|
-
let loader = new RouteLoader(metroRoutesFile, agencyFile, TRANSIT_MODES.bus, database)
|
|
72
|
-
await loader.loadRoutes()
|
|
73
|
-
|
|
74
|
-
let smart900 = await routes.findDocument({
|
|
75
|
-
routeGTFSID: '4-900'
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
expect(smart900).to.not.be.null
|
|
79
|
-
expect(smart900.mode).to.equal(TRANSIT_MODES.bus)
|
|
80
|
-
expect(smart900.routeNumber).to.equal('900')
|
|
81
|
-
expect(smart900.operators).to.have.members(['CDC', 'Ventura Bus Lines'])
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('Should handle metro train routes', async () => {
|
|
85
|
-
let database = new LokiDatabaseConnection('test-db')
|
|
86
|
-
let routes = await database.createCollection('gtfs-routes')
|
|
87
|
-
|
|
88
|
-
let loader = new RouteLoader(metroLinesFile, agencyFile, TRANSIT_MODES.metroTrain, database)
|
|
89
|
-
await loader.loadRoutes()
|
|
90
|
-
|
|
91
|
-
let stony = await routes.findDocument({
|
|
92
|
-
routeGTFSID: '2-STY'
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
expect(stony).to.not.be.null
|
|
96
|
-
expect(stony.mode).to.equal('metro train')
|
|
97
|
-
expect(stony.routeNumber).to.be.null
|
|
98
|
-
expect(stony.routeName).to.equal('Stony Point')
|
|
99
|
-
expect(stony.operators).to.have.members(['Metro'])
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('Should allow for custom route rewriting', async () => {
|
|
103
|
-
let database = new LokiDatabaseConnection('test-db')
|
|
104
|
-
let routes = await database.createCollection('gtfs-routes')
|
|
105
|
-
|
|
106
|
-
let loader = new RouteLoader(wgtFile, agencyFile, TRANSIT_MODES.bus, database)
|
|
107
|
-
await loader.loadRoutes({
|
|
108
|
-
processRoute: route => {
|
|
109
|
-
if (route.routeGTFSID.match(/6-w\d\d/)) {
|
|
110
|
-
route.routeGTFSID = '6-WGT'
|
|
111
|
-
route.routeName = 'West Gippsland Transit'
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return route
|
|
115
|
-
}
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
let wgt = await routes.findDocument({
|
|
119
|
-
routeGTFSID: '6-WGT'
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
expect(wgt, 'Expected West Gippsland Transit route 6-WGT to be created').to.not.be.null
|
|
123
|
-
expect(wgt.mode).to.equal('bus')
|
|
124
|
-
expect(wgt.routeNumber).to.be.null
|
|
125
|
-
expect(wgt.routeName).to.equal('West Gippsland Transit')
|
|
126
|
-
expect(wgt.operators).to.have.members(['Warragul Bus Lines'])
|
|
127
|
-
|
|
128
|
-
expect(await routes.findDocument({
|
|
129
|
-
routeGTFSID: '6-w40'
|
|
130
|
-
}), 'Expected 6-w40 Moe - Garfield to not exist').to.be.null
|
|
131
|
-
|
|
132
|
-
expect(await routes.findDocument({
|
|
133
|
-
routeGTFSID: '6-w41'
|
|
134
|
-
}), 'Expected 6-w41 Warragul - Pakenham to not exist').to.be.null
|
|
135
|
-
|
|
136
|
-
let nojee = await routes.findDocument({
|
|
137
|
-
routeGTFSID: '6-W89'
|
|
138
|
-
})
|
|
139
|
-
expect(nojee, 'Expected 6-W89 Warragul - Nojee to exist and not be modified').to.not.be.null
|
|
140
|
-
expect(nojee.mode).to.equal('bus')
|
|
141
|
-
expect(nojee.routeNumber).to.equal('89')
|
|
142
|
-
expect(nojee.routeName).to.equal('Noojee - Warragul Station Via Buln Buln')
|
|
143
|
-
expect(nojee.operators).to.have.members(['Warragul Bus Lines'])
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
it('Should return a route ID map', async () => {
|
|
148
|
-
let database = new LokiDatabaseConnection('test-db')
|
|
149
|
-
let routes = await database.createCollection('gtfs-routes')
|
|
150
|
-
|
|
151
|
-
let loader = new RouteLoader(wgtFile, agencyFile, TRANSIT_MODES.bus, database)
|
|
152
|
-
await loader.loadRoutes({
|
|
153
|
-
processRoute: route => {
|
|
154
|
-
if (route.routeGTFSID.match(/6-w\d\d/)) {
|
|
155
|
-
route.routeGTFSID = '6-WGT'
|
|
156
|
-
route.routeName = 'West Gippsland Transit'
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return route
|
|
160
|
-
}
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
let map = loader.getRouteIDMap()
|
|
164
|
-
expect(map['6-w19-mjp-1']).to.equal('6-WGT')
|
|
165
|
-
expect(map['6-W89-mjp-1']).to.equal('6-W89')
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
it('Should allow for dropping of unwanted routes', async () => {
|
|
169
|
-
let database = new LokiDatabaseConnection('test-db')
|
|
170
|
-
let routes = await database.createCollection('gtfs-routes')
|
|
171
|
-
|
|
172
|
-
let loader = new RouteLoader(vlineFile, agencyFile, TRANSIT_MODES.regionalTrain, database)
|
|
173
|
-
await loader.loadRoutes({
|
|
174
|
-
processRoute: route => {
|
|
175
|
-
if (route.routeGTFSID === '1-vPK') return null
|
|
176
|
-
return route
|
|
177
|
-
}
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
expect(await routes.findDocument({
|
|
181
|
-
routeGTFSID: '1-vPK'
|
|
182
|
-
}), 'Expected 1-vPK V/Line Dandenong Metro Services to not exist').to.be.null
|
|
183
|
-
|
|
184
|
-
let traralgon = await routes.findDocument({
|
|
185
|
-
routeGTFSID: '1-TRN'
|
|
186
|
-
})
|
|
187
|
-
expect(traralgon, 'Expected 1-TRN Traralgon to exist and not be modified').to.not.be.null
|
|
188
|
-
expect(traralgon.mode).to.equal('regional train')
|
|
189
|
-
expect(traralgon.routeNumber).to.be.null
|
|
190
|
-
expect(traralgon.routeName).to.equal('Traralgon - Melbourne Via Pakenham, Moe & Morwell')
|
|
191
|
-
expect(traralgon.operators).to.have.members(['V/Line'])
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
it('Should handle invalid agency references', async () => {
|
|
195
|
-
let database = new LokiDatabaseConnection('test-db')
|
|
196
|
-
let routes = await database.createCollection('gtfs-routes')
|
|
197
|
-
|
|
198
|
-
let loader = new RouteLoader(badRoutesFile_NoAgency, agencyFile, TRANSIT_MODES.bus, database)
|
|
199
|
-
await loader.loadRoutes()
|
|
200
|
-
|
|
201
|
-
let skybus = await routes.findDocument({
|
|
202
|
-
routeGTFSID: '11-SKY'
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
expect(skybus).to.not.be.null
|
|
206
|
-
expect(skybus.operators).to.deep.equal([GTFSAgency.UNKNOWN_AGENCY.name])
|
|
207
|
-
})
|
|
208
|
-
})
|