gtfs 3.2.5 → 3.4.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.
- package/@types/index.d.ts +337 -75
- package/CHANGELOG.md +34 -0
- package/README.md +191 -13
- package/bin/gtfsrealtime-update.js +32 -0
- package/config-sample-rtupdates.json +28 -0
- package/lib/advancedQuery.js +45 -0
- package/lib/db.js +1 -1
- package/lib/gtfs/shapes.js +3 -2
- package/lib/gtfs/stops.js +2 -0
- package/lib/gtfs-realtime/service-alerts.js +32 -0
- package/lib/gtfs-realtime/stop-times-updates.js +30 -0
- package/lib/gtfs-realtime/trip-updates.js +30 -0
- package/lib/gtfs-realtime/vehicle-positions.js +30 -0
- package/lib/gtfs.js +36 -1
- package/lib/import.js +320 -7
- package/lib/non-standard/trips-dated-vehicle-journey.js +30 -0
- package/lib/utils.js +29 -4
- package/models/gtfs-realtime/service-alert-targets.js +38 -0
- package/models/gtfs-realtime/service-alerts.js +60 -0
- package/models/gtfs-realtime/stop-times-updates.js +67 -0
- package/models/gtfs-realtime/trip-updates.js +50 -0
- package/models/gtfs-realtime/vehicle-positions.js +73 -0
- package/models/models.js +13 -0
- package/models/non-standard/timetable-pages.js +0 -1
- package/models/non-standard/trips-dated-vehicle-journey.js +32 -0
- package/package.json +15 -12
- package/test/mocha/advanced-query.js +80 -0
- package/test/mocha/exec-raw-query.js +39 -0
- package/test/mocha/get-shapes-as-geojson.js +15 -0
- package/test/mocha/get-shapes.js +28 -0
- package/test/mocha/get-stops-as-geojson.js +14 -0
- package/test/mocha/get-stops.js +49 -0
- package/test/mocha/run-raw-query.js +54 -0
package/lib/import.js
CHANGED
|
@@ -9,6 +9,8 @@ import stripBomStream from 'strip-bom-stream';
|
|
|
9
9
|
import { dir } from 'tmp-promise';
|
|
10
10
|
import untildify from 'untildify';
|
|
11
11
|
import mapSeries from 'promise-map-series';
|
|
12
|
+
import gtfsrt from 'gtfs-realtime-bindings';
|
|
13
|
+
import sqlString from 'sqlstring-sqlite';
|
|
12
14
|
|
|
13
15
|
import models from '../models/models.js';
|
|
14
16
|
import { openDb, setupDb } from './db.js';
|
|
@@ -22,6 +24,7 @@ import {
|
|
|
22
24
|
calculateSecondsFromMidnight,
|
|
23
25
|
setDefaultConfig,
|
|
24
26
|
validateConfigForImport,
|
|
27
|
+
convertLongTimeToDate,
|
|
25
28
|
} from './utils.js';
|
|
26
29
|
|
|
27
30
|
const downloadFiles = async (task) => {
|
|
@@ -31,7 +34,7 @@ const downloadFiles = async (task) => {
|
|
|
31
34
|
|
|
32
35
|
const response = await fetch(task.agency_url, {
|
|
33
36
|
method: 'GET',
|
|
34
|
-
headers: task.
|
|
37
|
+
headers: task.headers || {},
|
|
35
38
|
});
|
|
36
39
|
|
|
37
40
|
if (response.status !== 200) {
|
|
@@ -44,6 +47,249 @@ const downloadFiles = async (task) => {
|
|
|
44
47
|
task.log('Download successful');
|
|
45
48
|
};
|
|
46
49
|
|
|
50
|
+
const downloadGtfsRealtimeData = async (url, headers) => {
|
|
51
|
+
const response = await fetch(url, {
|
|
52
|
+
method: 'GET',
|
|
53
|
+
headers: { ...{}, ...headers, ...{ 'Accept-Encoding': 'gzip' } },
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (response.status !== 200) {
|
|
57
|
+
throw new Error('Couldn’t download files');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const buffer = await response.arrayBuffer();
|
|
61
|
+
return gtfsrt.transit_realtime.FeedMessage.decode(Buffer.from(buffer));
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
function getDescendantProp(obj, desc, defaultvalue) {
|
|
65
|
+
if (desc === undefined) return defaultvalue;
|
|
66
|
+
const arr = desc.split('.');
|
|
67
|
+
while (arr.length) {
|
|
68
|
+
const nextKey = arr.shift();
|
|
69
|
+
if (nextKey.includes('[')) {
|
|
70
|
+
const arrayKey = nextKey.match(/(\w*)\[(\d+)\]/);
|
|
71
|
+
if (!obj[arrayKey[1]]) return defaultvalue;
|
|
72
|
+
if (!obj[arrayKey[1]][arrayKey[2]]) return defaultvalue;
|
|
73
|
+
obj = obj[arrayKey[1]][arrayKey[2]];
|
|
74
|
+
} else {
|
|
75
|
+
if (!obj[nextKey]) return defaultvalue;
|
|
76
|
+
obj = obj[nextKey];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (obj.__isLong__) return convertLongTimeToDate(obj);
|
|
81
|
+
|
|
82
|
+
return obj;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const markRealtimeDataStale = async (db, log) => {
|
|
86
|
+
const vehiclePositionModel = models.find(
|
|
87
|
+
(x) => x.filenameBase === 'vehicle_positions'
|
|
88
|
+
);
|
|
89
|
+
const tripUpdatesModel = models.find(
|
|
90
|
+
(x) => x.filenameBase === 'trip_updates'
|
|
91
|
+
);
|
|
92
|
+
const stopTimesUpdatesModel = models.find(
|
|
93
|
+
(x) => x.filenameBase === 'stop_times_updates'
|
|
94
|
+
);
|
|
95
|
+
const serviceAlertsModel = models.find(
|
|
96
|
+
(x) => x.filenameBase === 'service_alerts'
|
|
97
|
+
);
|
|
98
|
+
const serviceAlertTargetsModel = models.find(
|
|
99
|
+
(x) => x.filenameBase === 'service_alert_targets'
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// Mark all data as stale
|
|
103
|
+
log(`Marking GTFS-Realtime data as stale..`);
|
|
104
|
+
await db.run(`UPDATE ${vehiclePositionModel.filenameBase} SET isUpdated=0`);
|
|
105
|
+
await db.run(`UPDATE ${tripUpdatesModel.filenameBase} SET isUpdated=0`);
|
|
106
|
+
await db.run(`UPDATE ${stopTimesUpdatesModel.filenameBase} SET isUpdated=0`);
|
|
107
|
+
await db.run(`UPDATE ${serviceAlertsModel.filenameBase} SET isUpdated=0`);
|
|
108
|
+
await db.run(
|
|
109
|
+
`UPDATE ${serviceAlertTargetsModel.filenameBase} SET isUpdated=0`
|
|
110
|
+
);
|
|
111
|
+
log(`Marked GTFS-Realtime data as stale\r`, true);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const cleanStaleRealtimeData = async (db, log) => {
|
|
115
|
+
const vehiclePositionModel = models.find(
|
|
116
|
+
(x) => x.filenameBase === 'vehicle_positions'
|
|
117
|
+
);
|
|
118
|
+
const tripUpdatesModel = models.find(
|
|
119
|
+
(x) => x.filenameBase === 'trip_updates'
|
|
120
|
+
);
|
|
121
|
+
const stopTimesUpdatesModel = models.find(
|
|
122
|
+
(x) => x.filenameBase === 'stop_times_updates'
|
|
123
|
+
);
|
|
124
|
+
const serviceAlertsModel = models.find(
|
|
125
|
+
(x) => x.filenameBase === 'service_alerts'
|
|
126
|
+
);
|
|
127
|
+
const serviceAlertTargetsModel = models.find(
|
|
128
|
+
(x) => x.filenameBase === 'service_alert_targets'
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
log(`Cleaning stale GRFS-RT data..`);
|
|
132
|
+
await db.run(
|
|
133
|
+
`DELETE FROM ${vehiclePositionModel.filenameBase} WHERE isUpdated=0`
|
|
134
|
+
);
|
|
135
|
+
await db.run(
|
|
136
|
+
`DELETE FROM ${tripUpdatesModel.filenameBase} WHERE isUpdated=0`
|
|
137
|
+
);
|
|
138
|
+
await db.run(
|
|
139
|
+
`DELETE FROM ${stopTimesUpdatesModel.filenameBase} WHERE isUpdated=0`
|
|
140
|
+
);
|
|
141
|
+
await db.run(
|
|
142
|
+
`DELETE FROM ${serviceAlertsModel.filenameBase} WHERE isUpdated=0`
|
|
143
|
+
);
|
|
144
|
+
await db.run(
|
|
145
|
+
`DELETE FROM ${serviceAlertTargetsModel.filenameBase} WHERE isUpdated=0`
|
|
146
|
+
);
|
|
147
|
+
log(`Cleaned stale GTFS-Realtime data\r`, true);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const updateRealtimeData = async (task) => {
|
|
151
|
+
const model = {
|
|
152
|
+
vehicle_positions: models.find(
|
|
153
|
+
(x) => x.filenameBase === 'vehicle_positions'
|
|
154
|
+
),
|
|
155
|
+
trip_updates: models.find((x) => x.filenameBase === 'trip_updates'),
|
|
156
|
+
stop_times_updates: models.find(
|
|
157
|
+
(x) => x.filenameBase === 'stop_times_updates'
|
|
158
|
+
),
|
|
159
|
+
service_alerts: models.find((x) => x.filenameBase === 'service_alerts'),
|
|
160
|
+
service_alert_targets: models.find(
|
|
161
|
+
(x) => x.filenameBase === 'service_alert_targets'
|
|
162
|
+
),
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const fields = {
|
|
166
|
+
vehicle_positions: model.vehicle_positions.schema
|
|
167
|
+
.map((column) => column.name)
|
|
168
|
+
.join(', '),
|
|
169
|
+
trip_updates: model.trip_updates.schema
|
|
170
|
+
.map((column) => column.name)
|
|
171
|
+
.join(', '),
|
|
172
|
+
stop_times_updates: model.stop_times_updates.schema
|
|
173
|
+
.map((column) => column.name)
|
|
174
|
+
.join(', '),
|
|
175
|
+
service_alerts: model.service_alerts.schema
|
|
176
|
+
.map((column) => column.name)
|
|
177
|
+
.join(', '),
|
|
178
|
+
service_alert_targets: model.service_alert_targets.schema
|
|
179
|
+
.map((column) => column.name)
|
|
180
|
+
.join(', '),
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
task.log(
|
|
184
|
+
`Starting GTFS-Realtime import from ${task.realtime_urls.length} urls`
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
for (const realtimeUrl of task.realtime_urls) {
|
|
188
|
+
task.log(`Downloading GTFS-Realtime from ${realtimeUrl}`);
|
|
189
|
+
// eslint-disable-next-line no-await-in-loop
|
|
190
|
+
const tripUpdateData = await downloadGtfsRealtimeData(
|
|
191
|
+
realtimeUrl,
|
|
192
|
+
task.realtime_headers
|
|
193
|
+
);
|
|
194
|
+
task.log(`Download successful`);
|
|
195
|
+
|
|
196
|
+
let totalLineCount = 0;
|
|
197
|
+
for (const entity of tripUpdateData.entity) {
|
|
198
|
+
// Determine the type of GTFS-Realtime
|
|
199
|
+
let gtfsRealtimeType = null;
|
|
200
|
+
if (entity.vehicle) {
|
|
201
|
+
gtfsRealtimeType = 'vehicle_positions';
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (entity.tripUpdate) {
|
|
205
|
+
gtfsRealtimeType = 'trip_updates';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (entity.alert) {
|
|
209
|
+
gtfsRealtimeType = 'service_alerts';
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!gtfsRealtimeType) {
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Do base processing
|
|
217
|
+
const fieldValues = model[gtfsRealtimeType].schema.map((column) =>
|
|
218
|
+
sqlString.escape(
|
|
219
|
+
getDescendantProp(entity, column.source, column.default)
|
|
220
|
+
)
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
// eslint-disable-next-line no-await-in-loop
|
|
224
|
+
await task.db
|
|
225
|
+
.run(
|
|
226
|
+
`REPLACE INTO ${model[gtfsRealtimeType].filenameBase} (${
|
|
227
|
+
fields[gtfsRealtimeType]
|
|
228
|
+
}) VALUES (${fieldValues.join(', ')})`
|
|
229
|
+
)
|
|
230
|
+
.catch((error) => {
|
|
231
|
+
task.warn('Import error: ' + error.message);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Special processing for tripUpdates
|
|
235
|
+
if (entity.tripUpdate) {
|
|
236
|
+
const stopUpdateArray = [];
|
|
237
|
+
for (const stopUpdate of entity.tripUpdate.stopTimeUpdate) {
|
|
238
|
+
stopUpdate.parent = entity;
|
|
239
|
+
const subValues = model.stop_times_updates.schema.map((column) =>
|
|
240
|
+
sqlString.escape(
|
|
241
|
+
getDescendantProp(stopUpdate, column.source, column.default)
|
|
242
|
+
)
|
|
243
|
+
);
|
|
244
|
+
stopUpdateArray.push(`(${subValues.join(', ')})`);
|
|
245
|
+
totalLineCount++;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// eslint-disable-next-line no-await-in-loop
|
|
249
|
+
await task.db
|
|
250
|
+
.run(
|
|
251
|
+
`REPLACE INTO ${model.stop_times_updates.filenameBase} (${
|
|
252
|
+
fields.stop_times_updates
|
|
253
|
+
}) VALUES ${stopUpdateArray.join(', ')}`
|
|
254
|
+
)
|
|
255
|
+
.catch((error) => {
|
|
256
|
+
task.warn('Import error: ' + error.message);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Special processing for serviceAlerts
|
|
261
|
+
if (entity.alert) {
|
|
262
|
+
const alertTargetArray = [];
|
|
263
|
+
for (const informedEntity of entity.alert.informedEntity) {
|
|
264
|
+
informedEntity.parent = entity;
|
|
265
|
+
const subValues = model.service_alert_targets.schema.map((column) =>
|
|
266
|
+
sqlString.escape(
|
|
267
|
+
getDescendantProp(informedEntity, column.source, column.default)
|
|
268
|
+
)
|
|
269
|
+
);
|
|
270
|
+
alertTargetArray.push(`(${subValues.join(', ')})`);
|
|
271
|
+
totalLineCount++;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// eslint-disable-next-line no-await-in-loop
|
|
275
|
+
await task.db
|
|
276
|
+
.run(
|
|
277
|
+
`REPLACE INTO ${model.service_alert_targets.filenameBase} (${
|
|
278
|
+
fields.service_alert_targets
|
|
279
|
+
}) VALUES ${alertTargetArray.join(', ')}`
|
|
280
|
+
)
|
|
281
|
+
.catch((error) => {
|
|
282
|
+
task.warn('Import error: ' + error.message);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
task.log(`Importing - ${totalLineCount++} entries imported\r`, true);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
task.log(`GTFS-Realtime data import complete`);
|
|
291
|
+
};
|
|
292
|
+
|
|
47
293
|
const getTextFiles = async (folderPath) => {
|
|
48
294
|
const files = await readdir(folderPath);
|
|
49
295
|
return files.filter((filename) => filename.slice(-3) === 'txt');
|
|
@@ -290,6 +536,12 @@ const importFiles = (task) =>
|
|
|
290
536
|
return;
|
|
291
537
|
}
|
|
292
538
|
|
|
539
|
+
// If the model is a database/gtfs-realtime model then just silently exit as we dont really care here
|
|
540
|
+
if (model.extension === 'gtfs-realtime') {
|
|
541
|
+
resolve();
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
|
|
293
545
|
const filepath = path.join(
|
|
294
546
|
task.downloadDir,
|
|
295
547
|
`${model.filenameBase}.txt`
|
|
@@ -348,7 +600,7 @@ const importFiles = (task) =>
|
|
|
348
600
|
})
|
|
349
601
|
);
|
|
350
602
|
|
|
351
|
-
|
|
603
|
+
export async function importGtfs(initialConfig) {
|
|
352
604
|
const config = setDefaultConfig(initialConfig);
|
|
353
605
|
validateConfigForImport(config);
|
|
354
606
|
const log = _log(config);
|
|
@@ -384,7 +636,9 @@ const importGtfs = async (initialConfig) => {
|
|
|
384
636
|
const task = {
|
|
385
637
|
exclude: agency.exclude,
|
|
386
638
|
agency_url: agency.url,
|
|
387
|
-
|
|
639
|
+
headers: agency.headers || false,
|
|
640
|
+
realtime_headers: agency.realtimeHeaders || false,
|
|
641
|
+
realtime_urls: agency.realtimeUrls || false,
|
|
388
642
|
downloadDir: path,
|
|
389
643
|
path: agency.path,
|
|
390
644
|
csvOptions: config.csvOptions || {},
|
|
@@ -407,11 +661,70 @@ const importGtfs = async (initialConfig) => {
|
|
|
407
661
|
await readFiles(task);
|
|
408
662
|
await importFiles(task);
|
|
409
663
|
|
|
664
|
+
if (task.realtime_urls) {
|
|
665
|
+
await updateRealtimeData(task);
|
|
666
|
+
}
|
|
667
|
+
|
|
410
668
|
cleanup();
|
|
411
|
-
task.log('Completed GTFS import');
|
|
412
669
|
});
|
|
413
670
|
|
|
414
|
-
log(`Completed GTFS import for ${pluralize('
|
|
415
|
-
}
|
|
671
|
+
log(`Completed GTFS import for ${pluralize('agency', agencyCount, true)}\n`);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
export async function updateGtfsRealtime(initialConfig) {
|
|
675
|
+
const config = setDefaultConfig(initialConfig);
|
|
676
|
+
validateConfigForImport(config);
|
|
677
|
+
const log = _log(config);
|
|
678
|
+
const logError = _logError(config);
|
|
679
|
+
const logWarning = _logWarning(config);
|
|
680
|
+
const db = await openDb(config).catch((error) => {
|
|
681
|
+
if (error instanceof Error && error.code === 'SQLITE_CANTOPEN') {
|
|
682
|
+
logError(
|
|
683
|
+
`Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
throw error;
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
const agencyCount = config.agencies.length;
|
|
691
|
+
log(
|
|
692
|
+
`Starting GTFS-Realtime refresh for ${pluralize(
|
|
693
|
+
'agencies',
|
|
694
|
+
agencyCount,
|
|
695
|
+
true
|
|
696
|
+
)} using SQLite database at ${config.sqlitePath}`
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
await markRealtimeDataStale(db, log);
|
|
416
700
|
|
|
417
|
-
|
|
701
|
+
await mapSeries(config.agencies, async (agency) => {
|
|
702
|
+
const task = {
|
|
703
|
+
realtime_headers: agency.realtimeHeaders || false,
|
|
704
|
+
realtime_urls: agency.realtimeUrls || false,
|
|
705
|
+
db,
|
|
706
|
+
log(message, overwrite) {
|
|
707
|
+
log(message, overwrite);
|
|
708
|
+
},
|
|
709
|
+
warn(message) {
|
|
710
|
+
logWarning(message);
|
|
711
|
+
},
|
|
712
|
+
error(message) {
|
|
713
|
+
logError(message);
|
|
714
|
+
},
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
if (task.realtime_urls) {
|
|
718
|
+
await updateRealtimeData(task);
|
|
719
|
+
}
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
await cleanStaleRealtimeData(db, log);
|
|
723
|
+
log(
|
|
724
|
+
`Completed GTFS-Realtime refresh for ${pluralize(
|
|
725
|
+
'agencies',
|
|
726
|
+
agencyCount,
|
|
727
|
+
true
|
|
728
|
+
)}\n`
|
|
729
|
+
);
|
|
730
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import sqlString from 'sqlstring-sqlite';
|
|
2
|
+
|
|
3
|
+
import { getDb } from '../db.js';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
formatOrderByClause,
|
|
7
|
+
formatSelectClause,
|
|
8
|
+
formatWhereClauses,
|
|
9
|
+
} from '../utils.js';
|
|
10
|
+
import directions from '../../models/non-standard/trips-dated-vehicle-journey.js';
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
* Returns an array of all directions that match the query parameters.
|
|
14
|
+
*/
|
|
15
|
+
export async function getTripsDatedVehicleJourneys(
|
|
16
|
+
query = {},
|
|
17
|
+
fields = [],
|
|
18
|
+
orderBy = [],
|
|
19
|
+
options = {}
|
|
20
|
+
) {
|
|
21
|
+
const db = options.db ?? (await getDb());
|
|
22
|
+
const tableName = sqlString.escapeId(directions.filenameBase);
|
|
23
|
+
const selectClause = formatSelectClause(fields);
|
|
24
|
+
const whereClause = formatWhereClauses(query);
|
|
25
|
+
const orderByClause = formatOrderByClause(orderBy);
|
|
26
|
+
|
|
27
|
+
return db.all(
|
|
28
|
+
`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`
|
|
29
|
+
);
|
|
30
|
+
}
|
package/lib/utils.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import sqlString from 'sqlstring-sqlite';
|
|
2
|
+
import Long from 'long';
|
|
2
3
|
|
|
3
4
|
/*
|
|
4
5
|
* Validate configuration.
|
|
@@ -33,6 +34,11 @@ export function setDefaultConfig(initialConfig) {
|
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
export function convertLongTimeToDate(longDate) {
|
|
38
|
+
const { high, low, unsigned } = longDate;
|
|
39
|
+
return new Date(new Long(low, high, unsigned).toInt() * 1000).toISOString();
|
|
40
|
+
}
|
|
41
|
+
|
|
36
42
|
/*
|
|
37
43
|
* Calculate seconds from midnight for HH:mm:ss
|
|
38
44
|
*/
|
|
@@ -46,14 +52,33 @@ export function calculateSecondsFromMidnight(time) {
|
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
export function formatSelectClause(fields) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
if (Array.isArray(fields)) {
|
|
56
|
+
const selectItem =
|
|
57
|
+
fields.length > 0
|
|
58
|
+
? fields.map((fieldName) => sqlString.escapeId(fieldName)).join(', ')
|
|
59
|
+
: '*';
|
|
60
|
+
return `SELECT ${selectItem}`;
|
|
61
|
+
}
|
|
53
62
|
|
|
63
|
+
const selectItem = Object.entries(fields)
|
|
64
|
+
.map(
|
|
65
|
+
(key) => `${sqlString.escapeId(key[0])} AS ${sqlString.escapeId(key[1])}`
|
|
66
|
+
)
|
|
67
|
+
.join(', ');
|
|
54
68
|
return `SELECT ${selectItem}`;
|
|
55
69
|
}
|
|
56
70
|
|
|
71
|
+
export function formatJoinClause(joinObject) {
|
|
72
|
+
return joinObject
|
|
73
|
+
.map(
|
|
74
|
+
(data) =>
|
|
75
|
+
`${data.type ? data.type + ' JOIN' : 'INNER JOIN'} ${sqlString.escapeId(
|
|
76
|
+
data.table
|
|
77
|
+
)} ON ${data.on}`
|
|
78
|
+
)
|
|
79
|
+
.join(' ');
|
|
80
|
+
}
|
|
81
|
+
|
|
57
82
|
export function formatWhereClause(key, value) {
|
|
58
83
|
if (Array.isArray(value)) {
|
|
59
84
|
return `${sqlString.escapeId(key)} IN (${value
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const model = {
|
|
2
|
+
filenameBase: 'service_alert_targets',
|
|
3
|
+
extension: 'gtfs-realtime',
|
|
4
|
+
schema: [
|
|
5
|
+
{
|
|
6
|
+
name: 'alert_id',
|
|
7
|
+
type: 'varchar(255)',
|
|
8
|
+
required: true,
|
|
9
|
+
primary: true,
|
|
10
|
+
index: true,
|
|
11
|
+
source: 'parent.id',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: 'stop_id',
|
|
15
|
+
type: 'varchar(255)',
|
|
16
|
+
index: true,
|
|
17
|
+
source: 'stopId',
|
|
18
|
+
default: null,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'route_id',
|
|
22
|
+
type: 'varchar(255)',
|
|
23
|
+
index: true,
|
|
24
|
+
source: 'routeId',
|
|
25
|
+
default: null,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'isUpdated',
|
|
29
|
+
type: 'integer',
|
|
30
|
+
required: true,
|
|
31
|
+
min: 0,
|
|
32
|
+
max: 1,
|
|
33
|
+
default: 1,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default model;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const model = {
|
|
2
|
+
filenameBase: 'service_alerts',
|
|
3
|
+
extension: 'gtfs-realtime',
|
|
4
|
+
schema: [
|
|
5
|
+
{
|
|
6
|
+
name: 'id',
|
|
7
|
+
type: 'varchar(255)',
|
|
8
|
+
required: true,
|
|
9
|
+
primary: true,
|
|
10
|
+
index: true,
|
|
11
|
+
source: 'id',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: 'cause',
|
|
15
|
+
type: 'integer',
|
|
16
|
+
required: true,
|
|
17
|
+
min: 0,
|
|
18
|
+
source: 'alert.cause',
|
|
19
|
+
default: 0,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'start_time',
|
|
23
|
+
type: 'varchar(255)',
|
|
24
|
+
required: true,
|
|
25
|
+
source: 'alert.activePeriod[0].start',
|
|
26
|
+
default: '',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'end_time',
|
|
30
|
+
type: 'varchar(255)',
|
|
31
|
+
required: true,
|
|
32
|
+
source: 'alert.activePeriod[0].end',
|
|
33
|
+
default: '',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'headline',
|
|
37
|
+
type: 'varchar(2048)',
|
|
38
|
+
required: true,
|
|
39
|
+
source: 'alert.headerText.translation[0].text',
|
|
40
|
+
default: '',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'description',
|
|
44
|
+
type: 'varchar(4096)',
|
|
45
|
+
required: true,
|
|
46
|
+
source: 'alert.descriptionText.translation[0].text',
|
|
47
|
+
default: '',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'isUpdated',
|
|
51
|
+
type: 'integer',
|
|
52
|
+
required: true,
|
|
53
|
+
min: 0,
|
|
54
|
+
max: 1,
|
|
55
|
+
default: 1,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default model;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const model = {
|
|
2
|
+
filenameBase: 'stop_times_updates',
|
|
3
|
+
extension: 'gtfs-realtime',
|
|
4
|
+
schema: [
|
|
5
|
+
{
|
|
6
|
+
name: 'trip_id',
|
|
7
|
+
type: 'varchar(255)',
|
|
8
|
+
index: true,
|
|
9
|
+
source: 'parent.tripUpdate.trip.tripId',
|
|
10
|
+
default: null,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'route_id',
|
|
14
|
+
type: 'varchar(255)',
|
|
15
|
+
index: true,
|
|
16
|
+
source: 'parent.tripUpdate.trip.routeId',
|
|
17
|
+
default: null,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'stop_id',
|
|
21
|
+
type: 'varchar(255)',
|
|
22
|
+
index: true,
|
|
23
|
+
source: 'stopId',
|
|
24
|
+
default: null,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'stop_sequence',
|
|
28
|
+
type: 'integer',
|
|
29
|
+
source: 'stopSequence',
|
|
30
|
+
default: null,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'arrival_delay',
|
|
34
|
+
type: 'integer',
|
|
35
|
+
source: 'arrival.delay',
|
|
36
|
+
default: null,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'departure_delay',
|
|
40
|
+
type: 'integer',
|
|
41
|
+
source: 'departure.delay',
|
|
42
|
+
default: null,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'departure_timestamp',
|
|
46
|
+
type: 'varchar(255)',
|
|
47
|
+
source: 'departure.time',
|
|
48
|
+
default: null,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'arrival_timestamp',
|
|
52
|
+
type: 'varchar(255)',
|
|
53
|
+
source: 'arrival.time',
|
|
54
|
+
default: null,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'isUpdated',
|
|
58
|
+
type: 'integer',
|
|
59
|
+
required: true,
|
|
60
|
+
min: 0,
|
|
61
|
+
max: 1,
|
|
62
|
+
default: 1,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default model;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const model = {
|
|
2
|
+
filenameBase: 'trip_updates',
|
|
3
|
+
extension: 'gtfs-realtime',
|
|
4
|
+
schema: [
|
|
5
|
+
{
|
|
6
|
+
name: 'update_id',
|
|
7
|
+
type: 'varchar(255)',
|
|
8
|
+
required: true,
|
|
9
|
+
primary: true,
|
|
10
|
+
index: true,
|
|
11
|
+
source: 'id',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: 'vehicle_id',
|
|
15
|
+
type: 'varchar(255)',
|
|
16
|
+
index: true,
|
|
17
|
+
source: 'tripUpdate.vehicle.id',
|
|
18
|
+
default: null,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'trip_id',
|
|
22
|
+
type: 'varchar(255)',
|
|
23
|
+
index: true,
|
|
24
|
+
source: 'tripUpdate.trip.tripId',
|
|
25
|
+
default: null,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'start_date',
|
|
29
|
+
type: 'varchar(255)',
|
|
30
|
+
source: 'tripUpdate.trip.startDate',
|
|
31
|
+
default: null,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'timestamp',
|
|
35
|
+
type: 'varchar(255)',
|
|
36
|
+
source: 'tripUpdate.timestamp',
|
|
37
|
+
default: null,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'isUpdated',
|
|
41
|
+
type: 'integer',
|
|
42
|
+
required: true,
|
|
43
|
+
min: 0,
|
|
44
|
+
max: 1,
|
|
45
|
+
default: 1,
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default model;
|