@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.
Files changed (85) hide show
  1. package/lib/gtfs-parser/GTFSRoute.mjs +22 -4
  2. package/lib/gtfs-parser/GTFSTrip.mjs +23 -1
  3. package/lib/gtfs-parser/readers/GTFSCalendarReader.mjs +1 -0
  4. package/lib/gtfs-parser/readers/line-reader.mjs +1 -6
  5. package/lib/loader/RouteLoader.mjs +4 -2
  6. package/lib/loader/ShapeLoader.mjs +3 -2
  7. package/lib/loader/StopsLoader.mjs +4 -2
  8. package/lib/loader/TripLoader.mjs +13 -4
  9. package/lib/post-processing/set-route-stops.mjs +12 -3
  10. package/package.json +1 -1
  11. package/.github/workflows/unit-test.yml +0 -46
  12. package/test/gtfs-calendar.test.mjs +0 -182
  13. package/test/gtfs-route-loader.test.mjs +0 -208
  14. package/test/gtfs-routes.test.mjs +0 -77
  15. package/test/gtfs-shapes-loader.test.mjs +0 -105
  16. package/test/gtfs-shapes.test.mjs +0 -23
  17. package/test/gtfs-stop-loader.test.mjs +0 -415
  18. package/test/gtfs-stop-times.test.mjs +0 -37
  19. package/test/gtfs-stops.test.mjs +0 -311
  20. package/test/gtfs-trip-loader.test.mjs +0 -188
  21. package/test/gtfs-trips.test.mjs +0 -108
  22. package/test/line-reader.test.mjs +0 -120
  23. package/test/merge-stops-full.test.mjs +0 -118
  24. package/test/merge-stops.test.mjs +0 -376
  25. package/test/new-metro-loader-test.test.mjs +0 -244
  26. package/test/new-tram-format.test.mjs +0 -49
  27. package/test/newer-metro-loader-test.test.mjs +0 -80
  28. package/test/newer-tram-format.test.mjs +0 -67
  29. package/test/ptv-gtfs.test.mjs +0 -106
  30. package/test/sample-data/calendar/calendar.txt +0 -34
  31. package/test/sample-data/calendar/calendar_dates.txt +0 -26
  32. package/test/sample-data/gtfs-splitting/force_unique_stops_bus.txt +0 -7
  33. package/test/sample-data/gtfs-splitting/force_unique_stops_tram.txt +0 -3
  34. package/test/sample-data/gtfs-splitting/general-stores.txt +0 -19
  35. package/test/sample-data/gtfs-splitting/line_reader_test.txt +0 -8
  36. package/test/sample-data/gtfs-splitting/name_overrides.txt +0 -4
  37. package/test/sample-data/gtfs-splitting/sample_stops.txt +0 -20
  38. package/test/sample-data/gtfs-splitting/special_chars_stops_csv.txt +0 -6
  39. package/test/sample-data/gtfs-splitting/stops_without_secondary.txt +0 -6
  40. package/test/sample-data/gtfs-splitting/union_with_spaces.txt +0 -5
  41. package/test/sample-data/gtfs-unzipping/gtfs.zip +0 -0
  42. package/test/sample-data/merge/shapes.txt +0 -1224
  43. package/test/sample-data/merge/stop_times.txt +0 -103
  44. package/test/sample-data/merge/stops.txt +0 -39
  45. package/test/sample-data/merge/trips.txt +0 -7
  46. package/test/sample-data/mtm-rail/stops.txt +0 -4
  47. package/test/sample-data/new-metro/calendar.txt +0 -13
  48. package/test/sample-data/new-metro/calendar_dates.txt +0 -1
  49. package/test/sample-data/new-metro/routes.txt +0 -18
  50. package/test/sample-data/new-metro/simple_stop_test.txt +0 -6
  51. package/test/sample-data/new-metro/stop_times.txt +0 -64
  52. package/test/sample-data/new-metro/stops.txt +0 -139
  53. package/test/sample-data/new-metro/trips.txt +0 -6
  54. package/test/sample-data/new-metro-2/calendar.txt +0 -13
  55. package/test/sample-data/new-metro-2/calendar_dates.txt +0 -1
  56. package/test/sample-data/new-metro-2/routes.txt +0 -2
  57. package/test/sample-data/new-metro-2/stop_times.txt +0 -5
  58. package/test/sample-data/new-metro-2/stops.txt +0 -9
  59. package/test/sample-data/new-metro-2/trips.txt +0 -2
  60. package/test/sample-data/new-tram/routes.txt +0 -25
  61. package/test/sample-data/new-tram/stops.txt +0 -5
  62. package/test/sample-data/new-tram-2/routes.txt +0 -25
  63. package/test/sample-data/new-tram-2/stops.txt +0 -8
  64. package/test/sample-data/one_line.csv +0 -2
  65. package/test/sample-data/parent-stop/multi_stop.txt +0 -4
  66. package/test/sample-data/parent-stop/stops.txt +0 -15
  67. package/test/sample-data/routes/agency.txt +0 -12
  68. package/test/sample-data/routes/bad_routes.txt +0 -4
  69. package/test/sample-data/routes/metro_bus_routes.txt +0 -4
  70. package/test/sample-data/routes/metro_lines.txt +0 -8
  71. package/test/sample-data/routes/regional_bus_routes.txt +0 -7
  72. package/test/sample-data/routes/vline.txt +0 -7
  73. package/test/sample-data/routes/west_gipp_transit.txt +0 -20
  74. package/test/sample-data/shapes/shapes.txt +0 -89
  75. package/test/sample-data/shapes/stop_times.txt +0 -9
  76. package/test/sample-data/shapes/stops.txt +0 -4
  77. package/test/sample-data/shapes/trips.txt +0 -4
  78. package/test/sample-data/stop-data/name-overrides.json +0 -25
  79. package/test/sample-data/stop-data/unique-stops.json +0 -1
  80. package/test/sample-data/suburbs.json +0 -77
  81. package/test/sample-data/trips/bus_trips.txt +0 -5
  82. package/test/sample-data/trips/stop_times.txt +0 -29
  83. package/test/sample-data/trips/stops.txt +0 -93
  84. package/test/sample-data/trips/trips.txt +0 -5
  85. 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 !== null && routeNumber.length === 0) routeNumber = null
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('gtfs-routes')
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('gtfs-routes')
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('gtfs-stops')
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
- this.#routes = database.getCollection('gtfs-routes')
48
- this.#stops = database.getCollection('gtfs-stops')
49
- this.#trips = database.getCollection('gtfs-gtfs timetables')
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
- let gtfsTimetables = database.getCollection('gtfs-gtfs timetables')
13
- let routes = database.getCollection('gtfs-routes')
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@transportme/load-ptv-gtfs",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
4
4
  "description": "",
5
5
  "main": "index.mjs",
6
6
  "scripts": {
@@ -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
- })