@transportme/load-ptv-gtfs 1.0.8 → 1.1.0

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.
@@ -57,7 +57,7 @@ export default class GTFSRoute {
57
57
  */
58
58
  static create(data, mode) {
59
59
  let { routeGTFSID } = data
60
- let routeTypes = [ MetroGTFSRoute, SmartrakGTFSRoute, GTFSRoute ]
60
+ let routeTypes = [ NewMetroGTFSRoute, MetroGTFSRoute, SmartrakGTFSRoute, GTFSRoute ]
61
61
  for (let type of routeTypes) {
62
62
  if (type.canProcess(routeGTFSID, mode)) return new type(data)
63
63
  }
@@ -87,7 +87,7 @@ class SmartrakGTFSRoute extends GTFSRoute {
87
87
  }
88
88
 
89
89
  /**
90
- * Used to represent a Metro train route
90
+ * Used to represent a Metro train route on the new data
91
91
  *
92
92
  * Used for a technicality where the route name (eg Pakenham) is stored in the route number instead.
93
93
  */
@@ -103,4 +103,22 @@ class MetroGTFSRoute extends GTFSRoute {
103
103
  return mode === TRANSIT_MODES.metroTrain
104
104
  }
105
105
 
106
+ }
107
+
108
+ /**
109
+ * Used to represent a Metro train route on the new data
110
+ *
111
+ * Required to parse the route ID which is of a slightly different format
112
+ */
113
+ class NewMetroGTFSRoute extends MetroGTFSRoute {
114
+
115
+ static canProcess(routeGTFSID, mode) {
116
+ return super.canProcess(routeGTFSID, mode) && routeGTFSID.includes(':vic:')
117
+ }
118
+
119
+ parseRouteID(routeGTFSID) {
120
+ let [ _, routeNumber ] = routeGTFSID.match(/02\-(\w{3})/)
121
+ return `2-${routeNumber}`
122
+ }
123
+
106
124
  }
@@ -182,7 +182,7 @@ export class GTFSStop {
182
182
  }
183
183
 
184
184
  static create(data, suburbs) {
185
- let types = [ GTFSPlatformStop, GTFSStop ]
185
+ let types = [ GTFSRRBStop, GTFSPlatformStop, GTFSStop ]
186
186
  for (let type of types) {
187
187
  if (type.canProcess(data)) return new type(data, suburbs)
188
188
  }
@@ -209,4 +209,18 @@ export class GTFSPlatformStop extends GTFSStop {
209
209
  return data.platform && data.platform.length
210
210
  }
211
211
 
212
+ }
213
+
214
+ export class GTFSRRBStop extends GTFSPlatformStop {
215
+
216
+ constructor(data, suburbs) {
217
+ data.stopName = data.stopName.replace('Rail Replacement Bus Stop', 'Railway Station')
218
+ super(data, suburbs)
219
+ this.platform = 'RRB'
220
+ }
221
+
222
+ static canProcess(data) {
223
+ return super.canProcess(data) && data.platform === 'R-Bus'
224
+ }
225
+
212
226
  }
@@ -136,9 +136,10 @@ export class SmartrakTrip extends GTFSTrip {
136
136
  /**
137
137
  * Used to represent a Metro train trip.
138
138
  *
139
- * Trip IDs are of the form 02-ROUTE--ROUTE_VARIANT-ROSTER-TDN. Note that TDNs are alphanumeric
139
+ * Trip IDs are of the form 02-ROUTE--ROUTE_VARIANT-CALENDAR-TDN. Note that TDNs are alphanumeric
140
140
  *
141
141
  * Example: 02-BEG--58-T5_n4-3651
142
+ * RRB Example: 02-SUY--13-T2-BS000
142
143
  */
143
144
  export class MetroTrip extends GTFSTrip {
144
145
 
@@ -147,18 +148,21 @@ export class MetroTrip extends GTFSTrip {
147
148
  constructor(data) {
148
149
  super(data)
149
150
 
150
- let runID = data.id.match(/^02-\w{3}-.*-(\w\d{3})$/)
151
+ let runID = data.id.match(/^02-\w{3}-.*-(\w{1,2}\d{3})$/)
151
152
  this.#runID = runID[1]
152
153
  }
153
154
 
154
155
  static canProcess(data, mode) {
155
- return mode === TRANSIT_MODES.metroTrain && data.id.match(/^02-\w{3}-.*-(\w\d{3})$/)
156
+ return mode === TRANSIT_MODES.metroTrain && data.id.match(/^02-\w{3}-.*-(\w{1,2}\d{3})$/)
156
157
  }
157
158
 
158
159
  getTripData() {
160
+ let runID = this.getRunID()
161
+
159
162
  return {
160
163
  ...super.getTripData(),
161
- runID: this.getRunID()
164
+ runID,
165
+ isRailReplacementBus: runID.length === 5 && runID[0] === 'B'
162
166
  }
163
167
  }
164
168
 
@@ -63,11 +63,19 @@ export default async function setRouteStops(database) {
63
63
 
64
64
  if (routeData.mode === TRANSIT_MODES.metroTrain) {
65
65
  let currentDir = stopsByService[routeGTFSID][gtfsDirection].directionName
66
- if (currentDir.includes('Flinders Street') || currentDir.includes('Town Hall')) {
67
- stopsByService[routeGTFSID][gtfsDirection].directionName = 'City'
66
+ let isUp = currentDir.includes('Flinders Street') || currentDir.includes('Town Hall')
67
+
68
+ let directionName = stopsByService[routeGTFSID][gtfsDirection].directionName
69
+
70
+ if (routeData.routeName === 'Stony Point') {
71
+ isUp = currentDir.includes('Frankston')
72
+ directionName = directionName.slice(0, -16)
68
73
  } else {
69
- stopsByService[routeGTFSID][gtfsDirection].directionName = currentDir.slice(0, -16)
74
+ directionName = isUp ? 'City' : routeData.routeName
70
75
  }
76
+
77
+ stopsByService[routeGTFSID][gtfsDirection].directionName = directionName
78
+ stopsByService[routeGTFSID][gtfsDirection].trainDirection = isUp ? 'Up' : 'Down'
71
79
  }
72
80
  }
73
81
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@transportme/load-ptv-gtfs",
3
- "version": "1.0.8",
3
+ "version": "1.1.0",
4
4
  "description": "",
5
5
  "main": "index.mjs",
6
6
  "scripts": {
@@ -21,9 +21,6 @@ const stopsFile = path.join(__dirname, 'sample-data', 'trips', 'stops.txt')
21
21
  const stopTimesFile = path.join(__dirname, 'sample-data', 'trips', 'stop_times.txt')
22
22
  const tripsFile = path.join(__dirname, 'sample-data', 'trips', 'trips.txt')
23
23
 
24
- const newStopTimesFile = path.join(__dirname, 'sample-data', 'trips', 'stop_times_new.txt')
25
- const newTripsFile = path.join(__dirname, 'sample-data', 'trips', 'trips_new.txt')
26
-
27
24
  describe('The TripLoader class', () => {
28
25
  it('Should process the trip and add it to the database', async () => {
29
26
  let database = new LokiDatabaseConnection('test-db')
@@ -112,31 +109,4 @@ describe('The TripLoader class', () => {
112
109
 
113
110
  expect(shapeIDMap['2-ALM-vpt-1.1.R']).to.equal('2-ALM')
114
111
  })
115
-
116
- it('It should accept trip IDs in the new format and extract the TDN from there', async () => {
117
- let database = new LokiDatabaseConnection('test-db')
118
- let stops = await database.createCollection('stops')
119
- let routes = await database.createCollection('routes')
120
- let trips = await database.createCollection('gtfs timetables')
121
-
122
- let stopLoader = new StopsLoader(stopsFile, suburbs, TRANSIT_MODES.metroTrain, database)
123
- await stopLoader.loadStops()
124
-
125
- let routeLoader = new RouteLoader(routesFile, agencyFile, TRANSIT_MODES.metroTrain, database)
126
- await routeLoader.loadRoutes()
127
-
128
- let routeIDMap = routeLoader.getRouteIDMap()
129
-
130
- let tripLoader = new TripLoader({
131
- tripsFile: newTripsFile, stopTimesFile: newStopTimesFile,
132
- calendarFile, calendarDatesFile
133
- }, TRANSIT_MODES.metroTrain, database)
134
-
135
- await tripLoader.loadTrips({ routeIDMap })
136
-
137
- let trip = await trips.findDocument({ tripID: '02-ALM--1-T2-2302' })
138
-
139
- expect(trip).to.not.be.null
140
- expect(trip.runID).to.equal('2302')
141
- })
142
112
  })
@@ -0,0 +1,99 @@
1
+ import { expect } from 'chai'
2
+ import path from 'path'
3
+ import url from 'url'
4
+ import { TRANSIT_MODES } from '../lib/constants.mjs'
5
+ import { LokiDatabaseConnection } from '@transportme/database'
6
+ import StopsLoader from '../lib/loader/StopsLoader.mjs'
7
+ import RouteLoader from '../lib/loader/RouteLoader.mjs'
8
+ import TripLoader from '../lib/loader/TripLoader.mjs'
9
+ import ShapeLoader from '../lib/loader/ShapeLoader.mjs'
10
+ import setRouteStops from '../lib/post-processing/set-route-stops.mjs'
11
+ import suburbs from './sample-data/suburbs.json' with { type: 'json' }
12
+
13
+ const __filename = url.fileURLToPath(import.meta.url)
14
+ const __dirname = path.dirname(__filename)
15
+
16
+ const routesFile = path.join(__dirname, 'sample-data', 'routes', 'metro_lines.txt')
17
+ const agencyFile = path.join(__dirname, 'sample-data', 'routes', 'agency.txt')
18
+
19
+ const calendarFile = path.join(__dirname, 'sample-data', 'calendar', 'calendar.txt')
20
+ const calendarDatesFile = path.join(__dirname, 'sample-data', 'calendar', 'calendar_dates.txt')
21
+
22
+ const stopsFile = path.join(__dirname, 'sample-data', 'merge', 'stops.txt')
23
+ const stopTimesFile = path.join(__dirname, 'sample-data', 'merge', 'stop_times.txt')
24
+ const tripsFile = path.join(__dirname, 'sample-data', 'merge', 'trips.txt')
25
+ const shapeFile = path.join(__dirname, 'sample-data', 'merge', 'shapes.txt')
26
+
27
+ let database = new LokiDatabaseConnection('test-db')
28
+ let stops = await database.createCollection('stops')
29
+ let routes = await database.createCollection('routes')
30
+ let trips = await database.createCollection('gtfs timetables')
31
+
32
+ let stopLoader = new StopsLoader(stopsFile, suburbs, TRANSIT_MODES.metroTrain, database)
33
+ await stopLoader.loadStops()
34
+
35
+ let routeLoader = new RouteLoader(routesFile, agencyFile, TRANSIT_MODES.metroTrain, database)
36
+ await routeLoader.loadRoutes()
37
+
38
+ let routeIDMap = routeLoader.getRouteIDMap()
39
+
40
+ let tripLoader = new TripLoader({
41
+ tripsFile, stopTimesFile,
42
+ calendarFile, calendarDatesFile
43
+ }, TRANSIT_MODES.metroTrain, database)
44
+
45
+ await tripLoader.loadTrips({ routeIDMap })
46
+ let shapeIDMap = tripLoader.getShapeIDMap()
47
+
48
+ let shapeLoader = new ShapeLoader(shapeFile, database)
49
+
50
+ await shapeLoader.loadShapes({ shapeIDMap })
51
+
52
+ await setRouteStops(database)
53
+
54
+ describe('The GTFS Route stop merger', () => {
55
+ describe('When processing Metro routes', () => {
56
+ it('Should set Up trips to be City-bound', async () => {
57
+ let pakenhamRoute = await routes.findDocument({ routeGTFSID: '2-PKM' })
58
+ expect(pakenhamRoute).to.not.be.null
59
+
60
+ let city = pakenhamRoute.directions.find(dir => dir.directionName === 'City')
61
+ expect(city).to.not.be.undefined
62
+ expect(city.stops[0].stopName).to.equal('East Pakenham Railway Station')
63
+ expect(city.stops[city.stops.length - 2].stopName).to.equal('Southern Cross Railway Station')
64
+ expect(city.stops[city.stops.length - 1].stopName).to.equal('Flinders Street Railway Station')
65
+ })
66
+
67
+ it('Should set Down trips to the name of the route', async () => {
68
+ let pakenhamRoute = await routes.findDocument({ routeGTFSID: '2-PKM' })
69
+ expect(pakenhamRoute).to.not.be.null
70
+
71
+ let pakenham = pakenhamRoute.directions.find(dir => dir.directionName !== 'City')
72
+ expect(pakenham).to.not.be.undefined
73
+ expect(pakenham.directionName).to.equal('Pakenham')
74
+ expect(pakenham.stops[0].stopName).to.equal('Flinders Street Railway Station')
75
+ expect(pakenham.stops[pakenham.stops.length - 2].stopName).to.equal('Pakenham Railway Station')
76
+ expect(pakenham.stops[pakenham.stops.length - 1].stopName).to.equal('East Pakenham Railway Station')
77
+ })
78
+
79
+ it('Should set Stony Point to Frankston trips as Up', async () => {
80
+ let stonyPointRoute = await routes.findDocument({ routeGTFSID: '2-STY' })
81
+ expect(stonyPointRoute).to.not.be.null
82
+
83
+ let frankston = stonyPointRoute.directions.find(dir => dir.directionName === 'Frankston')
84
+ expect(frankston).to.not.be.undefined
85
+ expect(frankston.stops[0].stopName).to.equal('Stony Point Railway Station')
86
+ expect(frankston.stops[frankston.stops.length - 1].stopName).to.equal('Frankston Railway Station')
87
+ })
88
+
89
+ it('Should set Stony Point trips as Down', async () => {
90
+ let stonyPointRoute = await routes.findDocument({ routeGTFSID: '2-STY' })
91
+ expect(stonyPointRoute).to.not.be.null
92
+
93
+ let stonyPoint = stonyPointRoute.directions.find(dir => dir.directionName === 'Stony Point')
94
+ expect(stonyPoint).to.not.be.undefined
95
+ expect(stonyPoint.stops[0].stopName).to.equal('Frankston Railway Station')
96
+ expect(stonyPoint.stops[stonyPoint.stops.length - 1].stopName).to.equal('Stony Point Railway Station')
97
+ })
98
+ })
99
+ })
@@ -0,0 +1,165 @@
1
+ import { LokiDatabaseConnection } from '@transportme/database'
2
+ import StopsLoader from '../lib/loader/StopsLoader.mjs'
3
+ import { TRANSIT_MODES } from '../lib/constants.mjs'
4
+ import path from 'path'
5
+ import url from 'url'
6
+ import { expect } from 'chai'
7
+ import suburbs from './sample-data/suburbs.json' with { type: 'json' }
8
+ import RouteLoader from '../lib/loader/RouteLoader.mjs'
9
+ import TripLoader from '../lib/loader/TripLoader.mjs'
10
+
11
+ const __filename = url.fileURLToPath(import.meta.url)
12
+ const __dirname = path.dirname(__filename)
13
+
14
+ const agencyFile = path.join(__dirname, 'sample-data', 'routes', 'agency.txt')
15
+
16
+ const calendarFile = path.join(__dirname, 'sample-data', 'new-metro', 'calendar.txt')
17
+ const calendarDatesFile = path.join(__dirname, 'sample-data', 'new-metro', 'calendar_dates.txt')
18
+
19
+ const routesFile = path.join(__dirname, 'sample-data', 'new-metro', 'routes.txt')
20
+
21
+ const stopTimesFile = path.join(__dirname, 'sample-data', 'new-metro', 'stop_times.txt')
22
+ const tripsFile = path.join(__dirname, 'sample-data', 'new-metro', 'trips.txt')
23
+ const stopsFile = path.join(__dirname, 'sample-data', 'new-metro', 'stops.txt')
24
+
25
+ const simpleStopsFile = path.join(__dirname, 'sample-data', 'new-metro', 'simple_stop_test.txt')
26
+
27
+ describe('The GTFS Loaders with the new Metro data', () => {
28
+ describe('The stop loader', () => {
29
+ it('Should process the stops and add them to the database', async () => {
30
+ let database = new LokiDatabaseConnection('test-db')
31
+ let stops = await database.createCollection('stops')
32
+
33
+ let loader = new StopsLoader(simpleStopsFile, suburbs, TRANSIT_MODES.metroTrain, database)
34
+ await loader.loadStops()
35
+
36
+ let stop = await stops.findDocument({
37
+ 'stopName': 'Camberwell Railway Station'
38
+ })
39
+
40
+ expect(stop).to.not.be.null
41
+ })
42
+
43
+ it('Should label the individual platforms with their platform numbers', async () => {
44
+ let database = new LokiDatabaseConnection('test-db')
45
+ let stops = await database.createCollection('stops')
46
+
47
+ let loader = new StopsLoader(simpleStopsFile, suburbs, TRANSIT_MODES.metroTrain, database)
48
+ await loader.loadStops()
49
+
50
+ let stop = await stops.findDocument({
51
+ 'stopName': 'Camberwell Railway Station'
52
+ })
53
+
54
+ let platformIDs = ["11207", "11208", "11209"]
55
+ let platforms = platformIDs.map(id => stop.bays.find(bay => bay.stopGTFSID === id))
56
+
57
+ for (let i = 0; i < platformIDs.length; i++) {
58
+ let platform = platforms[i]
59
+ expect(platform).to.not.be.undefined
60
+ expect(platforms[i].platform).to.equal((i + 1).toString())
61
+ }
62
+ })
63
+ })
64
+
65
+ describe('The route loader', () => {
66
+ it('Should convert the updated route ID format to the legacy format', async () => {
67
+ let database = new LokiDatabaseConnection('test-db')
68
+ let routes = await database.createCollection('routes')
69
+
70
+ let routeLoader = new RouteLoader(routesFile, agencyFile, TRANSIT_MODES.metroTrain, database)
71
+ await routeLoader.loadRoutes()
72
+
73
+ let pakenham = await routes.findDocument({ routeName: 'Pakenham' })
74
+
75
+ expect(pakenham).to.exist
76
+ expect(pakenham.routeGTFSID).to.equal('2-PKM')
77
+ })
78
+ })
79
+
80
+ describe('The trip loader', () => {
81
+ it('It should accept trip IDs in the new format and extract the TDN from there', async () => {
82
+ let database = new LokiDatabaseConnection('test-db')
83
+ let stops = await database.createCollection('stops')
84
+ let routes = await database.createCollection('routes')
85
+ let trips = await database.createCollection('gtfs timetables')
86
+
87
+ let stopLoader = new StopsLoader(stopsFile, suburbs, TRANSIT_MODES.metroTrain, database)
88
+ await stopLoader.loadStops()
89
+
90
+ let routeLoader = new RouteLoader(routesFile, agencyFile, TRANSIT_MODES.metroTrain, database)
91
+ await routeLoader.loadRoutes()
92
+
93
+ let routeIDMap = routeLoader.getRouteIDMap()
94
+
95
+ let tripLoader = new TripLoader({
96
+ tripsFile, stopTimesFile,
97
+ calendarFile, calendarDatesFile
98
+ }, TRANSIT_MODES.metroTrain, database)
99
+
100
+ await tripLoader.loadTrips({ routeIDMap })
101
+
102
+ let trip = await trips.findDocument({ runID: 'C000' })
103
+
104
+ expect(trip).to.not.be.null
105
+ expect(trip.tripID).to.equal('02-PKM--12-T5-C000')
106
+ expect(trip.shapeID).to.equal('2-PKM-vpt-12.20.R')
107
+ expect(trip.routeGTFSID).to.equal('2-PKM')
108
+ expect(trip.block).to.equal('3347')
109
+ expect(trip.isRailReplacementBus).to.be.false
110
+
111
+ expect(trip.stopTimings[0].stopName).to.equal('East Pakenham Railway Station')
112
+ expect(trip.stopTimings[0].platform).to.equal('1')
113
+
114
+ expect(trip.stopTimings[22].stopName).to.equal('Richmond Railway Station')
115
+ expect(trip.stopTimings[22].platform).to.equal('5')
116
+
117
+ expect(trip.stopTimings[27].stopName).to.equal('Flinders Street Railway Station')
118
+ expect(trip.stopTimings[27].platform).to.equal('7')
119
+
120
+ let nextTrip = await trips.findDocument({ block: '3347', runID: { $ne: 'C000' } })
121
+ expect(nextTrip.runID).to.equal('C005')
122
+ expect(trip.isRailReplacementBus).to.be.false
123
+
124
+ expect(nextTrip.stopTimings[0].stopName).to.equal('Flinders Street Railway Station')
125
+ expect(nextTrip.stopTimings[0].platform).to.equal('7')
126
+ })
127
+
128
+ it('It identify rail replacement bus trip', async () => {
129
+ let database = new LokiDatabaseConnection('test-db')
130
+ let stops = await database.createCollection('stops')
131
+ let routes = await database.createCollection('routes')
132
+ let trips = await database.createCollection('gtfs timetables')
133
+
134
+ let stopLoader = new StopsLoader(stopsFile, suburbs, TRANSIT_MODES.metroTrain, database)
135
+ await stopLoader.loadStops()
136
+
137
+ let routeLoader = new RouteLoader(routesFile, agencyFile, TRANSIT_MODES.metroTrain, database)
138
+ await routeLoader.loadRoutes()
139
+
140
+ let routeIDMap = routeLoader.getRouteIDMap()
141
+
142
+ let tripLoader = new TripLoader({
143
+ tripsFile, stopTimesFile,
144
+ calendarFile, calendarDatesFile
145
+ }, TRANSIT_MODES.metroTrain, database)
146
+
147
+ await tripLoader.loadTrips({ routeIDMap })
148
+
149
+ let trip = await trips.findDocument({ runID: 'BL000' })
150
+
151
+ expect(trip).to.not.be.null
152
+ expect(trip.tripID).to.equal('02-LIL--13-T5_w3-BL000')
153
+ expect(trip.shapeID).to.equal('2-LIL-vpt-13.133.R')
154
+ expect(trip.routeGTFSID).to.equal('2-LIL')
155
+ expect(trip.block).to.equal(null)
156
+ expect(trip.isRailReplacementBus).to.be.true
157
+
158
+ expect(trip.stopTimings[0].stopName).to.equal('Burnley Railway Station')
159
+ expect(trip.stopTimings[0].platform).to.equal('RRB')
160
+
161
+ expect(trip.stopTimings[3].stopName).to.equal('Parliament Railway Station')
162
+ expect(trip.stopTimings[3].platform).to.equal('RRB')
163
+ })
164
+ })
165
+ })