gtfs 4.17.7 → 4.18.1
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 +14 -14
- package/dist/bin/gtfs-export.js +29 -4
- package/dist/bin/gtfs-export.js.map +1 -1
- package/dist/bin/gtfs-import.js +431 -343
- package/dist/bin/gtfs-import.js.map +1 -1
- package/dist/bin/gtfsrealtime-update.js +240 -183
- package/dist/bin/gtfsrealtime-update.js.map +1 -1
- package/dist/index.d.ts +431 -290
- package/dist/index.js +478 -383
- package/dist/index.js.map +1 -1
- package/dist/models/models.d.ts +19 -0
- package/dist/models/models.js +18 -0
- package/dist/models/models.js.map +1 -1
- package/package.json +18 -13
package/dist/index.js
CHANGED
|
@@ -130,6 +130,12 @@ var agency = {
|
|
|
130
130
|
name: "agency_email",
|
|
131
131
|
type: "text",
|
|
132
132
|
nocase: true
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: "cemv_support",
|
|
136
|
+
type: "integer",
|
|
137
|
+
min: 0,
|
|
138
|
+
max: 2
|
|
133
139
|
}
|
|
134
140
|
]
|
|
135
141
|
};
|
|
@@ -1060,6 +1066,12 @@ var routes = {
|
|
|
1060
1066
|
name: "network_id",
|
|
1061
1067
|
type: "text",
|
|
1062
1068
|
prefix: true
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
name: "cemv_support",
|
|
1072
|
+
type: "integer",
|
|
1073
|
+
min: 0,
|
|
1074
|
+
max: 2
|
|
1063
1075
|
}
|
|
1064
1076
|
]
|
|
1065
1077
|
};
|
|
@@ -1314,6 +1326,12 @@ var stops = {
|
|
|
1314
1326
|
{
|
|
1315
1327
|
name: "platform_code",
|
|
1316
1328
|
type: "text"
|
|
1329
|
+
},
|
|
1330
|
+
{
|
|
1331
|
+
name: "stop_access",
|
|
1332
|
+
type: "integer",
|
|
1333
|
+
min: 0,
|
|
1334
|
+
max: 1
|
|
1317
1335
|
}
|
|
1318
1336
|
]
|
|
1319
1337
|
};
|
|
@@ -4147,7 +4165,7 @@ function isValidJSON(string) {
|
|
|
4147
4165
|
try {
|
|
4148
4166
|
JSON.parse(string);
|
|
4149
4167
|
return true;
|
|
4150
|
-
} catch
|
|
4168
|
+
} catch {
|
|
4151
4169
|
return false;
|
|
4152
4170
|
}
|
|
4153
4171
|
}
|
|
@@ -4212,7 +4230,7 @@ function formatProperties(properties) {
|
|
|
4212
4230
|
if (formattedRouteTextColor) {
|
|
4213
4231
|
formattedProperties.route_text_color = formattedRouteTextColor;
|
|
4214
4232
|
}
|
|
4215
|
-
if (properties.routes) {
|
|
4233
|
+
if (properties.routes && Array.isArray(properties.routes)) {
|
|
4216
4234
|
formattedProperties.routes = properties.routes.map(
|
|
4217
4235
|
(route) => formatProperties(route)
|
|
4218
4236
|
);
|
|
@@ -4277,7 +4295,8 @@ function setDefaultConfig(initialConfig) {
|
|
|
4277
4295
|
ignoreDuplicates: false,
|
|
4278
4296
|
ignoreErrors: false,
|
|
4279
4297
|
gtfsRealtimeExpirationSeconds: 0,
|
|
4280
|
-
verbose: true
|
|
4298
|
+
verbose: true,
|
|
4299
|
+
downloadTimeout: 3e4
|
|
4281
4300
|
};
|
|
4282
4301
|
return {
|
|
4283
4302
|
...defaults,
|
|
@@ -4425,56 +4444,9 @@ function pluralize(singularWord, pluralWord, count) {
|
|
|
4425
4444
|
}
|
|
4426
4445
|
|
|
4427
4446
|
// src/lib/import-gtfs-realtime.ts
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
method: "GET",
|
|
4432
|
-
headers: {
|
|
4433
|
-
...urlConfig.headers ?? {},
|
|
4434
|
-
"Accept-Encoding": "gzip"
|
|
4435
|
-
},
|
|
4436
|
-
signal: task.downloadTimeout ? AbortSignal.timeout(task.downloadTimeout) : void 0
|
|
4437
|
-
});
|
|
4438
|
-
if (response.status !== 200) {
|
|
4439
|
-
task.logWarning(
|
|
4440
|
-
`Unable to download GTFS-Realtime from ${urlConfig.url}. Got status ${response.status}.`
|
|
4441
|
-
);
|
|
4442
|
-
return null;
|
|
4443
|
-
}
|
|
4444
|
-
const buffer = await response.arrayBuffer();
|
|
4445
|
-
const message = GtfsRealtimeBindings.transit_realtime.FeedMessage.decode(
|
|
4446
|
-
new Uint8Array(buffer)
|
|
4447
|
-
);
|
|
4448
|
-
return GtfsRealtimeBindings.transit_realtime.FeedMessage.toObject(message, {
|
|
4449
|
-
enums: String,
|
|
4450
|
-
longs: String,
|
|
4451
|
-
bytes: String,
|
|
4452
|
-
defaults: false,
|
|
4453
|
-
arrays: true,
|
|
4454
|
-
objects: true,
|
|
4455
|
-
oneofs: true
|
|
4456
|
-
});
|
|
4457
|
-
}
|
|
4458
|
-
function removeExpiredRealtimeData(config) {
|
|
4459
|
-
const db = openDb(config);
|
|
4460
|
-
log(config)(`Removing expired GTFS-Realtime data`);
|
|
4461
|
-
db.prepare(
|
|
4462
|
-
`DELETE FROM vehicle_positions WHERE expiration_timestamp <= strftime('%s','now')`
|
|
4463
|
-
).run();
|
|
4464
|
-
db.prepare(
|
|
4465
|
-
`DELETE FROM trip_updates WHERE expiration_timestamp <= strftime('%s','now')`
|
|
4466
|
-
).run();
|
|
4467
|
-
db.prepare(
|
|
4468
|
-
`DELETE FROM stop_time_updates WHERE expiration_timestamp <= strftime('%s','now')`
|
|
4469
|
-
).run();
|
|
4470
|
-
db.prepare(
|
|
4471
|
-
`DELETE FROM service_alerts WHERE expiration_timestamp <= strftime('%s','now')`
|
|
4472
|
-
).run();
|
|
4473
|
-
db.prepare(
|
|
4474
|
-
`DELETE FROM service_alert_informed_entities WHERE expiration_timestamp <= strftime('%s','now')`
|
|
4475
|
-
).run();
|
|
4476
|
-
log(config)(`Removed expired GTFS-Realtime data\r`, true);
|
|
4477
|
-
}
|
|
4447
|
+
var BATCH_SIZE = 1e3;
|
|
4448
|
+
var MAX_RETRIES = 3;
|
|
4449
|
+
var RETRY_DELAY = 1e3;
|
|
4478
4450
|
function prepareRealtimeFieldValue(entity, column, task) {
|
|
4479
4451
|
if (column.name === "created_timestamp") {
|
|
4480
4452
|
return task.currentTimestamp;
|
|
@@ -4491,163 +4463,265 @@ function prepareRealtimeFieldValue(entity, column, task) {
|
|
|
4491
4463
|
);
|
|
4492
4464
|
return column.type === "json" ? JSON.stringify(prefixedValue) : prefixedValue;
|
|
4493
4465
|
}
|
|
4494
|
-
|
|
4495
|
-
const
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
);
|
|
4500
|
-
const informedEntityStmt = db.prepare(
|
|
4501
|
-
`REPLACE INTO ${serviceAlertInformedEntities.filenameBase} (${serviceAlertInformedEntities.schema.map((column) => column.name).join(
|
|
4502
|
-
", "
|
|
4503
|
-
)}) VALUES (${serviceAlertInformedEntities.schema.map(() => "?").join(", ")})`
|
|
4466
|
+
function createPreparedStatement(db, model) {
|
|
4467
|
+
const columns = model.schema.map((column) => column.name);
|
|
4468
|
+
const placeholders = model.schema.map(() => "?").join(", ");
|
|
4469
|
+
return db.prepare(
|
|
4470
|
+
`REPLACE INTO ${model.filenameBase} (${columns.join(", ")}) VALUES (${placeholders})`
|
|
4504
4471
|
);
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4472
|
+
}
|
|
4473
|
+
async function processBatch(items, batchSize, processor) {
|
|
4474
|
+
let totalRecordCount = 0;
|
|
4475
|
+
let totalErrorCount = 0;
|
|
4476
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
4477
|
+
const batch = items.slice(i, i + batchSize);
|
|
4478
|
+
try {
|
|
4479
|
+
const result = await processor(batch);
|
|
4480
|
+
totalRecordCount += result.recordCount;
|
|
4481
|
+
totalErrorCount += result.errorCount;
|
|
4482
|
+
} catch (error) {
|
|
4483
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4484
|
+
totalErrorCount += batch.length;
|
|
4485
|
+
console.error(`Batch processing error: ${errorMessage}`);
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
return { recordCount: totalRecordCount, errorCount: totalErrorCount };
|
|
4489
|
+
}
|
|
4490
|
+
async function fetchGtfsRealtimeData(type, task) {
|
|
4491
|
+
const urlConfig = getUrlConfig(type, task);
|
|
4492
|
+
if (!urlConfig) {
|
|
4493
|
+
return null;
|
|
4494
|
+
}
|
|
4495
|
+
task.log(`Importing - GTFS-Realtime from ${urlConfig.url}`);
|
|
4496
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
4497
|
+
try {
|
|
4498
|
+
const response = await fetch(urlConfig.url, {
|
|
4499
|
+
method: "GET",
|
|
4500
|
+
headers: {
|
|
4501
|
+
...urlConfig.headers ?? {},
|
|
4502
|
+
"Accept-Encoding": "gzip"
|
|
4503
|
+
},
|
|
4504
|
+
signal: task.downloadTimeout ? AbortSignal.timeout(task.downloadTimeout) : void 0
|
|
4505
|
+
});
|
|
4506
|
+
if (response.status !== 200) {
|
|
4507
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
4508
|
+
}
|
|
4509
|
+
const buffer = await response.arrayBuffer();
|
|
4510
|
+
const message = GtfsRealtimeBindings.transit_realtime.FeedMessage.decode(
|
|
4511
|
+
new Uint8Array(buffer)
|
|
4510
4512
|
);
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4513
|
+
const feedMessage = GtfsRealtimeBindings.transit_realtime.FeedMessage.toObject(message, {
|
|
4514
|
+
enums: String,
|
|
4515
|
+
longs: String,
|
|
4516
|
+
bytes: String,
|
|
4517
|
+
defaults: false,
|
|
4518
|
+
arrays: true,
|
|
4519
|
+
objects: true,
|
|
4520
|
+
oneofs: true
|
|
4521
|
+
});
|
|
4522
|
+
return feedMessage;
|
|
4523
|
+
} catch (error) {
|
|
4524
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4525
|
+
if (attempt === MAX_RETRIES) {
|
|
4526
|
+
if (task.ignoreErrors) {
|
|
4527
|
+
task.logError(
|
|
4528
|
+
`Failed to fetch ${type} after ${MAX_RETRIES} attempts: ${errorMessage}`
|
|
4529
|
+
);
|
|
4530
|
+
return null;
|
|
4531
|
+
}
|
|
4532
|
+
throw error;
|
|
4533
|
+
}
|
|
4534
|
+
task.logWarning(`Attempt ${attempt} failed for ${type}: ${errorMessage}`);
|
|
4535
|
+
await new Promise(
|
|
4536
|
+
(resolve) => setTimeout(resolve, RETRY_DELAY * attempt)
|
|
4537
|
+
);
|
|
4538
|
+
}
|
|
4539
|
+
}
|
|
4540
|
+
return null;
|
|
4541
|
+
}
|
|
4542
|
+
function getUrlConfig(type, task) {
|
|
4543
|
+
switch (type) {
|
|
4544
|
+
case "alerts":
|
|
4545
|
+
return task.realtimeAlerts;
|
|
4546
|
+
case "tripupdates":
|
|
4547
|
+
return task.realtimeTripUpdates;
|
|
4548
|
+
case "vehiclepositions":
|
|
4549
|
+
return task.realtimeVehiclePositions;
|
|
4550
|
+
default:
|
|
4551
|
+
return void 0;
|
|
4552
|
+
}
|
|
4553
|
+
}
|
|
4554
|
+
function createServiceAlertsProcessor(db, task) {
|
|
4555
|
+
const alertStmt = createPreparedStatement(db, serviceAlerts);
|
|
4556
|
+
const informedEntityStmt = createPreparedStatement(
|
|
4557
|
+
db,
|
|
4558
|
+
serviceAlertInformedEntities
|
|
4559
|
+
);
|
|
4560
|
+
return async (batch) => {
|
|
4561
|
+
let recordCount = 0;
|
|
4562
|
+
let errorCount = 0;
|
|
4563
|
+
db.transaction(() => {
|
|
4564
|
+
for (const entity of batch) {
|
|
4565
|
+
try {
|
|
4566
|
+
const alertValues = serviceAlerts.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
|
|
4567
|
+
alertStmt.run(alertValues);
|
|
4568
|
+
recordCount++;
|
|
4569
|
+
if (entity.alert?.informedEntity?.length) {
|
|
4570
|
+
for (const informedEntity of entity.alert.informedEntity) {
|
|
4516
4571
|
informedEntity.parent = entity;
|
|
4517
|
-
|
|
4572
|
+
const entityValues = serviceAlertInformedEntities.schema.map(
|
|
4518
4573
|
(column) => prepareRealtimeFieldValue(informedEntity, column, task)
|
|
4519
4574
|
);
|
|
4575
|
+
informedEntityStmt.run(entityValues);
|
|
4576
|
+
recordCount++;
|
|
4520
4577
|
}
|
|
4521
|
-
);
|
|
4522
|
-
for (const values of informedEntities) {
|
|
4523
|
-
informedEntityStmt.run(values);
|
|
4524
4578
|
}
|
|
4579
|
+
} catch (error) {
|
|
4580
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4581
|
+
errorCount++;
|
|
4582
|
+
task.logWarning(`Alert processing error: ${errorMessage}`);
|
|
4525
4583
|
}
|
|
4526
|
-
totalLineCount++;
|
|
4527
|
-
} catch (error) {
|
|
4528
|
-
task.logWarning(`Import error: ${error.message}`);
|
|
4529
4584
|
}
|
|
4530
|
-
}
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
true
|
|
4534
|
-
);
|
|
4535
|
-
})();
|
|
4585
|
+
})();
|
|
4586
|
+
return { recordCount, errorCount };
|
|
4587
|
+
};
|
|
4536
4588
|
}
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
", "
|
|
4542
|
-
)}) VALUES (${tripUpdates.schema.map(() => "?").join(", ")})`
|
|
4589
|
+
function createTripUpdatesProcessor(db, task) {
|
|
4590
|
+
const tripUpdateStmt = createPreparedStatement(
|
|
4591
|
+
db,
|
|
4592
|
+
tripUpdates
|
|
4543
4593
|
);
|
|
4544
|
-
const stopTimeStmt =
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
)}) VALUES (${stopTimeUpdates.schema.map(() => "?").join(", ")})`
|
|
4594
|
+
const stopTimeStmt = createPreparedStatement(
|
|
4595
|
+
db,
|
|
4596
|
+
stopTimeUpdates
|
|
4548
4597
|
);
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
(
|
|
4560
|
-
|
|
4561
|
-
|
|
4598
|
+
return async (batch) => {
|
|
4599
|
+
let recordCount = 0;
|
|
4600
|
+
let errorCount = 0;
|
|
4601
|
+
db.transaction(() => {
|
|
4602
|
+
for (const entity of batch) {
|
|
4603
|
+
try {
|
|
4604
|
+
const tripUpdateValues = tripUpdates.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
|
|
4605
|
+
tripUpdateStmt.run(tripUpdateValues);
|
|
4606
|
+
recordCount++;
|
|
4607
|
+
if (entity.tripUpdate?.stopTimeUpdate?.length) {
|
|
4608
|
+
for (const stopTimeUpdate of entity.tripUpdate.stopTimeUpdate) {
|
|
4609
|
+
stopTimeUpdate.parent = entity;
|
|
4610
|
+
const stopTimeValues = stopTimeUpdates.schema.map(
|
|
4611
|
+
(column) => prepareRealtimeFieldValue(stopTimeUpdate, column, task)
|
|
4612
|
+
);
|
|
4613
|
+
stopTimeStmt.run(stopTimeValues);
|
|
4614
|
+
recordCount++;
|
|
4615
|
+
}
|
|
4616
|
+
}
|
|
4617
|
+
} catch (error) {
|
|
4618
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4619
|
+
errorCount++;
|
|
4620
|
+
task.logWarning(`Trip update processing error: ${errorMessage}`);
|
|
4562
4621
|
}
|
|
4563
|
-
totalLineCount++;
|
|
4564
|
-
} catch (error) {
|
|
4565
|
-
task.logWarning(`Import error: ${error.message}`);
|
|
4566
4622
|
}
|
|
4567
|
-
}
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
true
|
|
4571
|
-
);
|
|
4572
|
-
})();
|
|
4623
|
+
})();
|
|
4624
|
+
return { recordCount, errorCount };
|
|
4625
|
+
};
|
|
4573
4626
|
}
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
", "
|
|
4579
|
-
)}) VALUES (${vehiclePositions.schema.map(() => "?").join(", ")})`
|
|
4627
|
+
function createVehiclePositionsProcessor(db, task) {
|
|
4628
|
+
const vehiclePositionStmt = createPreparedStatement(
|
|
4629
|
+
db,
|
|
4630
|
+
vehiclePositions
|
|
4580
4631
|
);
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4632
|
+
return async (batch) => {
|
|
4633
|
+
let recordCount = 0;
|
|
4634
|
+
let errorCount = 0;
|
|
4635
|
+
db.transaction(() => {
|
|
4636
|
+
for (const entity of batch) {
|
|
4637
|
+
try {
|
|
4638
|
+
const fieldValues = vehiclePositions.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
|
|
4639
|
+
vehiclePositionStmt.run(fieldValues);
|
|
4640
|
+
recordCount++;
|
|
4641
|
+
} catch (error) {
|
|
4642
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4643
|
+
errorCount++;
|
|
4644
|
+
task.logWarning(`Vehicle position processing error: ${errorMessage}`);
|
|
4645
|
+
}
|
|
4589
4646
|
}
|
|
4647
|
+
})();
|
|
4648
|
+
return { recordCount, errorCount };
|
|
4649
|
+
};
|
|
4650
|
+
}
|
|
4651
|
+
function removeExpiredRealtimeData(config) {
|
|
4652
|
+
const db = openDb(config);
|
|
4653
|
+
log(config)(`Removing expired GTFS-Realtime data`);
|
|
4654
|
+
db.transaction(() => {
|
|
4655
|
+
const tables = [
|
|
4656
|
+
"vehicle_positions",
|
|
4657
|
+
"trip_updates",
|
|
4658
|
+
"stop_time_updates",
|
|
4659
|
+
"service_alerts",
|
|
4660
|
+
"service_alert_informed_entities"
|
|
4661
|
+
];
|
|
4662
|
+
for (const table of tables) {
|
|
4663
|
+
db.prepare(
|
|
4664
|
+
`DELETE FROM ${table} WHERE expiration_timestamp <= strftime('%s','now')`
|
|
4665
|
+
).run();
|
|
4590
4666
|
}
|
|
4591
|
-
task.log(
|
|
4592
|
-
`Importing - GTFS-Realtime vehicle positions - ${totalLineCount} entries imported\r`,
|
|
4593
|
-
true
|
|
4594
|
-
);
|
|
4595
4667
|
})();
|
|
4668
|
+
log(config)(`Removed expired GTFS-Realtime data\r`, true);
|
|
4596
4669
|
}
|
|
4597
4670
|
async function updateGtfsRealtimeData(task) {
|
|
4598
|
-
if (task.realtimeAlerts
|
|
4671
|
+
if (!task.realtimeAlerts && !task.realtimeTripUpdates && !task.realtimeVehiclePositions) {
|
|
4599
4672
|
return;
|
|
4600
4673
|
}
|
|
4674
|
+
const [alertsData, tripUpdatesData, vehiclePositionsData] = await Promise.all(
|
|
4675
|
+
[
|
|
4676
|
+
task.realtimeAlerts?.url ? fetchGtfsRealtimeData("alerts", task) : null,
|
|
4677
|
+
task.realtimeTripUpdates?.url ? fetchGtfsRealtimeData("tripupdates", task) : null,
|
|
4678
|
+
task.realtimeVehiclePositions?.url ? fetchGtfsRealtimeData("vehiclepositions", task) : null
|
|
4679
|
+
]
|
|
4680
|
+
);
|
|
4601
4681
|
const db = openDb({ sqlitePath: task.sqlitePath });
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4682
|
+
const recordCounts = {
|
|
4683
|
+
alerts: 0,
|
|
4684
|
+
tripupdates: 0,
|
|
4685
|
+
vehiclepositions: 0
|
|
4686
|
+
};
|
|
4687
|
+
const processingPromises = [];
|
|
4688
|
+
if (alertsData?.entity?.length) {
|
|
4689
|
+
processingPromises.push(
|
|
4690
|
+
processBatch(
|
|
4691
|
+
alertsData.entity,
|
|
4692
|
+
BATCH_SIZE,
|
|
4693
|
+
createServiceAlertsProcessor(db, task)
|
|
4694
|
+
).then((result) => {
|
|
4695
|
+
recordCounts.alerts = result.recordCount;
|
|
4696
|
+
})
|
|
4697
|
+
);
|
|
4615
4698
|
}
|
|
4616
|
-
if (
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
}
|
|
4625
|
-
|
|
4626
|
-
if (task.ignoreErrors) {
|
|
4627
|
-
task.logError(error.message);
|
|
4628
|
-
} else {
|
|
4629
|
-
throw error;
|
|
4630
|
-
}
|
|
4631
|
-
}
|
|
4699
|
+
if (tripUpdatesData?.entity?.length) {
|
|
4700
|
+
processingPromises.push(
|
|
4701
|
+
processBatch(
|
|
4702
|
+
tripUpdatesData.entity,
|
|
4703
|
+
BATCH_SIZE,
|
|
4704
|
+
createTripUpdatesProcessor(db, task)
|
|
4705
|
+
).then((result) => {
|
|
4706
|
+
recordCounts.tripupdates = result.recordCount;
|
|
4707
|
+
})
|
|
4708
|
+
);
|
|
4632
4709
|
}
|
|
4633
|
-
if (
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
}
|
|
4642
|
-
|
|
4643
|
-
if (task.ignoreErrors) {
|
|
4644
|
-
task.logError(error.message);
|
|
4645
|
-
} else {
|
|
4646
|
-
throw error;
|
|
4647
|
-
}
|
|
4648
|
-
}
|
|
4710
|
+
if (vehiclePositionsData?.entity?.length) {
|
|
4711
|
+
processingPromises.push(
|
|
4712
|
+
processBatch(
|
|
4713
|
+
vehiclePositionsData.entity,
|
|
4714
|
+
BATCH_SIZE,
|
|
4715
|
+
createVehiclePositionsProcessor(db, task)
|
|
4716
|
+
).then((result) => {
|
|
4717
|
+
recordCounts.vehiclepositions = result.recordCount;
|
|
4718
|
+
})
|
|
4719
|
+
);
|
|
4649
4720
|
}
|
|
4650
|
-
|
|
4721
|
+
await Promise.all(processingPromises);
|
|
4722
|
+
task.log(
|
|
4723
|
+
`GTFS-Realtime import complete: ${recordCounts.alerts} alerts, ${recordCounts.tripupdates} trip updates, ${recordCounts.vehiclepositions} vehicle positions`
|
|
4724
|
+
);
|
|
4651
4725
|
}
|
|
4652
4726
|
async function updateGtfsRealtime(initialConfig) {
|
|
4653
4727
|
const config = setDefaultConfig(initialConfig);
|
|
@@ -4681,8 +4755,9 @@ async function updateGtfsRealtime(initialConfig) {
|
|
|
4681
4755
|
};
|
|
4682
4756
|
await updateGtfsRealtimeData(task);
|
|
4683
4757
|
} catch (error) {
|
|
4758
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4684
4759
|
if (config.ignoreErrors) {
|
|
4685
|
-
logError(config)(
|
|
4760
|
+
logError(config)(errorMessage);
|
|
4686
4761
|
} else {
|
|
4687
4762
|
throw error;
|
|
4688
4763
|
}
|
|
@@ -4697,7 +4772,7 @@ async function updateGtfsRealtime(initialConfig) {
|
|
|
4697
4772
|
`
|
|
4698
4773
|
);
|
|
4699
4774
|
} catch (error) {
|
|
4700
|
-
if (error
|
|
4775
|
+
if (error.code === "SQLITE_CANTOPEN") {
|
|
4701
4776
|
logError(config)(
|
|
4702
4777
|
`Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`
|
|
4703
4778
|
);
|
|
@@ -4736,7 +4811,7 @@ var extractGtfsFiles = async (task) => {
|
|
|
4736
4811
|
throw new Error("No `path` specified in config");
|
|
4737
4812
|
}
|
|
4738
4813
|
const gtfsPath = untildify(task.path);
|
|
4739
|
-
task.log(`Importing GTFS from ${task.path}\r`);
|
|
4814
|
+
task.log(`Importing static GTFS from ${task.path}\r`);
|
|
4740
4815
|
if (path2.extname(gtfsPath) === ".zip") {
|
|
4741
4816
|
try {
|
|
4742
4817
|
await unzip(gtfsPath, task.downloadDir);
|
|
@@ -4819,11 +4894,11 @@ var createGtfsTables = (db) => {
|
|
|
4819
4894
|
if (column.type === "time") {
|
|
4820
4895
|
sqlColumnCreateStatements.push(
|
|
4821
4896
|
`${getTimestampColumnName(column.name)} INTEGER GENERATED ALWAYS AS (
|
|
4822
|
-
CASE
|
|
4823
|
-
WHEN ${column.name} IS NULL OR ${column.name} = '' THEN NULL
|
|
4897
|
+
CASE
|
|
4898
|
+
WHEN ${column.name} IS NULL OR ${column.name} = '' THEN NULL
|
|
4824
4899
|
ELSE CAST(
|
|
4825
|
-
substr(${column.name}, 1, instr(${column.name}, ':') - 1) * 3600 +
|
|
4826
|
-
substr(${column.name}, instr(${column.name}, ':') + 1, 2) * 60 +
|
|
4900
|
+
substr(${column.name}, 1, instr(${column.name}, ':') - 1) * 3600 +
|
|
4901
|
+
substr(${column.name}, instr(${column.name}, ':') + 1, 2) * 60 +
|
|
4827
4902
|
substr(${column.name}, -2) AS INTEGER
|
|
4828
4903
|
)
|
|
4829
4904
|
END
|
|
@@ -4880,7 +4955,7 @@ var formatGtfsLine = (line, model, totalLineCount) => {
|
|
|
4880
4955
|
continue;
|
|
4881
4956
|
}
|
|
4882
4957
|
if (type === "date") {
|
|
4883
|
-
value = value.replace(/-/g, "");
|
|
4958
|
+
value = value?.toString().replace(/-/g, "");
|
|
4884
4959
|
if (value.length !== 8) {
|
|
4885
4960
|
throw new Error(
|
|
4886
4961
|
`Invalid date in ${filenameBase}.${filenameExtension} for ${name} on line ${lineNumber}.`
|
|
@@ -4896,203 +4971,212 @@ var formatGtfsLine = (line, model, totalLineCount) => {
|
|
|
4896
4971
|
}
|
|
4897
4972
|
return formattedLine;
|
|
4898
4973
|
};
|
|
4899
|
-
var
|
|
4900
|
-
var importGtfsFiles = (db, task) =>
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
task.
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
if (model.extension === "gtfs-realtime") {
|
|
4911
|
-
resolve();
|
|
4912
|
-
return;
|
|
4913
|
-
}
|
|
4914
|
-
const filepath = path2.join(task.downloadDir, `${filename}`);
|
|
4915
|
-
if (!existsSync2(filepath)) {
|
|
4916
|
-
if (!model.nonstandard) {
|
|
4917
|
-
task.log(`Importing - ${filename} - No file found\r`);
|
|
4974
|
+
var BATCH_SIZE2 = 1e5;
|
|
4975
|
+
var importGtfsFiles = async (db, task) => {
|
|
4976
|
+
await mapSeries2(
|
|
4977
|
+
Object.values(models_exports),
|
|
4978
|
+
(model) => new Promise((resolve, reject) => {
|
|
4979
|
+
let totalLineCount = 0;
|
|
4980
|
+
const filename = `${model.filenameBase}.${model.filenameExtension}`;
|
|
4981
|
+
if (task.exclude && task.exclude.includes(model.filenameBase)) {
|
|
4982
|
+
task.log(`Skipping - ${filename}\r`);
|
|
4983
|
+
resolve();
|
|
4984
|
+
return;
|
|
4918
4985
|
}
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
const prepareStatement = `INSERT ${task.ignoreDuplicates ? "OR IGNORE" : ""} INTO ${model.filenameBase} (${columns.map(({ name }) => name).join(", ")}) VALUES (${columns.map(({ name }) => `@${name}`).join(", ")})`;
|
|
4928
|
-
const insert = db.prepare(prepareStatement);
|
|
4929
|
-
const insertLines = db.transaction((lines) => {
|
|
4930
|
-
for (const [rowNumber, line] of Object.entries(lines)) {
|
|
4931
|
-
try {
|
|
4932
|
-
if (task.prefix === void 0) {
|
|
4933
|
-
insert.run(line);
|
|
4934
|
-
} else {
|
|
4935
|
-
const prefixedLine = Object.fromEntries(
|
|
4936
|
-
Object.entries(
|
|
4937
|
-
line
|
|
4938
|
-
).map(([columnName, value]) => [
|
|
4939
|
-
columnName,
|
|
4940
|
-
applyPrefixToValue(
|
|
4941
|
-
value,
|
|
4942
|
-
prefixedColumns.has(columnName),
|
|
4943
|
-
task.prefix
|
|
4944
|
-
)
|
|
4945
|
-
])
|
|
4946
|
-
);
|
|
4947
|
-
insert.run(prefixedLine);
|
|
4948
|
-
}
|
|
4949
|
-
} catch (error) {
|
|
4950
|
-
if (error.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
|
|
4951
|
-
const primaryColumns = columns.filter(
|
|
4952
|
-
(column) => column.primary
|
|
4953
|
-
);
|
|
4954
|
-
task.logWarning(
|
|
4955
|
-
`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`
|
|
4956
|
-
);
|
|
4957
|
-
}
|
|
4958
|
-
task.logWarning(
|
|
4959
|
-
`Check ${filename} for invalid data on line ${rowNumber + 1}.`
|
|
4960
|
-
);
|
|
4961
|
-
throw error;
|
|
4986
|
+
if (model.extension === "gtfs-realtime") {
|
|
4987
|
+
resolve();
|
|
4988
|
+
return;
|
|
4989
|
+
}
|
|
4990
|
+
const filepath = path2.join(task.downloadDir, `${filename}`);
|
|
4991
|
+
if (!existsSync2(filepath)) {
|
|
4992
|
+
if (!model.nonstandard) {
|
|
4993
|
+
task.log(`Importing - ${filename} - No file found\r`);
|
|
4962
4994
|
}
|
|
4995
|
+
resolve();
|
|
4996
|
+
return;
|
|
4963
4997
|
}
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
const
|
|
4967
|
-
columns
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4998
|
+
task.log(`Importing - ${filename}\r`);
|
|
4999
|
+
const columns = model.schema;
|
|
5000
|
+
const prefixedColumns = new Set(
|
|
5001
|
+
columns.filter((column) => column.prefix).map((column) => column.name)
|
|
5002
|
+
);
|
|
5003
|
+
const prepareStatement = `INSERT ${task.ignoreDuplicates ? "OR IGNORE" : ""} INTO ${model.filenameBase} (${columns.map(({ name }) => name).join(", ")}) VALUES (${columns.map(({ name }) => `@${name}`).join(", ")})`;
|
|
5004
|
+
const insert = db.prepare(prepareStatement);
|
|
5005
|
+
const insertLines = db.transaction((lines) => {
|
|
5006
|
+
for (const [rowNumber, line] of Object.entries(lines)) {
|
|
5007
|
+
try {
|
|
5008
|
+
if (task.prefix === void 0) {
|
|
5009
|
+
insert.run(line);
|
|
5010
|
+
} else {
|
|
5011
|
+
const prefixedLine = Object.fromEntries(
|
|
5012
|
+
Object.entries(
|
|
5013
|
+
line
|
|
5014
|
+
).map(([columnName, value]) => [
|
|
5015
|
+
columnName,
|
|
5016
|
+
applyPrefixToValue(
|
|
5017
|
+
value,
|
|
5018
|
+
prefixedColumns.has(columnName),
|
|
5019
|
+
task.prefix
|
|
5020
|
+
)
|
|
5021
|
+
])
|
|
4986
5022
|
);
|
|
5023
|
+
insert.run(prefixedLine);
|
|
4987
5024
|
}
|
|
4988
|
-
}
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
5025
|
+
} catch (error) {
|
|
5026
|
+
if (error.code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
|
|
5027
|
+
const primaryColumns = columns.filter(
|
|
5028
|
+
(column) => column.primary
|
|
5029
|
+
);
|
|
5030
|
+
task.logWarning(
|
|
5031
|
+
`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`
|
|
5032
|
+
);
|
|
5033
|
+
}
|
|
5034
|
+
task.logWarning(
|
|
5035
|
+
`Check ${filename} for invalid data on line ${rowNumber + 1}.`
|
|
5036
|
+
);
|
|
5037
|
+
throw error;
|
|
4995
5038
|
}
|
|
4996
5039
|
}
|
|
4997
5040
|
});
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5041
|
+
if (model.filenameExtension === "txt") {
|
|
5042
|
+
const parser = parse({
|
|
5043
|
+
columns: true,
|
|
5044
|
+
relax_quotes: true,
|
|
5045
|
+
trim: true,
|
|
5046
|
+
skip_empty_lines: true,
|
|
5047
|
+
...task.csvOptions
|
|
5048
|
+
});
|
|
5049
|
+
let lines = [];
|
|
5050
|
+
parser.on("readable", () => {
|
|
5051
|
+
try {
|
|
5052
|
+
let record;
|
|
5053
|
+
while (record = parser.read()) {
|
|
5054
|
+
totalLineCount += 1;
|
|
5055
|
+
lines.push(formatGtfsLine(record, model, totalLineCount));
|
|
5056
|
+
if (lines.length >= BATCH_SIZE2) {
|
|
5057
|
+
insertLines(lines);
|
|
5058
|
+
lines = [];
|
|
5059
|
+
task.log(
|
|
5060
|
+
`Importing - ${filename} - ${totalLineCount} lines imported\r`,
|
|
5061
|
+
true
|
|
5007
5062
|
);
|
|
5008
|
-
resolve();
|
|
5009
|
-
return;
|
|
5010
|
-
} else {
|
|
5011
|
-
reject(error);
|
|
5012
|
-
return;
|
|
5013
5063
|
}
|
|
5014
5064
|
}
|
|
5065
|
+
} catch (error) {
|
|
5066
|
+
if (task.ignoreErrors) {
|
|
5067
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5068
|
+
task.logError(`Error processing ${filename}: ${errorMessage}`);
|
|
5069
|
+
resolve();
|
|
5070
|
+
} else {
|
|
5071
|
+
reject(error);
|
|
5072
|
+
}
|
|
5015
5073
|
}
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5074
|
+
});
|
|
5075
|
+
parser.on("end", () => {
|
|
5076
|
+
try {
|
|
5077
|
+
if (lines.length > 0) {
|
|
5078
|
+
try {
|
|
5079
|
+
insertLines(lines);
|
|
5080
|
+
} catch (error) {
|
|
5081
|
+
if (task.ignoreErrors) {
|
|
5082
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5083
|
+
task.logError(
|
|
5084
|
+
`Error inserting data for ${filename}: ${errorMessage}`
|
|
5085
|
+
);
|
|
5086
|
+
resolve();
|
|
5087
|
+
return;
|
|
5088
|
+
} else {
|
|
5089
|
+
reject(error);
|
|
5090
|
+
return;
|
|
5091
|
+
}
|
|
5092
|
+
}
|
|
5093
|
+
}
|
|
5094
|
+
task.log(
|
|
5095
|
+
`Importing - ${filename} - ${totalLineCount} lines imported\r`,
|
|
5096
|
+
true
|
|
5097
|
+
);
|
|
5024
5098
|
resolve();
|
|
5025
|
-
}
|
|
5026
|
-
|
|
5099
|
+
} catch (error) {
|
|
5100
|
+
if (task.ignoreErrors) {
|
|
5101
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5102
|
+
task.logError(`Error finalizing ${filename}: ${errorMessage}`);
|
|
5103
|
+
resolve();
|
|
5104
|
+
} else {
|
|
5105
|
+
reject(error);
|
|
5106
|
+
}
|
|
5027
5107
|
}
|
|
5028
|
-
}
|
|
5029
|
-
|
|
5030
|
-
parser.on("error", (error) => {
|
|
5031
|
-
if (task.ignoreErrors) {
|
|
5032
|
-
task.logError(`Parser error for ${filename}: ${error.message}`);
|
|
5033
|
-
resolve();
|
|
5034
|
-
} else {
|
|
5035
|
-
reject(error);
|
|
5036
|
-
}
|
|
5037
|
-
});
|
|
5038
|
-
createReadStream(filepath).pipe(stripBomStream()).pipe(parser);
|
|
5039
|
-
} else if (model.filenameExtension === "geojson") {
|
|
5040
|
-
readFile2(filepath, "utf8").then((data) => {
|
|
5041
|
-
if (isValidJSON(data) === false) {
|
|
5108
|
+
});
|
|
5109
|
+
parser.on("error", (error) => {
|
|
5042
5110
|
if (task.ignoreErrors) {
|
|
5043
|
-
|
|
5111
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5112
|
+
task.logError(`Parser error for ${filename}: ${errorMessage}`);
|
|
5044
5113
|
resolve();
|
|
5045
|
-
return;
|
|
5046
5114
|
} else {
|
|
5047
|
-
reject(
|
|
5048
|
-
return;
|
|
5115
|
+
reject(error);
|
|
5049
5116
|
}
|
|
5050
|
-
}
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5117
|
+
});
|
|
5118
|
+
createReadStream(filepath).pipe(stripBomStream()).pipe(parser);
|
|
5119
|
+
} else if (model.filenameExtension === "geojson") {
|
|
5120
|
+
readFile2(filepath, "utf8").then((data) => {
|
|
5121
|
+
if (isValidJSON(data) === false) {
|
|
5122
|
+
if (task.ignoreErrors) {
|
|
5123
|
+
task.logError(`Invalid JSON in ${filename}`);
|
|
5124
|
+
resolve();
|
|
5125
|
+
return;
|
|
5126
|
+
} else {
|
|
5127
|
+
reject(new Error(`Invalid JSON in ${filename}`));
|
|
5128
|
+
return;
|
|
5129
|
+
}
|
|
5130
|
+
}
|
|
5131
|
+
totalLineCount += 1;
|
|
5132
|
+
const line = formatGtfsLine(
|
|
5133
|
+
{ geojson: data },
|
|
5134
|
+
model,
|
|
5135
|
+
totalLineCount
|
|
5062
5136
|
);
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5137
|
+
try {
|
|
5138
|
+
insertLines([line]);
|
|
5139
|
+
task.log(
|
|
5140
|
+
`Importing - ${filename} - ${totalLineCount} lines imported\r`,
|
|
5141
|
+
true
|
|
5068
5142
|
);
|
|
5069
5143
|
resolve();
|
|
5144
|
+
} catch (error) {
|
|
5145
|
+
if (task.ignoreErrors) {
|
|
5146
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5147
|
+
task.logError(
|
|
5148
|
+
`Error inserting data for ${filename}: ${errorMessage}`
|
|
5149
|
+
);
|
|
5150
|
+
resolve();
|
|
5151
|
+
} else {
|
|
5152
|
+
reject(error);
|
|
5153
|
+
}
|
|
5154
|
+
}
|
|
5155
|
+
}).catch((error) => {
|
|
5156
|
+
if (task.ignoreErrors) {
|
|
5157
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5158
|
+
task.logError(`Error reading ${filename}: ${errorMessage}`);
|
|
5159
|
+
resolve();
|
|
5070
5160
|
} else {
|
|
5071
5161
|
reject(error);
|
|
5072
5162
|
}
|
|
5073
|
-
}
|
|
5074
|
-
}
|
|
5163
|
+
});
|
|
5164
|
+
} else {
|
|
5075
5165
|
if (task.ignoreErrors) {
|
|
5076
|
-
task.logError(
|
|
5166
|
+
task.logError(
|
|
5167
|
+
`Unsupported file type: ${model.filenameExtension} for ${filename}`
|
|
5168
|
+
);
|
|
5077
5169
|
resolve();
|
|
5078
5170
|
} else {
|
|
5079
|
-
reject(
|
|
5171
|
+
reject(
|
|
5172
|
+
new Error(`Unsupported file type: ${model.filenameExtension}`)
|
|
5173
|
+
);
|
|
5080
5174
|
}
|
|
5081
|
-
});
|
|
5082
|
-
} else {
|
|
5083
|
-
if (task.ignoreErrors) {
|
|
5084
|
-
task.logError(
|
|
5085
|
-
`Unsupported file type: ${model.filenameExtension} for ${filename}`
|
|
5086
|
-
);
|
|
5087
|
-
resolve();
|
|
5088
|
-
} else {
|
|
5089
|
-
reject(
|
|
5090
|
-
new Error(`Unsupported file type: ${model.filenameExtension}`)
|
|
5091
|
-
);
|
|
5092
5175
|
}
|
|
5093
|
-
}
|
|
5094
|
-
|
|
5095
|
-
);
|
|
5176
|
+
})
|
|
5177
|
+
);
|
|
5178
|
+
task.log(`Static GTFS import complete`);
|
|
5179
|
+
};
|
|
5096
5180
|
async function importGtfs(initialConfig) {
|
|
5097
5181
|
const startTime = process.hrtime.bigint();
|
|
5098
5182
|
const config = setDefaultConfig(initialConfig);
|
|
@@ -5109,7 +5193,6 @@ async function importGtfs(initialConfig) {
|
|
|
5109
5193
|
const tempPath = temporaryDirectory();
|
|
5110
5194
|
const task = {
|
|
5111
5195
|
exclude: agency2.exclude,
|
|
5112
|
-
url: agency2.url,
|
|
5113
5196
|
headers: agency2.headers,
|
|
5114
5197
|
realtimeAlerts: agency2.realtimeAlerts,
|
|
5115
5198
|
realtimeTripUpdates: agency2.realtimeTripUpdates,
|
|
@@ -5117,7 +5200,6 @@ async function importGtfs(initialConfig) {
|
|
|
5117
5200
|
downloadDir: tempPath,
|
|
5118
5201
|
downloadTimeout: config.downloadTimeout,
|
|
5119
5202
|
gtfsRealtimeExpirationSeconds: config.gtfsRealtimeExpirationSeconds,
|
|
5120
|
-
path: agency2.path,
|
|
5121
5203
|
csvOptions: config.csvOptions || {},
|
|
5122
5204
|
ignoreDuplicates: config.ignoreDuplicates,
|
|
5123
5205
|
ignoreErrors: config.ignoreErrors,
|
|
@@ -5128,8 +5210,13 @@ async function importGtfs(initialConfig) {
|
|
|
5128
5210
|
logWarning: logWarning(config),
|
|
5129
5211
|
logError: logError(config)
|
|
5130
5212
|
};
|
|
5131
|
-
if (
|
|
5213
|
+
if ("url" in agency2) {
|
|
5214
|
+
Object.assign(task, { url: agency2.url });
|
|
5132
5215
|
await downloadGtfsFiles(task);
|
|
5216
|
+
} else {
|
|
5217
|
+
Object.assign(task, {
|
|
5218
|
+
path: agency2.path
|
|
5219
|
+
});
|
|
5133
5220
|
}
|
|
5134
5221
|
await extractGtfsFiles(task);
|
|
5135
5222
|
await importGtfsFiles(db, task);
|
|
@@ -5137,7 +5224,8 @@ async function importGtfs(initialConfig) {
|
|
|
5137
5224
|
await rm2(tempPath, { recursive: true });
|
|
5138
5225
|
} catch (error) {
|
|
5139
5226
|
if (config.ignoreErrors) {
|
|
5140
|
-
|
|
5227
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
5228
|
+
logError(config)(errorMessage);
|
|
5141
5229
|
} else {
|
|
5142
5230
|
throw error;
|
|
5143
5231
|
}
|
|
@@ -5152,7 +5240,7 @@ async function importGtfs(initialConfig) {
|
|
|
5152
5240
|
`
|
|
5153
5241
|
);
|
|
5154
5242
|
} catch (error) {
|
|
5155
|
-
if (error
|
|
5243
|
+
if (error.code === "SQLITE_CANTOPEN") {
|
|
5156
5244
|
logError(config)(
|
|
5157
5245
|
`Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`
|
|
5158
5246
|
);
|
|
@@ -5171,7 +5259,7 @@ import mapSeries3 from "promise-map-series";
|
|
|
5171
5259
|
var getAgencies = (db, config) => {
|
|
5172
5260
|
try {
|
|
5173
5261
|
return db.prepare("SELECT agency_name FROM agency;").all();
|
|
5174
|
-
} catch
|
|
5262
|
+
} catch {
|
|
5175
5263
|
if (config.sqlitePath === ":memory:") {
|
|
5176
5264
|
throw new Error(
|
|
5177
5265
|
'No agencies found in SQLite. You are using an in-memory database - if running this from command line be sure to specify a value for `sqlitePath` in config.json other than ":memory:".'
|
|
@@ -5238,11 +5326,17 @@ var exportGtfs = async (initialConfig) => {
|
|
|
5238
5326
|
}
|
|
5239
5327
|
} else if (model.filenameBase === "fare_attributes") {
|
|
5240
5328
|
for (const line of lines) {
|
|
5241
|
-
line.price = formatCurrency(
|
|
5329
|
+
line.price = formatCurrency(
|
|
5330
|
+
line.price,
|
|
5331
|
+
line.currency_type
|
|
5332
|
+
);
|
|
5242
5333
|
}
|
|
5243
5334
|
} else if (model.filenameBase === "fare_products") {
|
|
5244
5335
|
for (const line of lines) {
|
|
5245
|
-
line.amount = formatCurrency(
|
|
5336
|
+
line.amount = formatCurrency(
|
|
5337
|
+
line.amount,
|
|
5338
|
+
line.currency
|
|
5339
|
+
);
|
|
5246
5340
|
}
|
|
5247
5341
|
}
|
|
5248
5342
|
const columns = without(
|
|
@@ -5617,10 +5711,7 @@ function getRoutes(query = {}, fields = [], orderBy2 = [], options = {}) {
|
|
|
5617
5711
|
let whereClause = "";
|
|
5618
5712
|
const orderByClause = formatOrderByClause(orderBy2);
|
|
5619
5713
|
const routeQuery = omit3(query, ["stop_id", "service_id"]);
|
|
5620
|
-
const tripQuery = pick(query, [
|
|
5621
|
-
"stop_id",
|
|
5622
|
-
"service_id"
|
|
5623
|
-
]);
|
|
5714
|
+
const tripQuery = pick(query, ["stop_id", "service_id"]);
|
|
5624
5715
|
const whereClauses = Object.entries(routeQuery).map(
|
|
5625
5716
|
([key, value]) => formatWhereClause(key, value)
|
|
5626
5717
|
);
|
|
@@ -5668,7 +5759,12 @@ function getShapes(query = {}, fields = [], orderBy2 = [], options = {}) {
|
|
|
5668
5759
|
"service_id",
|
|
5669
5760
|
"direction_id"
|
|
5670
5761
|
]);
|
|
5671
|
-
const tripQuery = pick2(query, [
|
|
5762
|
+
const tripQuery = pick2(query, [
|
|
5763
|
+
"route_id",
|
|
5764
|
+
"trip_id",
|
|
5765
|
+
"service_id",
|
|
5766
|
+
"direction_id"
|
|
5767
|
+
]);
|
|
5672
5768
|
const whereClauses = Object.entries(shapeQuery).map(
|
|
5673
5769
|
([key, value]) => formatWhereClause(key, value)
|
|
5674
5770
|
);
|
|
@@ -5692,12 +5788,7 @@ function getShapesAsGeoJSON(query = {}, options = {}) {
|
|
|
5692
5788
|
route_id: route.route_id,
|
|
5693
5789
|
...omit4(query, "route_id")
|
|
5694
5790
|
};
|
|
5695
|
-
const shapes2 = getShapes(
|
|
5696
|
-
shapeQuery,
|
|
5697
|
-
["shape_id", "shape_pt_sequence", "shape_pt_lon", "shape_pt_lat"],
|
|
5698
|
-
[],
|
|
5699
|
-
options
|
|
5700
|
-
);
|
|
5791
|
+
const shapes2 = getShapes(shapeQuery, [], [], options);
|
|
5701
5792
|
if (shapes2.length === 0) {
|
|
5702
5793
|
return;
|
|
5703
5794
|
}
|
|
@@ -5775,7 +5866,7 @@ function getStops(query = {}, fields = [], orderBy2 = [], options = {}) {
|
|
|
5775
5866
|
if (options.bounding_box_side_m !== void 0) {
|
|
5776
5867
|
stopQueryOmitKeys.push("stop_lat", "stop_lon");
|
|
5777
5868
|
}
|
|
5778
|
-
|
|
5869
|
+
const stopQuery = omit5(query, stopQueryOmitKeys);
|
|
5779
5870
|
const tripQuery = pick3(query, [
|
|
5780
5871
|
"route_id",
|
|
5781
5872
|
"trip_id",
|
|
@@ -5840,7 +5931,7 @@ function getStoptimes(query = {}, fields = [], orderBy2 = [], options = {}) {
|
|
|
5840
5931
|
let whereClause = "";
|
|
5841
5932
|
const orderByClause = formatOrderByClause(orderBy2);
|
|
5842
5933
|
const stoptimeQueryOmitKeys = ["date", "start_time", "end_time"];
|
|
5843
|
-
|
|
5934
|
+
const stoptimeQuery = omit6(query, stoptimeQueryOmitKeys);
|
|
5844
5935
|
const whereClauses = Object.entries(stoptimeQuery).map(
|
|
5845
5936
|
([key, value]) => formatWhereClause(key, value)
|
|
5846
5937
|
);
|
|
@@ -5848,7 +5939,7 @@ function getStoptimes(query = {}, fields = [], orderBy2 = [], options = {}) {
|
|
|
5848
5939
|
if (typeof query.date !== "number") {
|
|
5849
5940
|
throw new Error("`date` must be a number in yyyymmdd format");
|
|
5850
5941
|
}
|
|
5851
|
-
const serviceIds = getServiceIdsByDate(query.date);
|
|
5942
|
+
const serviceIds = getServiceIdsByDate(query.date, options);
|
|
5852
5943
|
const tripSubquery = `SELECT DISTINCT trip_id FROM trips WHERE service_id IN (${serviceIds.map((id) => sqlString4.escape(id)).join(",")})`;
|
|
5853
5944
|
whereClauses.push(`trip_id IN (${tripSubquery})`);
|
|
5854
5945
|
}
|
|
@@ -5922,7 +6013,7 @@ function getTrips(query = {}, fields = [], orderBy2 = [], options = {}) {
|
|
|
5922
6013
|
let whereClause = "";
|
|
5923
6014
|
const orderByClause = formatOrderByClause(orderBy2);
|
|
5924
6015
|
const tripQueryOmitKeys = ["date"];
|
|
5925
|
-
|
|
6016
|
+
const tripQuery = omit7(query, tripQueryOmitKeys);
|
|
5926
6017
|
const whereClauses = Object.entries(tripQuery).map(
|
|
5927
6018
|
([key, value]) => formatWhereClause(key, value)
|
|
5928
6019
|
);
|
|
@@ -5930,7 +6021,7 @@ function getTrips(query = {}, fields = [], orderBy2 = [], options = {}) {
|
|
|
5930
6021
|
if (typeof query.date !== "number") {
|
|
5931
6022
|
throw new Error("`date` must be a number in yyyymmdd format");
|
|
5932
6023
|
}
|
|
5933
|
-
const serviceIds = getServiceIdsByDate(query.date);
|
|
6024
|
+
const serviceIds = getServiceIdsByDate(query.date, options);
|
|
5934
6025
|
whereClauses.push(
|
|
5935
6026
|
`service_id IN (${serviceIds.map((id) => sqlString5.escape(id)).join(",")})`
|
|
5936
6027
|
);
|
|
@@ -6212,6 +6303,7 @@ export {
|
|
|
6212
6303
|
closeDb,
|
|
6213
6304
|
deleteDb,
|
|
6214
6305
|
exportGtfs,
|
|
6306
|
+
generateFolderName,
|
|
6215
6307
|
getAgencies2 as getAgencies,
|
|
6216
6308
|
getAreas,
|
|
6217
6309
|
getAttributions,
|
|
@@ -6272,6 +6364,9 @@ export {
|
|
|
6272
6364
|
getVehiclePositions,
|
|
6273
6365
|
importGtfs,
|
|
6274
6366
|
openDb,
|
|
6367
|
+
prepDirectory,
|
|
6368
|
+
untildify,
|
|
6369
|
+
unzip,
|
|
6275
6370
|
updateGtfsRealtime
|
|
6276
6371
|
};
|
|
6277
6372
|
//# sourceMappingURL=index.js.map
|