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
|
@@ -8,10 +8,10 @@ import PrettyError from "pretty-error";
|
|
|
8
8
|
// src/lib/file-utils.ts
|
|
9
9
|
import path from "path";
|
|
10
10
|
import { existsSync } from "fs";
|
|
11
|
+
import { homedir } from "os";
|
|
11
12
|
import { mkdir, readFile, rm } from "fs/promises";
|
|
12
13
|
import { omit, snakeCase } from "lodash-es";
|
|
13
14
|
import sanitize from "sanitize-filename";
|
|
14
|
-
import untildify from "untildify";
|
|
15
15
|
import StreamZip from "node-stream-zip";
|
|
16
16
|
|
|
17
17
|
// src/lib/log-utils.ts
|
|
@@ -65,6 +65,7 @@ function formatError(error) {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
// src/lib/file-utils.ts
|
|
68
|
+
var homeDirectory = homedir();
|
|
68
69
|
async function getConfig(argv2) {
|
|
69
70
|
let config;
|
|
70
71
|
let data;
|
|
@@ -101,17 +102,17 @@ async function getConfig(argv2) {
|
|
|
101
102
|
throw error;
|
|
102
103
|
}
|
|
103
104
|
}
|
|
105
|
+
function untildify(pathWithTilde) {
|
|
106
|
+
return homeDirectory ? pathWithTilde.replace(/^~(?=$|\/|\\)/, homeDirectory) : pathWithTilde;
|
|
107
|
+
}
|
|
104
108
|
|
|
105
109
|
// src/lib/import-gtfs.ts
|
|
106
110
|
import path2 from "path";
|
|
107
111
|
import { createReadStream, existsSync as existsSync2, lstatSync } from "fs";
|
|
108
112
|
import { cp, readdir, rename, readFile as readFile2, rm as rm2, writeFile } from "fs/promises";
|
|
109
113
|
import { parse } from "csv-parse";
|
|
110
|
-
import pluralize2 from "pluralize";
|
|
111
114
|
import stripBomStream from "strip-bom-stream";
|
|
112
115
|
import { temporaryDirectory } from "tempy";
|
|
113
|
-
import Timer from "timer-machine";
|
|
114
|
-
import untildify3 from "untildify";
|
|
115
116
|
import mapSeries2 from "promise-map-series";
|
|
116
117
|
|
|
117
118
|
// src/models/gtfs-realtime/trip-updates.ts
|
|
@@ -582,10 +583,9 @@ var serviceAlertInformedEntities = {
|
|
|
582
583
|
|
|
583
584
|
// src/lib/db.ts
|
|
584
585
|
import Database from "better-sqlite3";
|
|
585
|
-
import untildify2 from "untildify";
|
|
586
586
|
var dbs = {};
|
|
587
587
|
function setupDb(sqlitePath) {
|
|
588
|
-
const db = new Database(
|
|
588
|
+
const db = new Database(untildify(sqlitePath));
|
|
589
589
|
db.pragma("journal_mode = OFF");
|
|
590
590
|
db.pragma("synchronous = OFF");
|
|
591
591
|
db.pragma("temp_store = MEMORY");
|
|
@@ -632,7 +632,6 @@ import {
|
|
|
632
632
|
import { feature, featureCollection } from "@turf/helpers";
|
|
633
633
|
|
|
634
634
|
// src/lib/import-gtfs-realtime.ts
|
|
635
|
-
import pluralize from "pluralize";
|
|
636
635
|
import GtfsRealtimeBindings from "gtfs-realtime-bindings";
|
|
637
636
|
import mapSeries from "promise-map-series";
|
|
638
637
|
import { get } from "lodash-es";
|
|
@@ -659,7 +658,8 @@ function setDefaultConfig(initialConfig) {
|
|
|
659
658
|
ignoreDuplicates: false,
|
|
660
659
|
ignoreErrors: false,
|
|
661
660
|
gtfsRealtimeExpirationSeconds: 0,
|
|
662
|
-
verbose: true
|
|
661
|
+
verbose: true,
|
|
662
|
+
downloadTimeout: 3e4
|
|
663
663
|
};
|
|
664
664
|
return {
|
|
665
665
|
...defaults,
|
|
@@ -678,58 +678,14 @@ function applyPrefixToValue(value, columnShouldBePrefixed, prefix) {
|
|
|
678
678
|
}
|
|
679
679
|
return `${prefix}${value}`;
|
|
680
680
|
}
|
|
681
|
+
function pluralize(singularWord, pluralWord, count) {
|
|
682
|
+
return count === 1 ? singularWord : pluralWord;
|
|
683
|
+
}
|
|
681
684
|
|
|
682
685
|
// src/lib/import-gtfs-realtime.ts
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
method: "GET",
|
|
687
|
-
headers: {
|
|
688
|
-
...urlConfig.headers ?? {},
|
|
689
|
-
"Accept-Encoding": "gzip"
|
|
690
|
-
},
|
|
691
|
-
signal: task.downloadTimeout ? AbortSignal.timeout(task.downloadTimeout) : void 0
|
|
692
|
-
});
|
|
693
|
-
if (response.status !== 200) {
|
|
694
|
-
task.logWarning(
|
|
695
|
-
`Unable to download GTFS-Realtime from ${urlConfig.url}. Got status ${response.status}.`
|
|
696
|
-
);
|
|
697
|
-
return null;
|
|
698
|
-
}
|
|
699
|
-
const buffer = await response.arrayBuffer();
|
|
700
|
-
const message = GtfsRealtimeBindings.transit_realtime.FeedMessage.decode(
|
|
701
|
-
new Uint8Array(buffer)
|
|
702
|
-
);
|
|
703
|
-
return GtfsRealtimeBindings.transit_realtime.FeedMessage.toObject(message, {
|
|
704
|
-
enums: String,
|
|
705
|
-
longs: String,
|
|
706
|
-
bytes: String,
|
|
707
|
-
defaults: false,
|
|
708
|
-
arrays: true,
|
|
709
|
-
objects: true,
|
|
710
|
-
oneofs: true
|
|
711
|
-
});
|
|
712
|
-
}
|
|
713
|
-
function removeExpiredRealtimeData(config) {
|
|
714
|
-
const db = openDb(config);
|
|
715
|
-
log(config)(`Removing expired GTFS-Realtime data`);
|
|
716
|
-
db.prepare(
|
|
717
|
-
`DELETE FROM vehicle_positions WHERE expiration_timestamp <= strftime('%s','now')`
|
|
718
|
-
).run();
|
|
719
|
-
db.prepare(
|
|
720
|
-
`DELETE FROM trip_updates WHERE expiration_timestamp <= strftime('%s','now')`
|
|
721
|
-
).run();
|
|
722
|
-
db.prepare(
|
|
723
|
-
`DELETE FROM stop_time_updates WHERE expiration_timestamp <= strftime('%s','now')`
|
|
724
|
-
).run();
|
|
725
|
-
db.prepare(
|
|
726
|
-
`DELETE FROM service_alerts WHERE expiration_timestamp <= strftime('%s','now')`
|
|
727
|
-
).run();
|
|
728
|
-
db.prepare(
|
|
729
|
-
`DELETE FROM service_alert_informed_entities WHERE expiration_timestamp <= strftime('%s','now')`
|
|
730
|
-
).run();
|
|
731
|
-
log(config)(`Removed expired GTFS-Realtime data\r`, true);
|
|
732
|
-
}
|
|
686
|
+
var BATCH_SIZE = 1e3;
|
|
687
|
+
var MAX_RETRIES = 3;
|
|
688
|
+
var RETRY_DELAY = 1e3;
|
|
733
689
|
function prepareRealtimeFieldValue(entity, column, task) {
|
|
734
690
|
if (column.name === "created_timestamp") {
|
|
735
691
|
return task.currentTimestamp;
|
|
@@ -746,163 +702,265 @@ function prepareRealtimeFieldValue(entity, column, task) {
|
|
|
746
702
|
);
|
|
747
703
|
return column.type === "json" ? JSON.stringify(prefixedValue) : prefixedValue;
|
|
748
704
|
}
|
|
749
|
-
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
);
|
|
755
|
-
const informedEntityStmt = db.prepare(
|
|
756
|
-
`REPLACE INTO ${serviceAlertInformedEntities.filenameBase} (${serviceAlertInformedEntities.schema.map((column) => column.name).join(
|
|
757
|
-
", "
|
|
758
|
-
)}) VALUES (${serviceAlertInformedEntities.schema.map(() => "?").join(", ")})`
|
|
705
|
+
function createPreparedStatement(db, model) {
|
|
706
|
+
const columns = model.schema.map((column) => column.name);
|
|
707
|
+
const placeholders = model.schema.map(() => "?").join(", ");
|
|
708
|
+
return db.prepare(
|
|
709
|
+
`REPLACE INTO ${model.filenameBase} (${columns.join(", ")}) VALUES (${placeholders})`
|
|
759
710
|
);
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
711
|
+
}
|
|
712
|
+
async function processBatch(items, batchSize, processor) {
|
|
713
|
+
let totalRecordCount = 0;
|
|
714
|
+
let totalErrorCount = 0;
|
|
715
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
716
|
+
const batch = items.slice(i, i + batchSize);
|
|
717
|
+
try {
|
|
718
|
+
const result = await processor(batch);
|
|
719
|
+
totalRecordCount += result.recordCount;
|
|
720
|
+
totalErrorCount += result.errorCount;
|
|
721
|
+
} catch (error) {
|
|
722
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
723
|
+
totalErrorCount += batch.length;
|
|
724
|
+
console.error(`Batch processing error: ${errorMessage}`);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
return { recordCount: totalRecordCount, errorCount: totalErrorCount };
|
|
728
|
+
}
|
|
729
|
+
async function fetchGtfsRealtimeData(type, task) {
|
|
730
|
+
const urlConfig = getUrlConfig(type, task);
|
|
731
|
+
if (!urlConfig) {
|
|
732
|
+
return null;
|
|
733
|
+
}
|
|
734
|
+
task.log(`Importing - GTFS-Realtime from ${urlConfig.url}`);
|
|
735
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
736
|
+
try {
|
|
737
|
+
const response = await fetch(urlConfig.url, {
|
|
738
|
+
method: "GET",
|
|
739
|
+
headers: {
|
|
740
|
+
...urlConfig.headers ?? {},
|
|
741
|
+
"Accept-Encoding": "gzip"
|
|
742
|
+
},
|
|
743
|
+
signal: task.downloadTimeout ? AbortSignal.timeout(task.downloadTimeout) : void 0
|
|
744
|
+
});
|
|
745
|
+
if (response.status !== 200) {
|
|
746
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
747
|
+
}
|
|
748
|
+
const buffer = await response.arrayBuffer();
|
|
749
|
+
const message = GtfsRealtimeBindings.transit_realtime.FeedMessage.decode(
|
|
750
|
+
new Uint8Array(buffer)
|
|
765
751
|
);
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
752
|
+
const feedMessage = GtfsRealtimeBindings.transit_realtime.FeedMessage.toObject(message, {
|
|
753
|
+
enums: String,
|
|
754
|
+
longs: String,
|
|
755
|
+
bytes: String,
|
|
756
|
+
defaults: false,
|
|
757
|
+
arrays: true,
|
|
758
|
+
objects: true,
|
|
759
|
+
oneofs: true
|
|
760
|
+
});
|
|
761
|
+
return feedMessage;
|
|
762
|
+
} catch (error) {
|
|
763
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
764
|
+
if (attempt === MAX_RETRIES) {
|
|
765
|
+
if (task.ignoreErrors) {
|
|
766
|
+
task.logError(
|
|
767
|
+
`Failed to fetch ${type} after ${MAX_RETRIES} attempts: ${errorMessage}`
|
|
768
|
+
);
|
|
769
|
+
return null;
|
|
770
|
+
}
|
|
771
|
+
throw error;
|
|
772
|
+
}
|
|
773
|
+
task.logWarning(`Attempt ${attempt} failed for ${type}: ${errorMessage}`);
|
|
774
|
+
await new Promise(
|
|
775
|
+
(resolve) => setTimeout(resolve, RETRY_DELAY * attempt)
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
781
|
+
function getUrlConfig(type, task) {
|
|
782
|
+
switch (type) {
|
|
783
|
+
case "alerts":
|
|
784
|
+
return task.realtimeAlerts;
|
|
785
|
+
case "tripupdates":
|
|
786
|
+
return task.realtimeTripUpdates;
|
|
787
|
+
case "vehiclepositions":
|
|
788
|
+
return task.realtimeVehiclePositions;
|
|
789
|
+
default:
|
|
790
|
+
return void 0;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
function createServiceAlertsProcessor(db, task) {
|
|
794
|
+
const alertStmt = createPreparedStatement(db, serviceAlerts);
|
|
795
|
+
const informedEntityStmt = createPreparedStatement(
|
|
796
|
+
db,
|
|
797
|
+
serviceAlertInformedEntities
|
|
798
|
+
);
|
|
799
|
+
return async (batch) => {
|
|
800
|
+
let recordCount = 0;
|
|
801
|
+
let errorCount = 0;
|
|
802
|
+
db.transaction(() => {
|
|
803
|
+
for (const entity of batch) {
|
|
804
|
+
try {
|
|
805
|
+
const alertValues = serviceAlerts.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
|
|
806
|
+
alertStmt.run(alertValues);
|
|
807
|
+
recordCount++;
|
|
808
|
+
if (entity.alert?.informedEntity?.length) {
|
|
809
|
+
for (const informedEntity of entity.alert.informedEntity) {
|
|
771
810
|
informedEntity.parent = entity;
|
|
772
|
-
|
|
811
|
+
const entityValues = serviceAlertInformedEntities.schema.map(
|
|
773
812
|
(column) => prepareRealtimeFieldValue(informedEntity, column, task)
|
|
774
813
|
);
|
|
814
|
+
informedEntityStmt.run(entityValues);
|
|
815
|
+
recordCount++;
|
|
775
816
|
}
|
|
776
|
-
);
|
|
777
|
-
for (const values of informedEntities) {
|
|
778
|
-
informedEntityStmt.run(values);
|
|
779
817
|
}
|
|
818
|
+
} catch (error) {
|
|
819
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
820
|
+
errorCount++;
|
|
821
|
+
task.logWarning(`Alert processing error: ${errorMessage}`);
|
|
780
822
|
}
|
|
781
|
-
totalLineCount++;
|
|
782
|
-
} catch (error) {
|
|
783
|
-
task.logWarning(`Import error: ${error.message}`);
|
|
784
823
|
}
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
true
|
|
789
|
-
);
|
|
790
|
-
})();
|
|
824
|
+
})();
|
|
825
|
+
return { recordCount, errorCount };
|
|
826
|
+
};
|
|
791
827
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
", "
|
|
797
|
-
)}) VALUES (${tripUpdates.schema.map(() => "?").join(", ")})`
|
|
828
|
+
function createTripUpdatesProcessor(db, task) {
|
|
829
|
+
const tripUpdateStmt = createPreparedStatement(
|
|
830
|
+
db,
|
|
831
|
+
tripUpdates
|
|
798
832
|
);
|
|
799
|
-
const stopTimeStmt =
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
)}) VALUES (${stopTimeUpdates.schema.map(() => "?").join(", ")})`
|
|
833
|
+
const stopTimeStmt = createPreparedStatement(
|
|
834
|
+
db,
|
|
835
|
+
stopTimeUpdates
|
|
803
836
|
);
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
(
|
|
815
|
-
|
|
816
|
-
|
|
837
|
+
return async (batch) => {
|
|
838
|
+
let recordCount = 0;
|
|
839
|
+
let errorCount = 0;
|
|
840
|
+
db.transaction(() => {
|
|
841
|
+
for (const entity of batch) {
|
|
842
|
+
try {
|
|
843
|
+
const tripUpdateValues = tripUpdates.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
|
|
844
|
+
tripUpdateStmt.run(tripUpdateValues);
|
|
845
|
+
recordCount++;
|
|
846
|
+
if (entity.tripUpdate?.stopTimeUpdate?.length) {
|
|
847
|
+
for (const stopTimeUpdate of entity.tripUpdate.stopTimeUpdate) {
|
|
848
|
+
stopTimeUpdate.parent = entity;
|
|
849
|
+
const stopTimeValues = stopTimeUpdates.schema.map(
|
|
850
|
+
(column) => prepareRealtimeFieldValue(stopTimeUpdate, column, task)
|
|
851
|
+
);
|
|
852
|
+
stopTimeStmt.run(stopTimeValues);
|
|
853
|
+
recordCount++;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
} catch (error) {
|
|
857
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
858
|
+
errorCount++;
|
|
859
|
+
task.logWarning(`Trip update processing error: ${errorMessage}`);
|
|
817
860
|
}
|
|
818
|
-
totalLineCount++;
|
|
819
|
-
} catch (error) {
|
|
820
|
-
task.logWarning(`Import error: ${error.message}`);
|
|
821
861
|
}
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
true
|
|
826
|
-
);
|
|
827
|
-
})();
|
|
862
|
+
})();
|
|
863
|
+
return { recordCount, errorCount };
|
|
864
|
+
};
|
|
828
865
|
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
", "
|
|
834
|
-
)}) VALUES (${vehiclePositions.schema.map(() => "?").join(", ")})`
|
|
866
|
+
function createVehiclePositionsProcessor(db, task) {
|
|
867
|
+
const vehiclePositionStmt = createPreparedStatement(
|
|
868
|
+
db,
|
|
869
|
+
vehiclePositions
|
|
835
870
|
);
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
871
|
+
return async (batch) => {
|
|
872
|
+
let recordCount = 0;
|
|
873
|
+
let errorCount = 0;
|
|
874
|
+
db.transaction(() => {
|
|
875
|
+
for (const entity of batch) {
|
|
876
|
+
try {
|
|
877
|
+
const fieldValues = vehiclePositions.schema.map((column) => prepareRealtimeFieldValue(entity, column, task));
|
|
878
|
+
vehiclePositionStmt.run(fieldValues);
|
|
879
|
+
recordCount++;
|
|
880
|
+
} catch (error) {
|
|
881
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
882
|
+
errorCount++;
|
|
883
|
+
task.logWarning(`Vehicle position processing error: ${errorMessage}`);
|
|
884
|
+
}
|
|
844
885
|
}
|
|
886
|
+
})();
|
|
887
|
+
return { recordCount, errorCount };
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
function removeExpiredRealtimeData(config) {
|
|
891
|
+
const db = openDb(config);
|
|
892
|
+
log(config)(`Removing expired GTFS-Realtime data`);
|
|
893
|
+
db.transaction(() => {
|
|
894
|
+
const tables = [
|
|
895
|
+
"vehicle_positions",
|
|
896
|
+
"trip_updates",
|
|
897
|
+
"stop_time_updates",
|
|
898
|
+
"service_alerts",
|
|
899
|
+
"service_alert_informed_entities"
|
|
900
|
+
];
|
|
901
|
+
for (const table of tables) {
|
|
902
|
+
db.prepare(
|
|
903
|
+
`DELETE FROM ${table} WHERE expiration_timestamp <= strftime('%s','now')`
|
|
904
|
+
).run();
|
|
845
905
|
}
|
|
846
|
-
task.log(
|
|
847
|
-
`Importing - GTFS-Realtime vehicle positions - ${totalLineCount} entries imported\r`,
|
|
848
|
-
true
|
|
849
|
-
);
|
|
850
906
|
})();
|
|
907
|
+
log(config)(`Removed expired GTFS-Realtime data\r`, true);
|
|
851
908
|
}
|
|
852
909
|
async function updateGtfsRealtimeData(task) {
|
|
853
|
-
if (task.realtimeAlerts
|
|
910
|
+
if (!task.realtimeAlerts && !task.realtimeTripUpdates && !task.realtimeVehiclePositions) {
|
|
854
911
|
return;
|
|
855
912
|
}
|
|
913
|
+
const [alertsData, tripUpdatesData, vehiclePositionsData] = await Promise.all(
|
|
914
|
+
[
|
|
915
|
+
task.realtimeAlerts?.url ? fetchGtfsRealtimeData("alerts", task) : null,
|
|
916
|
+
task.realtimeTripUpdates?.url ? fetchGtfsRealtimeData("tripupdates", task) : null,
|
|
917
|
+
task.realtimeVehiclePositions?.url ? fetchGtfsRealtimeData("vehiclepositions", task) : null
|
|
918
|
+
]
|
|
919
|
+
);
|
|
856
920
|
const db = openDb({ sqlitePath: task.sqlitePath });
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
921
|
+
const recordCounts = {
|
|
922
|
+
alerts: 0,
|
|
923
|
+
tripupdates: 0,
|
|
924
|
+
vehiclepositions: 0
|
|
925
|
+
};
|
|
926
|
+
const processingPromises = [];
|
|
927
|
+
if (alertsData?.entity?.length) {
|
|
928
|
+
processingPromises.push(
|
|
929
|
+
processBatch(
|
|
930
|
+
alertsData.entity,
|
|
931
|
+
BATCH_SIZE,
|
|
932
|
+
createServiceAlertsProcessor(db, task)
|
|
933
|
+
).then((result) => {
|
|
934
|
+
recordCounts.alerts = result.recordCount;
|
|
935
|
+
})
|
|
936
|
+
);
|
|
870
937
|
}
|
|
871
|
-
if (
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
if (task.ignoreErrors) {
|
|
882
|
-
task.logError(error.message);
|
|
883
|
-
} else {
|
|
884
|
-
throw error;
|
|
885
|
-
}
|
|
886
|
-
}
|
|
938
|
+
if (tripUpdatesData?.entity?.length) {
|
|
939
|
+
processingPromises.push(
|
|
940
|
+
processBatch(
|
|
941
|
+
tripUpdatesData.entity,
|
|
942
|
+
BATCH_SIZE,
|
|
943
|
+
createTripUpdatesProcessor(db, task)
|
|
944
|
+
).then((result) => {
|
|
945
|
+
recordCounts.tripupdates = result.recordCount;
|
|
946
|
+
})
|
|
947
|
+
);
|
|
887
948
|
}
|
|
888
|
-
if (
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
if (task.ignoreErrors) {
|
|
899
|
-
task.logError(error.message);
|
|
900
|
-
} else {
|
|
901
|
-
throw error;
|
|
902
|
-
}
|
|
903
|
-
}
|
|
949
|
+
if (vehiclePositionsData?.entity?.length) {
|
|
950
|
+
processingPromises.push(
|
|
951
|
+
processBatch(
|
|
952
|
+
vehiclePositionsData.entity,
|
|
953
|
+
BATCH_SIZE,
|
|
954
|
+
createVehiclePositionsProcessor(db, task)
|
|
955
|
+
).then((result) => {
|
|
956
|
+
recordCounts.vehiclepositions = result.recordCount;
|
|
957
|
+
})
|
|
958
|
+
);
|
|
904
959
|
}
|
|
905
|
-
|
|
960
|
+
await Promise.all(processingPromises);
|
|
961
|
+
task.log(
|
|
962
|
+
`GTFS-Realtime import complete: ${recordCounts.alerts} alerts, ${recordCounts.tripupdates} trip updates, ${recordCounts.vehiclepositions} vehicle positions`
|
|
963
|
+
);
|
|
906
964
|
}
|
|
907
965
|
async function updateGtfsRealtime(initialConfig) {
|
|
908
966
|
const config = setDefaultConfig(initialConfig);
|
|
@@ -912,9 +970,9 @@ async function updateGtfsRealtime(initialConfig) {
|
|
|
912
970
|
const agencyCount = config.agencies.length;
|
|
913
971
|
log(config)(
|
|
914
972
|
`Starting GTFS-Realtime refresh for ${pluralize(
|
|
973
|
+
"agency",
|
|
915
974
|
"agencies",
|
|
916
|
-
agencyCount
|
|
917
|
-
true
|
|
975
|
+
agencyCount
|
|
918
976
|
)} using SQLite database at ${config.sqlitePath}`
|
|
919
977
|
);
|
|
920
978
|
removeExpiredRealtimeData(config);
|
|
@@ -936,8 +994,9 @@ async function updateGtfsRealtime(initialConfig) {
|
|
|
936
994
|
};
|
|
937
995
|
await updateGtfsRealtimeData(task);
|
|
938
996
|
} catch (error) {
|
|
997
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
939
998
|
if (config.ignoreErrors) {
|
|
940
|
-
logError(config)(
|
|
999
|
+
logError(config)(errorMessage);
|
|
941
1000
|
} else {
|
|
942
1001
|
throw error;
|
|
943
1002
|
}
|
|
@@ -945,14 +1004,14 @@ async function updateGtfsRealtime(initialConfig) {
|
|
|
945
1004
|
});
|
|
946
1005
|
log(config)(
|
|
947
1006
|
`Completed GTFS-Realtime refresh for ${pluralize(
|
|
1007
|
+
"agency",
|
|
948
1008
|
"agencies",
|
|
949
|
-
agencyCount
|
|
950
|
-
true
|
|
1009
|
+
agencyCount
|
|
951
1010
|
)}
|
|
952
1011
|
`
|
|
953
1012
|
);
|
|
954
1013
|
} catch (error) {
|
|
955
|
-
if (error
|
|
1014
|
+
if (error.code === "SQLITE_CANTOPEN") {
|
|
956
1015
|
logError(config)(
|
|
957
1016
|
`Unable to open sqlite database "${config.sqlitePath}" defined as \`sqlitePath\` config.json. Ensure the parent directory exists or remove \`sqlitePath\` from config.json.`
|
|
958
1017
|
);
|
|
@@ -965,11 +1024,9 @@ async function updateGtfsRealtime(initialConfig) {
|
|
|
965
1024
|
import path3 from "path";
|
|
966
1025
|
import { writeFile as writeFile2 } from "fs/promises";
|
|
967
1026
|
import { without, compact as compact2 } from "lodash-es";
|
|
968
|
-
import pluralize3 from "pluralize";
|
|
969
1027
|
import { stringify } from "csv-stringify";
|
|
970
1028
|
import sqlString2 from "sqlstring-sqlite";
|
|
971
1029
|
import mapSeries3 from "promise-map-series";
|
|
972
|
-
import untildify4 from "untildify";
|
|
973
1030
|
|
|
974
1031
|
// src/lib/advancedQuery.ts
|
|
975
1032
|
import sqlString3 from "sqlstring-sqlite";
|