gtfs 3.8.0 → 4.0.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 +35 -60
- package/@types/tests.ts +18 -13
- package/CHANGELOG.md +10 -0
- package/README.md +227 -207
- package/lib/advancedQuery.js +8 -17
- package/lib/db.js +29 -59
- package/lib/export.js +16 -10
- package/lib/gtfs/agencies.js +8 -6
- package/lib/gtfs/areas.js +8 -11
- package/lib/gtfs/attributions.js +8 -6
- package/lib/gtfs/calendar-dates.js +8 -6
- package/lib/gtfs/calendars.js +8 -6
- package/lib/gtfs/fare-attributes.js +8 -6
- package/lib/gtfs/fare-leg-rules.js +8 -6
- package/lib/gtfs/fare-products.js +8 -6
- package/lib/gtfs/fare-rules.js +8 -6
- package/lib/gtfs/fare-transfer-rules.js +8 -6
- package/lib/gtfs/feed-info.js +8 -6
- package/lib/gtfs/frequencies.js +8 -6
- package/lib/gtfs/levels.js +8 -11
- package/lib/gtfs/pathways.js +8 -6
- package/lib/gtfs/routes.js +8 -11
- package/lib/gtfs/shapes.js +34 -39
- package/lib/gtfs/stop-areas.js +8 -6
- package/lib/gtfs/stop-times.js +8 -6
- package/lib/gtfs/stops.js +27 -33
- package/lib/gtfs/transfers.js +8 -6
- package/lib/gtfs/translations.js +8 -6
- package/lib/gtfs/trips.js +8 -11
- package/lib/gtfs-realtime/service-alerts.js +9 -7
- package/lib/gtfs-realtime/stop-times-updates.js +9 -7
- package/lib/gtfs-realtime/trip-updates.js +9 -7
- package/lib/gtfs-realtime/vehicle-positions.js +9 -7
- package/lib/gtfs-ride/board-alights.js +8 -6
- package/lib/gtfs-ride/ride-feed-infos.js +8 -6
- package/lib/gtfs-ride/rider-trips.js +8 -6
- package/lib/gtfs-ride/riderships.js +8 -6
- package/lib/gtfs-ride/trip-capacities.js +8 -6
- package/lib/gtfs.js +3 -9
- package/lib/import.js +195 -202
- package/lib/non-standard/directions.js +8 -6
- package/lib/non-standard/stop-attributes.js +8 -6
- package/lib/non-standard/timetable-notes-references.js +8 -6
- package/lib/non-standard/timetable-notes.js +8 -6
- package/lib/non-standard/timetable-pages.js +8 -6
- package/lib/non-standard/timetable-stop-order.js +8 -6
- package/lib/non-standard/timetables.js +8 -6
- package/lib/non-standard/trips-dated-vehicle-journey.js +9 -7
- package/package.json +2 -3
- package/test/mocha/advanced-query.js +9 -15
- package/test/mocha/export-gtfs.js +5 -6
- package/test/mocha/fare-transfer-rules.js +32 -0
- package/test/mocha/get-agencies.js +13 -19
- package/test/mocha/get-areas.js +27 -0
- package/test/mocha/get-attributions.js +7 -13
- package/test/mocha/get-board-alights.js +7 -13
- package/test/mocha/get-calendar-dates.js +11 -17
- package/test/mocha/get-calendars.js +11 -17
- package/test/mocha/get-directions.js +7 -13
- package/test/mocha/get-fare-attributes.js +9 -15
- package/test/mocha/get-fare-leg-rules.js +27 -0
- package/test/mocha/get-fare-products.js +27 -0
- package/test/mocha/get-fare-rules.js +9 -15
- package/test/mocha/get-feed-info.js +7 -13
- package/test/mocha/get-frequencies.js +7 -13
- package/test/mocha/get-levels.js +7 -7
- package/test/mocha/get-pathways.js +7 -13
- package/test/mocha/get-ride-feed-infos.js +7 -13
- package/test/mocha/get-rider-trips.js +7 -13
- package/test/mocha/get-riderships.js +7 -13
- package/test/mocha/get-routes.js +13 -13
- package/test/mocha/get-shapes-as-geojson.js +14 -15
- package/test/mocha/get-shapes.js +25 -25
- package/test/mocha/get-stop-attributes.js +7 -13
- package/test/mocha/get-stops-as-geojson.js +13 -19
- package/test/mocha/get-stops.js +19 -19
- package/test/mocha/get-stoptimes.js +11 -17
- package/test/mocha/get-timetable-pages.js +7 -13
- package/test/mocha/get-timetable-stop-orders.js +6 -7
- package/test/mocha/get-timetables.js +7 -13
- package/test/mocha/get-transfers.js +7 -13
- package/test/mocha/get-translations.js +7 -13
- package/test/mocha/get-trip-capacities.js +7 -13
- package/test/mocha/get-trips.js +9 -9
- package/test/mocha/import-gtfs.js +11 -11
- package/test/mocha/{get-db.js → open-db.js} +37 -29
- package/test/mocha/raw-query.js +34 -0
- package/test/mocha/exec-raw-query.js +0 -39
- package/test/mocha/run-raw-query.js +0 -54
package/lib/import.js
CHANGED
|
@@ -13,7 +13,7 @@ import gtfsrt from 'gtfs-realtime-bindings';
|
|
|
13
13
|
import sqlString from 'sqlstring-sqlite';
|
|
14
14
|
|
|
15
15
|
import models from '../models/models.js';
|
|
16
|
-
import { openDb
|
|
16
|
+
import { openDb } from './db.js';
|
|
17
17
|
import { unzip } from './file-utils.js';
|
|
18
18
|
import {
|
|
19
19
|
log as _log,
|
|
@@ -82,7 +82,7 @@ function getDescendantProp(obj, desc, defaultvalue) {
|
|
|
82
82
|
return obj;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
const markRealtimeDataStale =
|
|
85
|
+
const markRealtimeDataStale = (db, log) => {
|
|
86
86
|
const vehiclePositionModel = models.find(
|
|
87
87
|
(x) => x.filenameBase === 'vehicle_positions'
|
|
88
88
|
);
|
|
@@ -101,17 +101,21 @@ const markRealtimeDataStale = async (db, log) => {
|
|
|
101
101
|
|
|
102
102
|
// Mark all data as stale
|
|
103
103
|
log(`Marking GTFS-Realtime data as stale..`);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
db.prepare(
|
|
105
|
+
`UPDATE ${vehiclePositionModel.filenameBase} SET isUpdated=0`
|
|
106
|
+
).run();
|
|
107
|
+
db.prepare(`UPDATE ${tripUpdatesModel.filenameBase} SET isUpdated=0`).run();
|
|
108
|
+
db.prepare(
|
|
109
|
+
`UPDATE ${stopTimesUpdatesModel.filenameBase} SET isUpdated=0`
|
|
110
|
+
).run();
|
|
111
|
+
db.prepare(`UPDATE ${serviceAlertsModel.filenameBase} SET isUpdated=0`).run();
|
|
112
|
+
db.prepare(
|
|
109
113
|
`UPDATE ${serviceAlertTargetsModel.filenameBase} SET isUpdated=0`
|
|
110
|
-
);
|
|
114
|
+
).run();
|
|
111
115
|
log(`Marked GTFS-Realtime data as stale\r`, true);
|
|
112
116
|
};
|
|
113
117
|
|
|
114
|
-
const cleanStaleRealtimeData =
|
|
118
|
+
const cleanStaleRealtimeData = (db, log) => {
|
|
115
119
|
const vehiclePositionModel = models.find(
|
|
116
120
|
(x) => x.filenameBase === 'vehicle_positions'
|
|
117
121
|
);
|
|
@@ -129,21 +133,21 @@ const cleanStaleRealtimeData = async (db, log) => {
|
|
|
129
133
|
);
|
|
130
134
|
|
|
131
135
|
log(`Cleaning stale GTFS-RT data..`);
|
|
132
|
-
|
|
136
|
+
db.prepare(
|
|
133
137
|
`DELETE FROM ${vehiclePositionModel.filenameBase} WHERE isUpdated=0`
|
|
134
|
-
);
|
|
135
|
-
|
|
138
|
+
).run();
|
|
139
|
+
db.prepare(
|
|
136
140
|
`DELETE FROM ${tripUpdatesModel.filenameBase} WHERE isUpdated=0`
|
|
137
|
-
);
|
|
138
|
-
|
|
141
|
+
).run();
|
|
142
|
+
db.prepare(
|
|
139
143
|
`DELETE FROM ${stopTimesUpdatesModel.filenameBase} WHERE isUpdated=0`
|
|
140
|
-
);
|
|
141
|
-
|
|
144
|
+
).run();
|
|
145
|
+
db.prepare(
|
|
142
146
|
`DELETE FROM ${serviceAlertsModel.filenameBase} WHERE isUpdated=0`
|
|
143
|
-
);
|
|
144
|
-
|
|
147
|
+
).run();
|
|
148
|
+
db.prepare(
|
|
145
149
|
`DELETE FROM ${serviceAlertTargetsModel.filenameBase} WHERE isUpdated=0`
|
|
146
|
-
);
|
|
150
|
+
).run();
|
|
147
151
|
log(`Cleaned stale GTFS-Realtime data\r`, true);
|
|
148
152
|
};
|
|
149
153
|
|
|
@@ -220,16 +224,17 @@ const updateRealtimeData = async (task) => {
|
|
|
220
224
|
)
|
|
221
225
|
);
|
|
222
226
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
227
|
+
try {
|
|
228
|
+
task.db
|
|
229
|
+
.prepare(
|
|
230
|
+
`REPLACE INTO ${model[gtfsRealtimeType].filenameBase} (${
|
|
231
|
+
fields[gtfsRealtimeType]
|
|
232
|
+
}) VALUES (${fieldValues.join(', ')})`
|
|
233
|
+
)
|
|
234
|
+
.run();
|
|
235
|
+
} catch (error) {
|
|
236
|
+
task.warn('Import error: ' + error.message);
|
|
237
|
+
}
|
|
233
238
|
|
|
234
239
|
// Special processing for tripUpdates
|
|
235
240
|
if (entity.tripUpdate) {
|
|
@@ -245,16 +250,17 @@ const updateRealtimeData = async (task) => {
|
|
|
245
250
|
totalLineCount++;
|
|
246
251
|
}
|
|
247
252
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
253
|
+
try {
|
|
254
|
+
task.db
|
|
255
|
+
.prepare(
|
|
256
|
+
`REPLACE INTO ${model.stop_times_updates.filenameBase} (${
|
|
257
|
+
fields.stop_times_updates
|
|
258
|
+
}) VALUES ${stopUpdateArray.join(', ')}`
|
|
259
|
+
)
|
|
260
|
+
.run();
|
|
261
|
+
} catch (error) {
|
|
262
|
+
task.warn('Import error: ' + error.message);
|
|
263
|
+
}
|
|
258
264
|
}
|
|
259
265
|
|
|
260
266
|
// Special processing for serviceAlerts
|
|
@@ -271,16 +277,17 @@ const updateRealtimeData = async (task) => {
|
|
|
271
277
|
totalLineCount++;
|
|
272
278
|
}
|
|
273
279
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
280
|
+
try {
|
|
281
|
+
task.db
|
|
282
|
+
.prepare(
|
|
283
|
+
`REPLACE INTO ${model.service_alert_targets.filenameBase} (${
|
|
284
|
+
fields.service_alert_targets
|
|
285
|
+
}) VALUES ${alertTargetArray.join(', ')}`
|
|
286
|
+
)
|
|
287
|
+
.run();
|
|
288
|
+
} catch (error) {
|
|
289
|
+
task.warn('Import error: ' + error.message);
|
|
290
|
+
}
|
|
284
291
|
}
|
|
285
292
|
|
|
286
293
|
task.log(`Importing - ${totalLineCount++} entries imported\r`, true);
|
|
@@ -355,159 +362,146 @@ const readFiles = async (task) => {
|
|
|
355
362
|
}
|
|
356
363
|
};
|
|
357
364
|
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
365
|
+
const createTables = (db) => {
|
|
366
|
+
for (const model of models) {
|
|
367
|
+
if (!model.schema) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
362
370
|
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
371
|
+
const columns = model.schema.map((column) => {
|
|
372
|
+
let check = '';
|
|
373
|
+
if (column.min !== undefined && column.max) {
|
|
374
|
+
check = `CHECK( ${column.name} >= ${column.min} AND ${column.name} <= ${column.max} )`;
|
|
375
|
+
} else if (column.min) {
|
|
376
|
+
check = `CHECK( ${column.name} >= ${column.min} )`;
|
|
377
|
+
} else if (column.max) {
|
|
378
|
+
check = `CHECK( ${column.name} <= ${column.max} )`;
|
|
368
379
|
}
|
|
369
380
|
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
} else if (column.max) {
|
|
377
|
-
check = `CHECK( ${column.name} <= ${column.max} )`;
|
|
378
|
-
}
|
|
381
|
+
const primary = column.primary ? 'PRIMARY KEY' : '';
|
|
382
|
+
const required = column.required ? 'NOT NULL' : '';
|
|
383
|
+
const columnDefault = column.default ? 'DEFAULT ' + column.default : '';
|
|
384
|
+
const columnCollation = column.nocase ? 'COLLATE NOCASE' : '';
|
|
385
|
+
return `${column.name} ${column.type} ${check} ${primary} ${required} ${columnDefault} ${columnCollation}`;
|
|
386
|
+
});
|
|
379
387
|
|
|
380
|
-
|
|
381
|
-
const required = column.required ? 'NOT NULL' : '';
|
|
382
|
-
const columnDefault = column.default ? 'DEFAULT ' + column.default : '';
|
|
383
|
-
const columnCollation = column.nocase ? 'COLLATE NOCASE' : '';
|
|
384
|
-
return `${column.name} ${column.type} ${check} ${primary} ${required} ${columnDefault} ${columnCollation}`;
|
|
385
|
-
});
|
|
388
|
+
db.prepare(`DROP TABLE IF EXISTS ${model.filenameBase};`).run();
|
|
386
389
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
+
db.prepare(
|
|
391
|
+
`CREATE TABLE ${model.filenameBase} (${columns.join(', ')});`
|
|
392
|
+
).run();
|
|
390
393
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
})
|
|
400
|
-
);
|
|
401
|
-
})
|
|
402
|
-
);
|
|
394
|
+
for (const column of model.schema.filter((column) => column.index)) {
|
|
395
|
+
const unique = column.index === 'unique' ? 'UNIQUE' : '';
|
|
396
|
+
db.prepare(
|
|
397
|
+
`CREATE ${unique} INDEX idx_${model.filenameBase}_${column.name} ON ${model.filenameBase} (${column.name});`
|
|
398
|
+
).run();
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
};
|
|
403
402
|
|
|
404
403
|
const formatLine = (line, model, totalLineCount) => {
|
|
405
404
|
const lineNumber = totalLineCount + 1;
|
|
406
|
-
for (const fieldName of Object.keys(line)) {
|
|
407
|
-
const columnSchema = model.schema.find(
|
|
408
|
-
(schema) => schema.name === fieldName
|
|
409
|
-
);
|
|
410
405
|
|
|
411
|
-
|
|
412
|
-
if (!columnSchema) {
|
|
413
|
-
delete line[fieldName];
|
|
414
|
-
continue;
|
|
415
|
-
}
|
|
406
|
+
const formattedLine = {};
|
|
416
407
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
delete line[fieldName];
|
|
420
|
-
}
|
|
408
|
+
for (const columnSchema of model.schema) {
|
|
409
|
+
const lineValue = line[columnSchema.name];
|
|
421
410
|
|
|
422
|
-
// Convert fields that should be integer
|
|
423
411
|
if (columnSchema.type === 'integer') {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
412
|
+
// Convert fields that should be integer
|
|
413
|
+
formattedLine[columnSchema.name] = Number.parseInt(lineValue, 10);
|
|
414
|
+
} else if (columnSchema.type === 'real') {
|
|
415
|
+
// Convert fields that should be float
|
|
416
|
+
formattedLine[columnSchema.name] = Number.parseFloat(lineValue);
|
|
417
|
+
} else {
|
|
418
|
+
formattedLine[columnSchema.name] = lineValue;
|
|
431
419
|
}
|
|
432
420
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}
|
|
421
|
+
if (
|
|
422
|
+
formattedLine[columnSchema.name] === '' ||
|
|
423
|
+
formattedLine[columnSchema.name] === undefined ||
|
|
424
|
+
formattedLine[columnSchema.name] === null ||
|
|
425
|
+
Number.isNaN(formattedLine[columnSchema.name])
|
|
426
|
+
) {
|
|
427
|
+
// Add null values
|
|
428
|
+
formattedLine[columnSchema.name] = null;
|
|
442
429
|
}
|
|
443
430
|
|
|
444
431
|
// Validate required
|
|
445
432
|
if (
|
|
446
433
|
columnSchema.required === true &&
|
|
447
|
-
|
|
434
|
+
formattedLine[columnSchema.name] === null
|
|
448
435
|
) {
|
|
449
436
|
throw new Error(
|
|
450
|
-
`Missing required value in ${model.filenameBase}.txt for ${
|
|
437
|
+
`Missing required value in ${model.filenameBase}.txt for ${columnSchema.name} on line ${lineNumber}.`
|
|
451
438
|
);
|
|
452
439
|
}
|
|
453
440
|
|
|
454
441
|
// Validate minimum
|
|
455
|
-
if (
|
|
442
|
+
if (
|
|
443
|
+
columnSchema.min !== undefined &&
|
|
444
|
+
formattedLine[columnSchema.name] < columnSchema.min
|
|
445
|
+
) {
|
|
456
446
|
throw new Error(
|
|
457
|
-
`Invalid value in ${model.filenameBase}.txt for ${
|
|
447
|
+
`Invalid value in ${model.filenameBase}.txt for ${columnSchema.name} on line ${lineNumber}: below minimum value of ${columnSchema.min}.`
|
|
458
448
|
);
|
|
459
449
|
}
|
|
460
450
|
|
|
461
451
|
// Validate maximum
|
|
462
|
-
if (
|
|
452
|
+
if (
|
|
453
|
+
columnSchema.max !== undefined &&
|
|
454
|
+
formattedLine[columnSchema.name] > columnSchema.max
|
|
455
|
+
) {
|
|
463
456
|
throw new Error(
|
|
464
|
-
`Invalid value in ${model.filenameBase}.txt for ${
|
|
457
|
+
`Invalid value in ${model.filenameBase}.txt for ${columnSchema.name} on line ${lineNumber}: above maximum value of ${columnSchema.max}.`
|
|
465
458
|
);
|
|
466
459
|
}
|
|
467
460
|
}
|
|
468
461
|
|
|
469
462
|
// Convert to midnight timestamp
|
|
470
|
-
const
|
|
463
|
+
const timestampColumnNames = [
|
|
471
464
|
'start_time',
|
|
472
465
|
'end_time',
|
|
473
466
|
'arrival_time',
|
|
474
467
|
'departure_time',
|
|
475
468
|
];
|
|
476
469
|
|
|
477
|
-
for (const
|
|
478
|
-
if (
|
|
479
|
-
|
|
470
|
+
for (const timestampColumnName of timestampColumnNames) {
|
|
471
|
+
if (formattedLine[timestampColumnName]) {
|
|
472
|
+
formattedLine[`${timestampColumnName}stamp`] =
|
|
473
|
+
calculateSecondsFromMidnight(formattedLine[timestampColumnName]);
|
|
480
474
|
}
|
|
481
475
|
}
|
|
482
476
|
|
|
483
|
-
return
|
|
477
|
+
return formattedLine;
|
|
484
478
|
};
|
|
485
479
|
|
|
486
|
-
const importLines =
|
|
480
|
+
const importLines = (task, lines, model, totalLineCount) => {
|
|
487
481
|
if (lines.length === 0) {
|
|
488
482
|
return;
|
|
489
483
|
}
|
|
490
484
|
|
|
491
485
|
const linesToImportCount = lines.length;
|
|
492
|
-
const fieldNames = model.schema
|
|
486
|
+
const fieldNames = model.schema
|
|
487
|
+
.filter((column) => column.name !== 'id')
|
|
488
|
+
.map((column) => column.name);
|
|
493
489
|
const placeholders = [];
|
|
494
490
|
const values = [];
|
|
495
491
|
|
|
496
492
|
while (lines.length > 0) {
|
|
497
493
|
const line = lines.pop();
|
|
498
494
|
placeholders.push(`(${fieldNames.map(() => '?').join(', ')})`);
|
|
499
|
-
|
|
500
|
-
values.push(line[fieldName]);
|
|
501
|
-
}
|
|
495
|
+
values.push(...fieldNames.map((fieldName) => line[fieldName]));
|
|
502
496
|
}
|
|
503
497
|
|
|
504
498
|
try {
|
|
505
|
-
|
|
499
|
+
const insert = task.db.prepare(
|
|
506
500
|
`INSERT ${task.ignoreDuplicates ? 'OR IGNORE' : ''} INTO ${
|
|
507
501
|
model.filenameBase
|
|
508
|
-
}(${fieldNames.join(', ')}) VALUES${placeholders.join(',')}
|
|
509
|
-
values
|
|
502
|
+
} (${fieldNames.join(', ')}) VALUES ${placeholders.join(',')}`
|
|
510
503
|
);
|
|
504
|
+
insert.run(...values);
|
|
511
505
|
} catch (error) {
|
|
512
506
|
task.warn(
|
|
513
507
|
`Check ${model.filenameBase}.txt for invalid data between lines ${
|
|
@@ -536,7 +530,7 @@ const importFiles = (task) =>
|
|
|
536
530
|
return;
|
|
537
531
|
}
|
|
538
532
|
|
|
539
|
-
// If the model is a database/gtfs-realtime model then
|
|
533
|
+
// If the model is a database/gtfs-realtime model then silently exit
|
|
540
534
|
if (model.extension === 'gtfs-realtime') {
|
|
541
535
|
resolve();
|
|
542
536
|
return;
|
|
@@ -569,18 +563,16 @@ const importFiles = (task) =>
|
|
|
569
563
|
...task.csvOptions,
|
|
570
564
|
});
|
|
571
565
|
|
|
572
|
-
parser.on('readable',
|
|
566
|
+
parser.on('readable', () => {
|
|
573
567
|
let record;
|
|
574
568
|
|
|
575
569
|
while ((record = parser.read())) {
|
|
576
570
|
try {
|
|
577
571
|
totalLineCount += 1;
|
|
578
572
|
lines.push(formatLine(record, model, totalLineCount));
|
|
579
|
-
|
|
580
573
|
// If we have a bunch of lines ready to insert, then do it
|
|
581
574
|
if (lines.length >= maxInsertVariables / model.schema.length) {
|
|
582
|
-
|
|
583
|
-
await importLines(task, lines, model, totalLineCount);
|
|
575
|
+
importLines(task, lines, model, totalLineCount);
|
|
584
576
|
}
|
|
585
577
|
} catch (error) {
|
|
586
578
|
reject(error);
|
|
@@ -588,9 +580,9 @@ const importFiles = (task) =>
|
|
|
588
580
|
}
|
|
589
581
|
});
|
|
590
582
|
|
|
591
|
-
parser.on('end',
|
|
583
|
+
parser.on('end', () => {
|
|
592
584
|
// Insert all remaining lines
|
|
593
|
-
|
|
585
|
+
importLines(task, lines, model, totalLineCount);
|
|
594
586
|
resolve();
|
|
595
587
|
});
|
|
596
588
|
|
|
@@ -606,70 +598,71 @@ export async function importGtfs(initialConfig) {
|
|
|
606
598
|
const log = _log(config);
|
|
607
599
|
const logError = _logError(config);
|
|
608
600
|
const logWarning = _logWarning(config);
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
log(
|
|
621
|
-
`Starting GTFS import for ${pluralize(
|
|
622
|
-
'file',
|
|
623
|
-
agencyCount,
|
|
624
|
-
true
|
|
625
|
-
)} using SQLite database at ${config.sqlitePath}`
|
|
626
|
-
);
|
|
627
|
-
|
|
628
|
-
await deleteTables(db);
|
|
629
|
-
await createTables(db);
|
|
601
|
+
try {
|
|
602
|
+
const db = openDb(config);
|
|
603
|
+
|
|
604
|
+
const agencyCount = config.agencies.length;
|
|
605
|
+
log(
|
|
606
|
+
`Starting GTFS import for ${pluralize(
|
|
607
|
+
'file',
|
|
608
|
+
agencyCount,
|
|
609
|
+
true
|
|
610
|
+
)} using SQLite database at ${config.sqlitePath}`
|
|
611
|
+
);
|
|
630
612
|
|
|
631
|
-
|
|
613
|
+
createTables(db);
|
|
614
|
+
|
|
615
|
+
await mapSeries(config.agencies, async (agency) => {
|
|
616
|
+
const { path, cleanup } = await dir({ unsafeCleanup: true });
|
|
617
|
+
|
|
618
|
+
const task = {
|
|
619
|
+
exclude: agency.exclude,
|
|
620
|
+
agency_url: agency.url,
|
|
621
|
+
headers: agency.headers || false,
|
|
622
|
+
realtime_headers: agency.realtimeHeaders || false,
|
|
623
|
+
realtime_urls: agency.realtimeUrls || false,
|
|
624
|
+
downloadDir: path,
|
|
625
|
+
path: agency.path,
|
|
626
|
+
csvOptions: config.csvOptions || {},
|
|
627
|
+
ignoreDuplicates: config.ignoreDuplicates,
|
|
628
|
+
db,
|
|
629
|
+
log(message, overwrite) {
|
|
630
|
+
log(message, overwrite);
|
|
631
|
+
},
|
|
632
|
+
warn(message) {
|
|
633
|
+
logWarning(message);
|
|
634
|
+
},
|
|
635
|
+
error(message) {
|
|
636
|
+
logError(message);
|
|
637
|
+
},
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
if (task.agency_url) {
|
|
641
|
+
await downloadFiles(task);
|
|
642
|
+
}
|
|
632
643
|
|
|
633
|
-
|
|
634
|
-
|
|
644
|
+
await readFiles(task);
|
|
645
|
+
await importFiles(task);
|
|
635
646
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
headers: agency.headers || false,
|
|
640
|
-
realtime_headers: agency.realtimeHeaders || false,
|
|
641
|
-
realtime_urls: agency.realtimeUrls || false,
|
|
642
|
-
downloadDir: path,
|
|
643
|
-
path: agency.path,
|
|
644
|
-
csvOptions: config.csvOptions || {},
|
|
645
|
-
ignoreDuplicates: config.ignoreDuplicates,
|
|
646
|
-
db,
|
|
647
|
-
log(message, overwrite) {
|
|
648
|
-
log(message, overwrite);
|
|
649
|
-
},
|
|
650
|
-
warn(message) {
|
|
651
|
-
logWarning(message);
|
|
652
|
-
},
|
|
653
|
-
error(message) {
|
|
654
|
-
logError(message);
|
|
655
|
-
},
|
|
656
|
-
};
|
|
657
|
-
|
|
658
|
-
if (task.agency_url) {
|
|
659
|
-
await downloadFiles(task);
|
|
660
|
-
}
|
|
647
|
+
if (task.realtime_urls) {
|
|
648
|
+
await updateRealtimeData(task);
|
|
649
|
+
}
|
|
661
650
|
|
|
662
|
-
|
|
663
|
-
|
|
651
|
+
cleanup();
|
|
652
|
+
});
|
|
664
653
|
|
|
665
|
-
|
|
666
|
-
|
|
654
|
+
log(
|
|
655
|
+
`Completed GTFS import for ${pluralize('agency', agencyCount, true)}\n`
|
|
656
|
+
);
|
|
657
|
+
} catch (error) {
|
|
658
|
+
if (error instanceof Error && error.code === 'SQLITE_CANTOPEN') {
|
|
659
|
+
logError(
|
|
660
|
+
`Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`
|
|
661
|
+
);
|
|
667
662
|
}
|
|
668
663
|
|
|
669
|
-
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
log(`Completed GTFS import for ${pluralize('agency', agencyCount, true)}\n`);
|
|
664
|
+
throw error;
|
|
665
|
+
}
|
|
673
666
|
}
|
|
674
667
|
|
|
675
668
|
export async function updateGtfsRealtime(initialConfig) {
|
|
@@ -678,7 +671,7 @@ export async function updateGtfsRealtime(initialConfig) {
|
|
|
678
671
|
const log = _log(config);
|
|
679
672
|
const logError = _logError(config);
|
|
680
673
|
const logWarning = _logWarning(config);
|
|
681
|
-
const db =
|
|
674
|
+
const db = openDb(config).catch((error) => {
|
|
682
675
|
if (error instanceof Error && error.code === 'SQLITE_CANTOPEN') {
|
|
683
676
|
logError(
|
|
684
677
|
`Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import sqlString from 'sqlstring-sqlite';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { openDb } from '../db.js';
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
formatOrderByClause,
|
|
@@ -12,19 +12,21 @@ import directions from '../../models/non-standard/directions.js';
|
|
|
12
12
|
/*
|
|
13
13
|
* Returns an array of all directions that match the query parameters.
|
|
14
14
|
*/
|
|
15
|
-
export
|
|
15
|
+
export function getDirections(
|
|
16
16
|
query = {},
|
|
17
17
|
fields = [],
|
|
18
18
|
orderBy = [],
|
|
19
19
|
options = {}
|
|
20
20
|
) {
|
|
21
|
-
const db = options.db ?? (
|
|
21
|
+
const db = options.db ?? openDb();
|
|
22
22
|
const tableName = sqlString.escapeId(directions.filenameBase);
|
|
23
23
|
const selectClause = formatSelectClause(fields);
|
|
24
24
|
const whereClause = formatWhereClauses(query);
|
|
25
25
|
const orderByClause = formatOrderByClause(orderBy);
|
|
26
26
|
|
|
27
|
-
return db
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
return db
|
|
28
|
+
.prepare(
|
|
29
|
+
`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`
|
|
30
|
+
)
|
|
31
|
+
.all();
|
|
30
32
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import sqlString from 'sqlstring-sqlite';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { openDb } from '../db.js';
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
formatOrderByClause,
|
|
@@ -12,19 +12,21 @@ import stopAttributes from '../../models/non-standard/stop-attributes.js';
|
|
|
12
12
|
/*
|
|
13
13
|
* Returns an array of all stop attributes that match the query parameters.
|
|
14
14
|
*/
|
|
15
|
-
export
|
|
15
|
+
export function getStopAttributes(
|
|
16
16
|
query = {},
|
|
17
17
|
fields = [],
|
|
18
18
|
orderBy = [],
|
|
19
19
|
options = {}
|
|
20
20
|
) {
|
|
21
|
-
const db = options.db ?? (
|
|
21
|
+
const db = options.db ?? openDb();
|
|
22
22
|
const tableName = sqlString.escapeId(stopAttributes.filenameBase);
|
|
23
23
|
const selectClause = formatSelectClause(fields);
|
|
24
24
|
const whereClause = formatWhereClauses(query);
|
|
25
25
|
const orderByClause = formatOrderByClause(orderBy);
|
|
26
26
|
|
|
27
|
-
return db
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
return db
|
|
28
|
+
.prepare(
|
|
29
|
+
`${selectClause} FROM ${tableName} ${whereClause} ${orderByClause};`
|
|
30
|
+
)
|
|
31
|
+
.all();
|
|
30
32
|
}
|