gtfs 4.17.6 → 4.18.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/README.md +32 -11
- package/dist/bin/gtfs-export.js +25 -18
- package/dist/bin/gtfs-export.js.map +1 -1
- package/dist/bin/gtfs-import.js +443 -316
- package/dist/bin/gtfs-import.js.map +1 -1
- package/dist/bin/gtfsrealtime-update.js +253 -196
- package/dist/bin/gtfsrealtime-update.js.map +1 -1
- package/dist/index.d.ts +145 -7
- package/dist/index.js +568 -427
- package/dist/index.js.map +1 -1
- package/package.json +16 -16
package/dist/bin/gtfs-import.js
CHANGED
|
@@ -13,10 +13,10 @@ import PrettyError from "pretty-error";
|
|
|
13
13
|
// src/lib/file-utils.ts
|
|
14
14
|
import path from "path";
|
|
15
15
|
import { existsSync } from "fs";
|
|
16
|
+
import { homedir } from "os";
|
|
16
17
|
import { mkdir, readFile, rm } from "fs/promises";
|
|
17
18
|
import { omit, snakeCase } from "lodash-es";
|
|
18
19
|
import sanitize from "sanitize-filename";
|
|
19
|
-
import untildify from "untildify";
|
|
20
20
|
import StreamZip from "node-stream-zip";
|
|
21
21
|
|
|
22
22
|
// src/lib/log-utils.ts
|
|
@@ -70,6 +70,7 @@ function formatError(error) {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
// src/lib/file-utils.ts
|
|
73
|
+
var homeDirectory = homedir();
|
|
73
74
|
async function getConfig(argv2) {
|
|
74
75
|
let config;
|
|
75
76
|
let data;
|
|
@@ -117,17 +118,17 @@ async function unzip(zipfilePath, exportPath) {
|
|
|
117
118
|
);
|
|
118
119
|
}
|
|
119
120
|
}
|
|
121
|
+
function untildify(pathWithTilde) {
|
|
122
|
+
return homeDirectory ? pathWithTilde.replace(/^~(?=$|\/|\\)/, homeDirectory) : pathWithTilde;
|
|
123
|
+
}
|
|
120
124
|
|
|
121
125
|
// src/lib/import-gtfs.ts
|
|
122
126
|
import path2 from "path";
|
|
123
127
|
import { createReadStream, existsSync as existsSync2, lstatSync } from "fs";
|
|
124
128
|
import { cp, readdir, rename, readFile as readFile2, rm as rm2, writeFile } from "fs/promises";
|
|
125
129
|
import { parse } from "csv-parse";
|
|
126
|
-
import pluralize2 from "pluralize";
|
|
127
130
|
import stripBomStream from "strip-bom-stream";
|
|
128
131
|
import { temporaryDirectory } from "tempy";
|
|
129
|
-
import Timer from "timer-machine";
|
|
130
|
-
import untildify3 from "untildify";
|
|
131
132
|
import mapSeries2 from "promise-map-series";
|
|
132
133
|
|
|
133
134
|
// src/models/models.ts
|
|
@@ -4089,10 +4090,9 @@ var vehicles = {
|
|
|
4089
4090
|
|
|
4090
4091
|
// src/lib/db.ts
|
|
4091
4092
|
import Database from "better-sqlite3";
|
|
4092
|
-
import untildify2 from "untildify";
|
|
4093
4093
|
var dbs = {};
|
|
4094
4094
|
function setupDb(sqlitePath) {
|
|
4095
|
-
const db = new Database(
|
|
4095
|
+
const db = new Database(untildify(sqlitePath));
|
|
4096
4096
|
db.pragma("journal_mode = OFF");
|
|
4097
4097
|
db.pragma("synchronous = OFF");
|
|
4098
4098
|
db.pragma("temp_store = MEMORY");
|
|
@@ -4158,13 +4158,12 @@ function isValidJSON(string) {
|
|
|
4158
4158
|
try {
|
|
4159
4159
|
JSON.parse(string);
|
|
4160
4160
|
return true;
|
|
4161
|
-
} catch
|
|
4161
|
+
} catch {
|
|
4162
4162
|
return false;
|
|
4163
4163
|
}
|
|
4164
4164
|
}
|
|
4165
4165
|
|
|
4166
4166
|
// src/lib/import-gtfs-realtime.ts
|
|
4167
|
-
import pluralize from "pluralize";
|
|
4168
4167
|
import GtfsRealtimeBindings from "gtfs-realtime-bindings";
|
|
4169
4168
|
import mapSeries from "promise-map-series";
|
|
4170
4169
|
import { get } from "lodash-es";
|
|
@@ -4191,7 +4190,8 @@ function setDefaultConfig(initialConfig) {
|
|
|
4191
4190
|
ignoreDuplicates: false,
|
|
4192
4191
|
ignoreErrors: false,
|
|
4193
4192
|
gtfsRealtimeExpirationSeconds: 0,
|
|
4194
|
-
verbose: true
|
|
4193
|
+
verbose: true,
|
|
4194
|
+
downloadTimeout: 3e4
|
|
4195
4195
|
};
|
|
4196
4196
|
return {
|
|
4197
4197
|
...defaults,
|
|
@@ -4220,38 +4220,14 @@ function applyPrefixToValue(value, columnShouldBePrefixed, prefix) {
|
|
|
4220
4220
|
}
|
|
4221
4221
|
return `${prefix}${value}`;
|
|
4222
4222
|
}
|
|
4223
|
+
function pluralize(singularWord, pluralWord, count) {
|
|
4224
|
+
return count === 1 ? singularWord : pluralWord;
|
|
4225
|
+
}
|
|
4223
4226
|
|
|
4224
4227
|
// src/lib/import-gtfs-realtime.ts
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
method: "GET",
|
|
4229
|
-
headers: {
|
|
4230
|
-
...urlConfig.headers ?? {},
|
|
4231
|
-
"Accept-Encoding": "gzip"
|
|
4232
|
-
},
|
|
4233
|
-
signal: task.downloadTimeout ? AbortSignal.timeout(task.downloadTimeout) : void 0
|
|
4234
|
-
});
|
|
4235
|
-
if (response.status !== 200) {
|
|
4236
|
-
task.logWarning(
|
|
4237
|
-
`Unable to download GTFS-Realtime from ${urlConfig.url}. Got status ${response.status}.`
|
|
4238
|
-
);
|
|
4239
|
-
return null;
|
|
4240
|
-
}
|
|
4241
|
-
const buffer = await response.arrayBuffer();
|
|
4242
|
-
const message = GtfsRealtimeBindings.transit_realtime.FeedMessage.decode(
|
|
4243
|
-
new Uint8Array(buffer)
|
|
4244
|
-
);
|
|
4245
|
-
return GtfsRealtimeBindings.transit_realtime.FeedMessage.toObject(message, {
|
|
4246
|
-
enums: String,
|
|
4247
|
-
longs: String,
|
|
4248
|
-
bytes: String,
|
|
4249
|
-
defaults: false,
|
|
4250
|
-
arrays: true,
|
|
4251
|
-
objects: true,
|
|
4252
|
-
oneofs: true
|
|
4253
|
-
});
|
|
4254
|
-
}
|
|
4228
|
+
var BATCH_SIZE = 1e3;
|
|
4229
|
+
var MAX_RETRIES = 3;
|
|
4230
|
+
var RETRY_DELAY = 1e3;
|
|
4255
4231
|
function prepareRealtimeFieldValue(entity, column, task) {
|
|
4256
4232
|
if (column.name === "created_timestamp") {
|
|
4257
4233
|
return task.currentTimestamp;
|
|
@@ -4268,163 +4244,246 @@ function prepareRealtimeFieldValue(entity, column, task) {
|
|
|
4268
4244
|
);
|
|
4269
4245
|
return column.type === "json" ? JSON.stringify(prefixedValue) : prefixedValue;
|
|
4270
4246
|
}
|
|
4271
|
-
|
|
4272
|
-
const
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
);
|
|
4277
|
-
const informedEntityStmt = db.prepare(
|
|
4278
|
-
`REPLACE INTO ${serviceAlertInformedEntities.filenameBase} (${serviceAlertInformedEntities.schema.map((column) => column.name).join(
|
|
4279
|
-
", "
|
|
4280
|
-
)}) VALUES (${serviceAlertInformedEntities.schema.map(() => "?").join(", ")})`
|
|
4247
|
+
function createPreparedStatement(db, model) {
|
|
4248
|
+
const columns = model.schema.map((column) => column.name);
|
|
4249
|
+
const placeholders = model.schema.map(() => "?").join(", ");
|
|
4250
|
+
return db.prepare(
|
|
4251
|
+
`REPLACE INTO ${model.filenameBase} (${columns.join(", ")}) VALUES (${placeholders})`
|
|
4281
4252
|
);
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4253
|
+
}
|
|
4254
|
+
async function processBatch(items, batchSize, processor) {
|
|
4255
|
+
let totalRecordCount = 0;
|
|
4256
|
+
let totalErrorCount = 0;
|
|
4257
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
4258
|
+
const batch = items.slice(i, i + batchSize);
|
|
4259
|
+
try {
|
|
4260
|
+
const result = await processor(batch);
|
|
4261
|
+
totalRecordCount += result.recordCount;
|
|
4262
|
+
totalErrorCount += result.errorCount;
|
|
4263
|
+
} catch (error) {
|
|
4264
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4265
|
+
totalErrorCount += batch.length;
|
|
4266
|
+
console.error(`Batch processing error: ${errorMessage}`);
|
|
4267
|
+
}
|
|
4268
|
+
}
|
|
4269
|
+
return { recordCount: totalRecordCount, errorCount: totalErrorCount };
|
|
4270
|
+
}
|
|
4271
|
+
async function fetchGtfsRealtimeData(type, task) {
|
|
4272
|
+
const urlConfig = getUrlConfig(type, task);
|
|
4273
|
+
if (!urlConfig) {
|
|
4274
|
+
return null;
|
|
4275
|
+
}
|
|
4276
|
+
task.log(`Importing - GTFS-Realtime from ${urlConfig.url}`);
|
|
4277
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
4278
|
+
try {
|
|
4279
|
+
const response = await fetch(urlConfig.url, {
|
|
4280
|
+
method: "GET",
|
|
4281
|
+
headers: {
|
|
4282
|
+
...urlConfig.headers ?? {},
|
|
4283
|
+
"Accept-Encoding": "gzip"
|
|
4284
|
+
},
|
|
4285
|
+
signal: task.downloadTimeout ? AbortSignal.timeout(task.downloadTimeout) : void 0
|
|
4286
|
+
});
|
|
4287
|
+
if (response.status !== 200) {
|
|
4288
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
4289
|
+
}
|
|
4290
|
+
const buffer = await response.arrayBuffer();
|
|
4291
|
+
const message = GtfsRealtimeBindings.transit_realtime.FeedMessage.decode(
|
|
4292
|
+
new Uint8Array(buffer)
|
|
4287
4293
|
);
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4294
|
+
const feedMessage = GtfsRealtimeBindings.transit_realtime.FeedMessage.toObject(message, {
|
|
4295
|
+
enums: String,
|
|
4296
|
+
longs: String,
|
|
4297
|
+
bytes: String,
|
|
4298
|
+
defaults: false,
|
|
4299
|
+
arrays: true,
|
|
4300
|
+
objects: true,
|
|
4301
|
+
oneofs: true
|
|
4302
|
+
});
|
|
4303
|
+
return feedMessage;
|
|
4304
|
+
} catch (error) {
|
|
4305
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4306
|
+
if (attempt === MAX_RETRIES) {
|
|
4307
|
+
if (task.ignoreErrors) {
|
|
4308
|
+
task.logError(
|
|
4309
|
+
`Failed to fetch ${type} after ${MAX_RETRIES} attempts: ${errorMessage}`
|
|
4310
|
+
);
|
|
4311
|
+
return null;
|
|
4312
|
+
}
|
|
4313
|
+
throw error;
|
|
4314
|
+
}
|
|
4315
|
+
task.logWarning(`Attempt ${attempt} failed for ${type}: ${errorMessage}`);
|
|
4316
|
+
await new Promise(
|
|
4317
|
+
(resolve) => setTimeout(resolve, RETRY_DELAY * attempt)
|
|
4318
|
+
);
|
|
4319
|
+
}
|
|
4320
|
+
}
|
|
4321
|
+
return null;
|
|
4322
|
+
}
|
|
4323
|
+
function getUrlConfig(type, task) {
|
|
4324
|
+
switch (type) {
|
|
4325
|
+
case "alerts":
|
|
4326
|
+
return task.realtimeAlerts;
|
|
4327
|
+
case "tripupdates":
|
|
4328
|
+
return task.realtimeTripUpdates;
|
|
4329
|
+
case "vehiclepositions":
|
|
4330
|
+
return task.realtimeVehiclePositions;
|
|
4331
|
+
default:
|
|
4332
|
+
return void 0;
|
|
4333
|
+
}
|
|
4334
|
+
}
|
|
4335
|
+
function createServiceAlertsProcessor(db, task) {
|
|
4336
|
+
const alertStmt = createPreparedStatement(db, serviceAlerts);
|
|
4337
|
+
const informedEntityStmt = createPreparedStatement(
|
|
4338
|
+
db,
|
|
4339
|
+
serviceAlertInformedEntities
|
|
4340
|
+
);
|
|
4341
|
+
return async (batch) => {
|
|
4342
|
+
let recordCount = 0;
|
|
4343
|
+
let errorCount = 0;
|
|
4344
|
+
db.transaction(() => {
|
|
4345
|
+
for (const entity of batch) {
|
|
4346
|
+
try {
|
|
4347
|
+
const alertValues = serviceAlerts.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
|
|
4348
|
+
alertStmt.run(alertValues);
|
|
4349
|
+
recordCount++;
|
|
4350
|
+
if (entity.alert?.informedEntity?.length) {
|
|
4351
|
+
for (const informedEntity of entity.alert.informedEntity) {
|
|
4293
4352
|
informedEntity.parent = entity;
|
|
4294
|
-
|
|
4353
|
+
const entityValues = serviceAlertInformedEntities.schema.map(
|
|
4295
4354
|
(column) => prepareRealtimeFieldValue(informedEntity, column, task)
|
|
4296
4355
|
);
|
|
4356
|
+
informedEntityStmt.run(entityValues);
|
|
4357
|
+
recordCount++;
|
|
4297
4358
|
}
|
|
4298
|
-
);
|
|
4299
|
-
for (const values of informedEntities) {
|
|
4300
|
-
informedEntityStmt.run(values);
|
|
4301
4359
|
}
|
|
4360
|
+
} catch (error) {
|
|
4361
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4362
|
+
errorCount++;
|
|
4363
|
+
task.logWarning(`Alert processing error: ${errorMessage}`);
|
|
4302
4364
|
}
|
|
4303
|
-
totalLineCount++;
|
|
4304
|
-
} catch (error) {
|
|
4305
|
-
task.logWarning(`Import error: ${error.message}`);
|
|
4306
4365
|
}
|
|
4307
|
-
}
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
true
|
|
4311
|
-
);
|
|
4312
|
-
})();
|
|
4366
|
+
})();
|
|
4367
|
+
return { recordCount, errorCount };
|
|
4368
|
+
};
|
|
4313
4369
|
}
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
", "
|
|
4319
|
-
)}) VALUES (${tripUpdates.schema.map(() => "?").join(", ")})`
|
|
4370
|
+
function createTripUpdatesProcessor(db, task) {
|
|
4371
|
+
const tripUpdateStmt = createPreparedStatement(
|
|
4372
|
+
db,
|
|
4373
|
+
tripUpdates
|
|
4320
4374
|
);
|
|
4321
|
-
const stopTimeStmt =
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
)}) VALUES (${stopTimeUpdates.schema.map(() => "?").join(", ")})`
|
|
4375
|
+
const stopTimeStmt = createPreparedStatement(
|
|
4376
|
+
db,
|
|
4377
|
+
stopTimeUpdates
|
|
4325
4378
|
);
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
(
|
|
4337
|
-
|
|
4338
|
-
|
|
4379
|
+
return async (batch) => {
|
|
4380
|
+
let recordCount = 0;
|
|
4381
|
+
let errorCount = 0;
|
|
4382
|
+
db.transaction(() => {
|
|
4383
|
+
for (const entity of batch) {
|
|
4384
|
+
try {
|
|
4385
|
+
const tripUpdateValues = tripUpdates.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
|
|
4386
|
+
tripUpdateStmt.run(tripUpdateValues);
|
|
4387
|
+
recordCount++;
|
|
4388
|
+
if (entity.tripUpdate?.stopTimeUpdate?.length) {
|
|
4389
|
+
for (const stopTimeUpdate of entity.tripUpdate.stopTimeUpdate) {
|
|
4390
|
+
stopTimeUpdate.parent = entity;
|
|
4391
|
+
const stopTimeValues = stopTimeUpdates.schema.map(
|
|
4392
|
+
(column) => prepareRealtimeFieldValue(stopTimeUpdate, column, task)
|
|
4393
|
+
);
|
|
4394
|
+
stopTimeStmt.run(stopTimeValues);
|
|
4395
|
+
recordCount++;
|
|
4396
|
+
}
|
|
4397
|
+
}
|
|
4398
|
+
} catch (error) {
|
|
4399
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4400
|
+
errorCount++;
|
|
4401
|
+
task.logWarning(`Trip update processing error: ${errorMessage}`);
|
|
4339
4402
|
}
|
|
4340
|
-
totalLineCount++;
|
|
4341
|
-
} catch (error) {
|
|
4342
|
-
task.logWarning(`Import error: ${error.message}`);
|
|
4343
4403
|
}
|
|
4344
|
-
}
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
true
|
|
4348
|
-
);
|
|
4349
|
-
})();
|
|
4404
|
+
})();
|
|
4405
|
+
return { recordCount, errorCount };
|
|
4406
|
+
};
|
|
4350
4407
|
}
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
", "
|
|
4356
|
-
)}) VALUES (${vehiclePositions.schema.map(() => "?").join(", ")})`
|
|
4408
|
+
function createVehiclePositionsProcessor(db, task) {
|
|
4409
|
+
const vehiclePositionStmt = createPreparedStatement(
|
|
4410
|
+
db,
|
|
4411
|
+
vehiclePositions
|
|
4357
4412
|
);
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4413
|
+
return async (batch) => {
|
|
4414
|
+
let recordCount = 0;
|
|
4415
|
+
let errorCount = 0;
|
|
4416
|
+
db.transaction(() => {
|
|
4417
|
+
for (const entity of batch) {
|
|
4418
|
+
try {
|
|
4419
|
+
const fieldValues = vehiclePositions.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
|
|
4420
|
+
vehiclePositionStmt.run(fieldValues);
|
|
4421
|
+
recordCount++;
|
|
4422
|
+
} catch (error) {
|
|
4423
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4424
|
+
errorCount++;
|
|
4425
|
+
task.logWarning(`Vehicle position processing error: ${errorMessage}`);
|
|
4426
|
+
}
|
|
4366
4427
|
}
|
|
4367
|
-
}
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
true
|
|
4371
|
-
);
|
|
4372
|
-
})();
|
|
4428
|
+
})();
|
|
4429
|
+
return { recordCount, errorCount };
|
|
4430
|
+
};
|
|
4373
4431
|
}
|
|
4374
4432
|
async function updateGtfsRealtimeData(task) {
|
|
4375
|
-
if (task.realtimeAlerts
|
|
4433
|
+
if (!task.realtimeAlerts && !task.realtimeTripUpdates && !task.realtimeVehiclePositions) {
|
|
4376
4434
|
return;
|
|
4377
4435
|
}
|
|
4436
|
+
const [alertsData, tripUpdatesData, vehiclePositionsData] = await Promise.all(
|
|
4437
|
+
[
|
|
4438
|
+
task.realtimeAlerts?.url ? fetchGtfsRealtimeData("alerts", task) : null,
|
|
4439
|
+
task.realtimeTripUpdates?.url ? fetchGtfsRealtimeData("tripupdates", task) : null,
|
|
4440
|
+
task.realtimeVehiclePositions?.url ? fetchGtfsRealtimeData("vehiclepositions", task) : null
|
|
4441
|
+
]
|
|
4442
|
+
);
|
|
4378
4443
|
const db = openDb({ sqlitePath: task.sqlitePath });
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4444
|
+
const recordCounts = {
|
|
4445
|
+
alerts: 0,
|
|
4446
|
+
tripupdates: 0,
|
|
4447
|
+
vehiclepositions: 0
|
|
4448
|
+
};
|
|
4449
|
+
const processingPromises = [];
|
|
4450
|
+
if (alertsData?.entity?.length) {
|
|
4451
|
+
processingPromises.push(
|
|
4452
|
+
processBatch(
|
|
4453
|
+
alertsData.entity,
|
|
4454
|
+
BATCH_SIZE,
|
|
4455
|
+
createServiceAlertsProcessor(db, task)
|
|
4456
|
+
).then((result) => {
|
|
4457
|
+
recordCounts.alerts = result.recordCount;
|
|
4458
|
+
})
|
|
4459
|
+
);
|
|
4392
4460
|
}
|
|
4393
|
-
if (
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
}
|
|
4402
|
-
|
|
4403
|
-
if (task.ignoreErrors) {
|
|
4404
|
-
task.logError(error.message);
|
|
4405
|
-
} else {
|
|
4406
|
-
throw error;
|
|
4407
|
-
}
|
|
4408
|
-
}
|
|
4461
|
+
if (tripUpdatesData?.entity?.length) {
|
|
4462
|
+
processingPromises.push(
|
|
4463
|
+
processBatch(
|
|
4464
|
+
tripUpdatesData.entity,
|
|
4465
|
+
BATCH_SIZE,
|
|
4466
|
+
createTripUpdatesProcessor(db, task)
|
|
4467
|
+
).then((result) => {
|
|
4468
|
+
recordCounts.tripupdates = result.recordCount;
|
|
4469
|
+
})
|
|
4470
|
+
);
|
|
4409
4471
|
}
|
|
4410
|
-
if (
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
}
|
|
4419
|
-
|
|
4420
|
-
if (task.ignoreErrors) {
|
|
4421
|
-
task.logError(error.message);
|
|
4422
|
-
} else {
|
|
4423
|
-
throw error;
|
|
4424
|
-
}
|
|
4425
|
-
}
|
|
4472
|
+
if (vehiclePositionsData?.entity?.length) {
|
|
4473
|
+
processingPromises.push(
|
|
4474
|
+
processBatch(
|
|
4475
|
+
vehiclePositionsData.entity,
|
|
4476
|
+
BATCH_SIZE,
|
|
4477
|
+
createVehiclePositionsProcessor(db, task)
|
|
4478
|
+
).then((result) => {
|
|
4479
|
+
recordCounts.vehiclepositions = result.recordCount;
|
|
4480
|
+
})
|
|
4481
|
+
);
|
|
4426
4482
|
}
|
|
4427
|
-
|
|
4483
|
+
await Promise.all(processingPromises);
|
|
4484
|
+
task.log(
|
|
4485
|
+
`GTFS-Realtime import complete: ${recordCounts.alerts} alerts, ${recordCounts.tripupdates} trip updates, ${recordCounts.vehiclepositions} vehicle positions`
|
|
4486
|
+
);
|
|
4428
4487
|
}
|
|
4429
4488
|
|
|
4430
4489
|
// src/lib/import-gtfs.ts
|
|
@@ -4456,8 +4515,8 @@ var extractGtfsFiles = async (task) => {
|
|
|
4456
4515
|
if (!task.path) {
|
|
4457
4516
|
throw new Error("No `path` specified in config");
|
|
4458
4517
|
}
|
|
4459
|
-
const gtfsPath =
|
|
4460
|
-
task.log(`Importing GTFS from ${task.path}\r`);
|
|
4518
|
+
const gtfsPath = untildify(task.path);
|
|
4519
|
+
task.log(`Importing static GTFS from ${task.path}\r`);
|
|
4461
4520
|
if (path2.extname(gtfsPath) === ".zip") {
|
|
4462
4521
|
try {
|
|
4463
4522
|
await unzip(gtfsPath, task.downloadDir);
|
|
@@ -4540,11 +4599,11 @@ var createGtfsTables = (db) => {
|
|
|
4540
4599
|
if (column.type === "time") {
|
|
4541
4600
|
sqlColumnCreateStatements.push(
|
|
4542
4601
|
`${getTimestampColumnName(column.name)} INTEGER GENERATED ALWAYS AS (
|
|
4543
|
-
CASE
|
|
4544
|
-
WHEN ${column.name} IS NULL OR ${column.name} = '' THEN NULL
|
|
4602
|
+
CASE
|
|
4603
|
+
WHEN ${column.name} IS NULL OR ${column.name} = '' THEN NULL
|
|
4545
4604
|
ELSE CAST(
|
|
4546
|
-
substr(${column.name}, 1, instr(${column.name}, ':') - 1) * 3600 +
|
|
4547
|
-
substr(${column.name}, instr(${column.name}, ':') + 1, 2) * 60 +
|
|
4605
|
+
substr(${column.name}, 1, instr(${column.name}, ':') - 1) * 3600 +
|
|
4606
|
+
substr(${column.name}, instr(${column.name}, ':') + 1, 2) * 60 +
|
|
4548
4607
|
substr(${column.name}, -2) AS INTEGER
|
|
4549
4608
|
)
|
|
4550
4609
|
END
|
|
@@ -4601,7 +4660,7 @@ var formatGtfsLine = (line, model, totalLineCount) => {
|
|
|
4601
4660
|
continue;
|
|
4602
4661
|
}
|
|
4603
4662
|
if (type === "date") {
|
|
4604
|
-
value = value.replace(/-/g, "");
|
|
4663
|
+
value = value?.toString().replace(/-/g, "");
|
|
4605
4664
|
if (value.length !== 8) {
|
|
4606
4665
|
throw new Error(
|
|
4607
4666
|
`Invalid date in ${filenameBase}.${filenameExtension} for ${name} on line ${lineNumber}.`
|
|
@@ -4617,155 +4676,221 @@ var formatGtfsLine = (line, model, totalLineCount) => {
|
|
|
4617
4676
|
}
|
|
4618
4677
|
return formattedLine;
|
|
4619
4678
|
};
|
|
4620
|
-
var
|
|
4621
|
-
var importGtfsFiles = (db, task) =>
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
task.
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
if (model.extension === "gtfs-realtime") {
|
|
4632
|
-
resolve();
|
|
4633
|
-
return;
|
|
4634
|
-
}
|
|
4635
|
-
const filepath = path2.join(task.downloadDir, `${filename}`);
|
|
4636
|
-
if (!existsSync2(filepath)) {
|
|
4637
|
-
if (!model.nonstandard) {
|
|
4638
|
-
task.log(`Importing - ${filename} - No file found\r`);
|
|
4679
|
+
var BATCH_SIZE2 = 1e5;
|
|
4680
|
+
var importGtfsFiles = async (db, task) => {
|
|
4681
|
+
await mapSeries2(
|
|
4682
|
+
Object.values(models_exports),
|
|
4683
|
+
(model) => new Promise((resolve, reject) => {
|
|
4684
|
+
let totalLineCount = 0;
|
|
4685
|
+
const filename = `${model.filenameBase}.${model.filenameExtension}`;
|
|
4686
|
+
if (task.exclude && task.exclude.includes(model.filenameBase)) {
|
|
4687
|
+
task.log(`Skipping - ${filename}\r`);
|
|
4688
|
+
resolve();
|
|
4689
|
+
return;
|
|
4639
4690
|
}
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
const prepareStatement = `INSERT ${task.ignoreDuplicates ? "OR IGNORE" : ""} INTO ${model.filenameBase} (${columns.map(({ name }) => name).join(", ")}) VALUES (${columns.map(({ name }) => `@${name}`).join(", ")})`;
|
|
4649
|
-
const insert = db.prepare(prepareStatement);
|
|
4650
|
-
const insertLines = db.transaction((lines) => {
|
|
4651
|
-
for (const [rowNumber, line] of Object.entries(lines)) {
|
|
4652
|
-
try {
|
|
4653
|
-
if (task.prefix === void 0) {
|
|
4654
|
-
insert.run(line);
|
|
4655
|
-
} else {
|
|
4656
|
-
const prefixedLine = Object.fromEntries(
|
|
4657
|
-
Object.entries(
|
|
4658
|
-
line
|
|
4659
|
-
).map(([columnName, value]) => [
|
|
4660
|
-
columnName,
|
|
4661
|
-
applyPrefixToValue(
|
|
4662
|
-
value,
|
|
4663
|
-
prefixedColumns.has(columnName),
|
|
4664
|
-
task.prefix
|
|
4665
|
-
)
|
|
4666
|
-
])
|
|
4667
|
-
);
|
|
4668
|
-
insert.run(prefixedLine);
|
|
4669
|
-
}
|
|
4670
|
-
} catch (error) {
|
|
4671
|
-
if (error.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
|
|
4672
|
-
const primaryColumns = columns.filter(
|
|
4673
|
-
(column) => column.primary
|
|
4674
|
-
);
|
|
4675
|
-
task.logWarning(
|
|
4676
|
-
`Duplicate values for primary key (${primaryColumns.map((column) => column.name).join(", ")}) found in ${filename}. Set the \`ignoreDuplicates\` option to true in config.json to ignore this error`
|
|
4677
|
-
);
|
|
4678
|
-
}
|
|
4679
|
-
task.logWarning(
|
|
4680
|
-
`Check ${filename} for invalid data on line ${rowNumber + 1}.`
|
|
4681
|
-
);
|
|
4682
|
-
throw error;
|
|
4691
|
+
if (model.extension === "gtfs-realtime") {
|
|
4692
|
+
resolve();
|
|
4693
|
+
return;
|
|
4694
|
+
}
|
|
4695
|
+
const filepath = path2.join(task.downloadDir, `${filename}`);
|
|
4696
|
+
if (!existsSync2(filepath)) {
|
|
4697
|
+
if (!model.nonstandard) {
|
|
4698
|
+
task.log(`Importing - ${filename} - No file found\r`);
|
|
4683
4699
|
}
|
|
4700
|
+
resolve();
|
|
4701
|
+
return;
|
|
4684
4702
|
}
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
const
|
|
4688
|
-
columns
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4703
|
+
task.log(`Importing - ${filename}\r`);
|
|
4704
|
+
const columns = model.schema;
|
|
4705
|
+
const prefixedColumns = new Set(
|
|
4706
|
+
columns.filter((column) => column.prefix).map((column) => column.name)
|
|
4707
|
+
);
|
|
4708
|
+
const prepareStatement = `INSERT ${task.ignoreDuplicates ? "OR IGNORE" : ""} INTO ${model.filenameBase} (${columns.map(({ name }) => name).join(", ")}) VALUES (${columns.map(({ name }) => `@${name}`).join(", ")})`;
|
|
4709
|
+
const insert = db.prepare(prepareStatement);
|
|
4710
|
+
const insertLines = db.transaction((lines) => {
|
|
4711
|
+
for (const [rowNumber, line] of Object.entries(lines)) {
|
|
4712
|
+
try {
|
|
4713
|
+
if (task.prefix === void 0) {
|
|
4714
|
+
insert.run(line);
|
|
4715
|
+
} else {
|
|
4716
|
+
const prefixedLine = Object.fromEntries(
|
|
4717
|
+
Object.entries(
|
|
4718
|
+
line
|
|
4719
|
+
).map(([columnName, value]) => [
|
|
4720
|
+
columnName,
|
|
4721
|
+
applyPrefixToValue(
|
|
4722
|
+
value,
|
|
4723
|
+
prefixedColumns.has(columnName),
|
|
4724
|
+
task.prefix
|
|
4725
|
+
)
|
|
4726
|
+
])
|
|
4707
4727
|
);
|
|
4728
|
+
insert.run(prefixedLine);
|
|
4708
4729
|
}
|
|
4730
|
+
} catch (error) {
|
|
4731
|
+
if (error.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
|
|
4732
|
+
const primaryColumns = columns.filter(
|
|
4733
|
+
(column) => column.primary
|
|
4734
|
+
);
|
|
4735
|
+
task.logWarning(
|
|
4736
|
+
`Duplicate values for primary key (${primaryColumns.map((column) => column.name).join(", ")}) found in ${filename}. Set the \`ignoreDuplicates\` option to true in config.json to ignore this error`
|
|
4737
|
+
);
|
|
4738
|
+
}
|
|
4739
|
+
task.logWarning(
|
|
4740
|
+
`Check ${filename} for invalid data on line ${rowNumber + 1}.`
|
|
4741
|
+
);
|
|
4742
|
+
throw error;
|
|
4709
4743
|
}
|
|
4710
|
-
} catch (error) {
|
|
4711
|
-
reject(error);
|
|
4712
4744
|
}
|
|
4713
4745
|
});
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4746
|
+
if (model.filenameExtension === "txt") {
|
|
4747
|
+
const parser = parse({
|
|
4748
|
+
columns: true,
|
|
4749
|
+
relax_quotes: true,
|
|
4750
|
+
trim: true,
|
|
4751
|
+
skip_empty_lines: true,
|
|
4752
|
+
...task.csvOptions
|
|
4753
|
+
});
|
|
4754
|
+
let lines = [];
|
|
4755
|
+
parser.on("readable", () => {
|
|
4756
|
+
try {
|
|
4757
|
+
let record;
|
|
4758
|
+
while (record = parser.read()) {
|
|
4759
|
+
totalLineCount += 1;
|
|
4760
|
+
lines.push(formatGtfsLine(record, model, totalLineCount));
|
|
4761
|
+
if (lines.length >= BATCH_SIZE2) {
|
|
4762
|
+
insertLines(lines);
|
|
4763
|
+
lines = [];
|
|
4764
|
+
task.log(
|
|
4765
|
+
`Importing - ${filename} - ${totalLineCount} lines imported\r`,
|
|
4766
|
+
true
|
|
4767
|
+
);
|
|
4768
|
+
}
|
|
4769
|
+
}
|
|
4770
|
+
} catch (error) {
|
|
4771
|
+
if (task.ignoreErrors) {
|
|
4772
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4773
|
+
task.logError(`Error processing ${filename}: ${errorMessage}`);
|
|
4774
|
+
resolve();
|
|
4775
|
+
} else {
|
|
4720
4776
|
reject(error);
|
|
4721
4777
|
}
|
|
4722
4778
|
}
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4779
|
+
});
|
|
4780
|
+
parser.on("end", () => {
|
|
4781
|
+
try {
|
|
4782
|
+
if (lines.length > 0) {
|
|
4783
|
+
try {
|
|
4784
|
+
insertLines(lines);
|
|
4785
|
+
} catch (error) {
|
|
4786
|
+
if (task.ignoreErrors) {
|
|
4787
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4788
|
+
task.logError(
|
|
4789
|
+
`Error inserting data for ${filename}: ${errorMessage}`
|
|
4790
|
+
);
|
|
4791
|
+
resolve();
|
|
4792
|
+
return;
|
|
4793
|
+
} else {
|
|
4794
|
+
reject(error);
|
|
4795
|
+
return;
|
|
4796
|
+
}
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
4799
|
+
task.log(
|
|
4800
|
+
`Importing - ${filename} - ${totalLineCount} lines imported\r`,
|
|
4801
|
+
true
|
|
4802
|
+
);
|
|
4803
|
+
resolve();
|
|
4804
|
+
} catch (error) {
|
|
4805
|
+
if (task.ignoreErrors) {
|
|
4806
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4807
|
+
task.logError(`Error finalizing ${filename}: ${errorMessage}`);
|
|
4808
|
+
resolve();
|
|
4809
|
+
} else {
|
|
4810
|
+
reject(error);
|
|
4811
|
+
}
|
|
4812
|
+
}
|
|
4813
|
+
});
|
|
4814
|
+
parser.on("error", (error) => {
|
|
4815
|
+
if (task.ignoreErrors) {
|
|
4816
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4817
|
+
task.logError(`Parser error for ${filename}: ${errorMessage}`);
|
|
4818
|
+
resolve();
|
|
4819
|
+
} else {
|
|
4820
|
+
reject(error);
|
|
4821
|
+
}
|
|
4822
|
+
});
|
|
4823
|
+
createReadStream(filepath).pipe(stripBomStream()).pipe(parser);
|
|
4824
|
+
} else if (model.filenameExtension === "geojson") {
|
|
4825
|
+
readFile2(filepath, "utf8").then((data) => {
|
|
4826
|
+
if (isValidJSON(data) === false) {
|
|
4827
|
+
if (task.ignoreErrors) {
|
|
4828
|
+
task.logError(`Invalid JSON in ${filename}`);
|
|
4829
|
+
resolve();
|
|
4830
|
+
return;
|
|
4831
|
+
} else {
|
|
4832
|
+
reject(new Error(`Invalid JSON in ${filename}`));
|
|
4833
|
+
return;
|
|
4834
|
+
}
|
|
4835
|
+
}
|
|
4836
|
+
totalLineCount += 1;
|
|
4837
|
+
const line = formatGtfsLine(
|
|
4838
|
+
{ geojson: data },
|
|
4839
|
+
model,
|
|
4840
|
+
totalLineCount
|
|
4841
|
+
);
|
|
4842
|
+
try {
|
|
4843
|
+
insertLines([line]);
|
|
4844
|
+
task.log(
|
|
4845
|
+
`Importing - ${filename} - ${totalLineCount} lines imported\r`,
|
|
4846
|
+
true
|
|
4847
|
+
);
|
|
4848
|
+
resolve();
|
|
4849
|
+
} catch (error) {
|
|
4850
|
+
if (task.ignoreErrors) {
|
|
4851
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4852
|
+
task.logError(
|
|
4853
|
+
`Error inserting data for ${filename}: ${errorMessage}`
|
|
4854
|
+
);
|
|
4855
|
+
resolve();
|
|
4856
|
+
} else {
|
|
4857
|
+
reject(error);
|
|
4858
|
+
}
|
|
4859
|
+
}
|
|
4860
|
+
}).catch((error) => {
|
|
4861
|
+
if (task.ignoreErrors) {
|
|
4862
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4863
|
+
task.logError(`Error reading ${filename}: ${errorMessage}`);
|
|
4864
|
+
resolve();
|
|
4865
|
+
} else {
|
|
4866
|
+
reject(error);
|
|
4867
|
+
}
|
|
4868
|
+
});
|
|
4869
|
+
} else {
|
|
4870
|
+
if (task.ignoreErrors) {
|
|
4871
|
+
task.logError(
|
|
4872
|
+
`Unsupported file type: ${model.filenameExtension} for ${filename}`
|
|
4726
4873
|
);
|
|
4727
4874
|
resolve();
|
|
4728
|
-
}
|
|
4729
|
-
reject(
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
parser.on("error", reject);
|
|
4733
|
-
createReadStream(filepath).pipe(stripBomStream()).pipe(parser);
|
|
4734
|
-
} else if (model.filenameExtension === "geojson") {
|
|
4735
|
-
readFile2(filepath, "utf8").then((data) => {
|
|
4736
|
-
if (isValidJSON(data) === false) {
|
|
4737
|
-
reject(new Error(`Invalid JSON in ${filename}`));
|
|
4875
|
+
} else {
|
|
4876
|
+
reject(
|
|
4877
|
+
new Error(`Unsupported file type: ${model.filenameExtension}`)
|
|
4878
|
+
);
|
|
4738
4879
|
}
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
);
|
|
4745
|
-
insertLines([line]);
|
|
4746
|
-
task.log(
|
|
4747
|
-
`Importing - ${filename} - ${totalLineCount} lines imported\r`,
|
|
4748
|
-
true
|
|
4749
|
-
);
|
|
4750
|
-
resolve();
|
|
4751
|
-
}).catch(reject);
|
|
4752
|
-
} else {
|
|
4753
|
-
reject(
|
|
4754
|
-
new Error(`Unsupported file type: ${model.filenameExtension}`)
|
|
4755
|
-
);
|
|
4756
|
-
}
|
|
4757
|
-
})
|
|
4758
|
-
);
|
|
4880
|
+
}
|
|
4881
|
+
})
|
|
4882
|
+
);
|
|
4883
|
+
task.log(`Static GTFS import complete`);
|
|
4884
|
+
};
|
|
4759
4885
|
async function importGtfs(initialConfig) {
|
|
4760
|
-
const
|
|
4761
|
-
timer.start();
|
|
4886
|
+
const startTime = process.hrtime.bigint();
|
|
4762
4887
|
const config = setDefaultConfig(initialConfig);
|
|
4763
4888
|
validateConfigForImport(config);
|
|
4764
4889
|
try {
|
|
4765
4890
|
const db = openDb(config);
|
|
4766
4891
|
const agencyCount = config.agencies.length;
|
|
4767
4892
|
log(config)(
|
|
4768
|
-
`Starting GTFS import for ${
|
|
4893
|
+
`Starting GTFS import for ${pluralize("file", "files", agencyCount)} using SQLite database at ${config.sqlitePath}`
|
|
4769
4894
|
);
|
|
4770
4895
|
createGtfsTables(db);
|
|
4771
4896
|
await mapSeries2(config.agencies, async (agency2) => {
|
|
@@ -4773,7 +4898,6 @@ async function importGtfs(initialConfig) {
|
|
|
4773
4898
|
const tempPath = temporaryDirectory();
|
|
4774
4899
|
const task = {
|
|
4775
4900
|
exclude: agency2.exclude,
|
|
4776
|
-
url: agency2.url,
|
|
4777
4901
|
headers: agency2.headers,
|
|
4778
4902
|
realtimeAlerts: agency2.realtimeAlerts,
|
|
4779
4903
|
realtimeTripUpdates: agency2.realtimeTripUpdates,
|
|
@@ -4781,7 +4905,6 @@ async function importGtfs(initialConfig) {
|
|
|
4781
4905
|
downloadDir: tempPath,
|
|
4782
4906
|
downloadTimeout: config.downloadTimeout,
|
|
4783
4907
|
gtfsRealtimeExpirationSeconds: config.gtfsRealtimeExpirationSeconds,
|
|
4784
|
-
path: agency2.path,
|
|
4785
4908
|
csvOptions: config.csvOptions || {},
|
|
4786
4909
|
ignoreDuplicates: config.ignoreDuplicates,
|
|
4787
4910
|
ignoreErrors: config.ignoreErrors,
|
|
@@ -4792,8 +4915,13 @@ async function importGtfs(initialConfig) {
|
|
|
4792
4915
|
logWarning: logWarning(config),
|
|
4793
4916
|
logError: logError(config)
|
|
4794
4917
|
};
|
|
4795
|
-
if (
|
|
4918
|
+
if ("url" in agency2) {
|
|
4919
|
+
Object.assign(task, { url: agency2.url });
|
|
4796
4920
|
await downloadGtfsFiles(task);
|
|
4921
|
+
} else {
|
|
4922
|
+
Object.assign(task, {
|
|
4923
|
+
path: agency2.path
|
|
4924
|
+
});
|
|
4797
4925
|
}
|
|
4798
4926
|
await extractGtfsFiles(task);
|
|
4799
4927
|
await importGtfsFiles(db, task);
|
|
@@ -4801,7 +4929,8 @@ async function importGtfs(initialConfig) {
|
|
|
4801
4929
|
await rm2(tempPath, { recursive: true });
|
|
4802
4930
|
} catch (error) {
|
|
4803
4931
|
if (config.ignoreErrors) {
|
|
4804
|
-
|
|
4932
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4933
|
+
logError(config)(errorMessage);
|
|
4805
4934
|
} else {
|
|
4806
4935
|
throw error;
|
|
4807
4936
|
}
|
|
@@ -4809,14 +4938,14 @@ async function importGtfs(initialConfig) {
|
|
|
4809
4938
|
});
|
|
4810
4939
|
log(config)(`Creating DB indexes`);
|
|
4811
4940
|
createGtfsIndexes(db);
|
|
4812
|
-
const
|
|
4813
|
-
|
|
4941
|
+
const endTime = process.hrtime.bigint();
|
|
4942
|
+
const elapsedSeconds = Number(endTime - startTime) / 1e9;
|
|
4814
4943
|
log(config)(
|
|
4815
|
-
`Completed GTFS import
|
|
4944
|
+
`Completed GTFS import in ${elapsedSeconds.toFixed(1)} seconds
|
|
4816
4945
|
`
|
|
4817
4946
|
);
|
|
4818
4947
|
} catch (error) {
|
|
4819
|
-
if (error
|
|
4948
|
+
if (error.code === "SQLITE_CANTOPEN") {
|
|
4820
4949
|
logError(config)(
|
|
4821
4950
|
`Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`
|
|
4822
4951
|
);
|
|
@@ -4829,11 +4958,9 @@ async function importGtfs(initialConfig) {
|
|
|
4829
4958
|
import path3 from "path";
|
|
4830
4959
|
import { writeFile as writeFile2 } from "fs/promises";
|
|
4831
4960
|
import { without, compact as compact2 } from "lodash-es";
|
|
4832
|
-
import pluralize3 from "pluralize";
|
|
4833
4961
|
import { stringify } from "csv-stringify";
|
|
4834
4962
|
import sqlString2 from "sqlstring-sqlite";
|
|
4835
4963
|
import mapSeries3 from "promise-map-series";
|
|
4836
|
-
import untildify4 from "untildify";
|
|
4837
4964
|
|
|
4838
4965
|
// src/lib/advancedQuery.ts
|
|
4839
4966
|
import sqlString3 from "sqlstring-sqlite";
|