gtfs 4.19.1 → 4.19.3

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 CHANGED
@@ -181,6 +181,8 @@ For GTFS files that contain more than one agency, you only need to list each GTF
181
181
  | `headers` | object | An object of HTTP headers in key:value format to use when fetching GTFS from the `url` specified. Optional. |
182
182
  | `prefix` | string | A prefix to be added to every ID field maintain uniqueness when importing multiple GTFS from multiple agencies. Optional. |
183
183
  | `exclude` | array | An array of GTFS file names (without `.txt`) to exclude when importing. Optional. |
184
+ | `fillEmptyAgencyId` | boolean | When `true`, fills empty `agency_id` on routes, fares, and other files for single-agency feeds. Useful for shared databases. Defaults to `false`. Optional. |
185
+ | `agencyId` | string | Explicit `agency_id` to use when `fillEmptyAgencyId` is `true` and `agency.txt` does not define one. Also backfills the `agency_id` on the agency row itself. If `agency.txt` already defines an `agency_id`, that value takes precedence. Optional. |
184
186
  | `realtimeAlerts` | object | An object containing a `url` field for GTFS-Realtime alerts and a `headers` field in key:value format to use when fetching GTFS-Realtime data. Optional. |
185
187
  | `realtimeTripUpdates` | object | An object containing a `url` field for GTFS-Realtime trip updates and a `headers` field in key:value format to use when fetching GTFS-Realtime data. Optional. |
186
188
  | `realtimeVehiclePositions` | object | An object containing a `url` field for GTFS-Realtime vehicle positions and a `headers` field in key:value format to use when fetching GTFS-Realtime data. Optional. |
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { Tt as formatError, st as exportGtfs, xt as getConfig } from "../src-DuXcyA_N.js";
2
+ import { Tt as formatError, st as exportGtfs, xt as getConfig } from "../src-gj91FR8c.js";
3
3
  import yargs from "yargs";
4
4
  import { hideBin } from "yargs/helpers";
5
5
  import PrettyError from "pretty-error";
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { Tt as formatError, ct as importGtfs, ft as openDb, ut as closeDb, xt as getConfig } from "../src-DuXcyA_N.js";
2
+ import { Tt as formatError, ct as importGtfs, ft as openDb, ut as closeDb, xt as getConfig } from "../src-gj91FR8c.js";
3
3
  import yargs from "yargs";
4
4
  import { hideBin } from "yargs/helpers";
5
5
  import PrettyError from "pretty-error";
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { Tt as formatError, lt as updateGtfsRealtime, xt as getConfig } from "../src-DuXcyA_N.js";
2
+ import { Tt as formatError, lt as updateGtfsRealtime, xt as getConfig } from "../src-gj91FR8c.js";
3
3
  import yargs from "yargs";
4
4
  import { hideBin } from "yargs/helpers";
5
5
  import PrettyError from "pretty-error";
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Options } from "csv-parse";
2
- import Database, { Database as Database$1 } from "better-sqlite3";
2
+ import Database$1, { Database } from "better-sqlite3";
3
3
 
4
4
  //#region src/lib/errors.d.ts
5
5
  declare enum GtfsErrorCategory {
@@ -121,6 +121,20 @@ interface BaseConfigAgency {
121
121
  * A prefix to be added to every ID field maintain uniqueness when importing multiple GTFS from multiple agencies
122
122
  */
123
123
  prefix?: string;
124
+ /**
125
+ * When set to true and the feed contains exactly one agency, populates any empty `agency_id` fields
126
+ * on routes, fare_attributes, and other relevant files. Useful when merging single-agency feeds into
127
+ * a shared database.
128
+ *
129
+ * @defaultValue false
130
+ */
131
+ fillEmptyAgencyId?: boolean;
132
+ /**
133
+ * Explicit `agency_id` to use when `fillEmptyAgencyId` is true and `agency.txt` does not define
134
+ * one. Also backfills the `agency_id` on the agency row itself. If `agency.txt` already defines
135
+ * an `agency_id` and it differs from this value, the value from `agency.txt` takes precedence.
136
+ */
137
+ agencyId?: string;
124
138
  }
125
139
  type ConfigAgency = BaseConfigAgency & ({
126
140
  /**
@@ -137,7 +151,7 @@ interface Config {
137
151
  /**
138
152
  * An existing database instance to use instead of relying on node-gtfs to connect.
139
153
  */
140
- db?: Database$1;
154
+ db?: Database;
141
155
  /**
142
156
  * A path to an SQLite database. Defaults to using an in-memory database.
143
157
  */
@@ -233,7 +247,7 @@ type SqlWhere = Record<string, null | SqlValue | SqlValue[]>;
233
247
  type QueryResult<Base extends object, Select extends keyof Base> = [Select] extends [never] ? Base : Pick<Base, Select>;
234
248
  type SqlOrderBy = Array<[string, 'ASC' | 'DESC']>;
235
249
  interface QueryOptions {
236
- db?: Database$1;
250
+ db?: Database;
237
251
  bounding_box_side_m?: number;
238
252
  }
239
253
  interface Agency {
@@ -830,15 +844,15 @@ declare const exportGtfs: (initialConfig: Config) => Promise<void>;
830
844
  //#endregion
831
845
  //#region src/lib/db.d.ts
832
846
  declare function openDb(config?: {
833
- db?: Database.Database;
847
+ db?: Database$1.Database;
834
848
  sqlitePath?: string;
835
- } | null): Database.Database;
836
- declare function closeDb(db?: Database.Database | null): void;
837
- declare function deleteDb(db?: Database.Database | null): void;
849
+ } | null): Database$1.Database;
850
+ declare function closeDb(db?: Database$1.Database | null): void;
851
+ declare function deleteDb(db?: Database$1.Database | null): void;
838
852
  //#endregion
839
853
  //#region src/lib/advancedQuery.d.ts
840
854
  declare function advancedQuery(table: string, advancedQueryOptions: {
841
- db?: Database.Database;
855
+ db?: Database$1.Database;
842
856
  query?: SqlWhere;
843
857
  fields?: string[];
844
858
  orderBy?: SqlOrderBy;
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { $ as getCalendars, A as getStopsAsGeoJSON, B as getNetworks, C as getCalendarAttributes, Ct as untildify, D as getTimeframes, E as getTransfers, F as getRouteAttributes, G as getFrequencies, H as getLocationGroupStops, I as getRoutes, J as getFareRules, K as getFeedInfo, L as getRouteNetworks, M as getStopAreas, N as getShapes, O as getStoptimes, P as getShapesAsGeoJSON, Q as getFareAttributes, R as getRiderCategories, S as getDirections, St as prepDirectory, T as getTranslations, U as getLocationGroups, V as getLocations, W as getLevels, X as getFareMedia, Y as getFareProducts, Z as getFareLegRules, _ as getTimetableNotesReferences, _t as formatGtfsError, a as getDeadheads, at as getAgencies, b as getTimetableStopOrders, bt as generateFolderName, c as getVehiclePositions, ct as importGtfs, d as getTripCapacities, dt as deleteDb, et as getServiceIdsByDate, f as getRidership, ft as openDb, g as getTripsDatedVehicleJourneys, gt as GtfsWarningCode, h as getBoardAlights, ht as GtfsErrorCode, i as getDeadheadTimes, it as getAreas, j as getStopAttributes, k as getStops, l as getTripUpdates, lt as updateGtfsRealtime, m as getRideFeedInfo, mt as GtfsErrorCategory, n as getRunEvents, nt as getBookingRules, o as getServiceAlertInformedEntities, ot as advancedQuery, p as getRiderTrips, pt as GtfsError, q as getFareTransferRules, r as getOpsLocations, rt as getAttributions, s as getServiceAlerts, st as exportGtfs, t as getRunsPieces, tt as getCalendarDates, u as getStopTimeUpdates, ut as closeDb, v as getTimetableNotes, vt as isGtfsError, w as getTrips, wt as unzip, x as getTimetables, y as getTimetablePages, yt as isGtfsValidationError, z as getPathways } from "./src-DuXcyA_N.js";
1
+ import { $ as getCalendars, A as getStopsAsGeoJSON, B as getNetworks, C as getCalendarAttributes, Ct as untildify, D as getTimeframes, E as getTransfers, F as getRouteAttributes, G as getFrequencies, H as getLocationGroupStops, I as getRoutes, J as getFareRules, K as getFeedInfo, L as getRouteNetworks, M as getStopAreas, N as getShapes, O as getStoptimes, P as getShapesAsGeoJSON, Q as getFareAttributes, R as getRiderCategories, S as getDirections, St as prepDirectory, T as getTranslations, U as getLocationGroups, V as getLocations, W as getLevels, X as getFareMedia, Y as getFareProducts, Z as getFareLegRules, _ as getTimetableNotesReferences, _t as formatGtfsError, a as getDeadheads, at as getAgencies, b as getTimetableStopOrders, bt as generateFolderName, c as getVehiclePositions, ct as importGtfs, d as getTripCapacities, dt as deleteDb, et as getServiceIdsByDate, f as getRidership, ft as openDb, g as getTripsDatedVehicleJourneys, gt as GtfsWarningCode, h as getBoardAlights, ht as GtfsErrorCode, i as getDeadheadTimes, it as getAreas, j as getStopAttributes, k as getStops, l as getTripUpdates, lt as updateGtfsRealtime, m as getRideFeedInfo, mt as GtfsErrorCategory, n as getRunEvents, nt as getBookingRules, o as getServiceAlertInformedEntities, ot as advancedQuery, p as getRiderTrips, pt as GtfsError, q as getFareTransferRules, r as getOpsLocations, rt as getAttributions, s as getServiceAlerts, st as exportGtfs, t as getRunsPieces, tt as getCalendarDates, u as getStopTimeUpdates, ut as closeDb, v as getTimetableNotes, vt as isGtfsError, w as getTrips, wt as unzip, x as getTimetables, y as getTimetablePages, yt as isGtfsValidationError, z as getPathways } from "./src-gj91FR8c.js";
2
2
 
3
3
  export { GtfsError, GtfsErrorCategory, GtfsErrorCode, GtfsWarningCode, advancedQuery, closeDb, deleteDb, exportGtfs, formatGtfsError, generateFolderName, getAgencies, getAreas, getAttributions, getBoardAlights, getBookingRules, getCalendarAttributes, getCalendarDates, getCalendars, getDeadheadTimes, getDeadheads, getDirections, getFareAttributes, getFareLegRules, getFareMedia, getFareProducts, getFareRules, getFareTransferRules, getFeedInfo, getFrequencies, getLevels, getLocationGroupStops, getLocationGroups, getLocations, getNetworks, getOpsLocations, getPathways, getRideFeedInfo, getRiderCategories, getRiderTrips, getRidership, getRouteAttributes, getRouteNetworks, getRoutes, getRunEvents, getRunsPieces, getServiceAlertInformedEntities, getServiceAlerts, getServiceIdsByDate, getShapes, getShapesAsGeoJSON, getStopAreas, getStopAttributes, getStopTimeUpdates, getStops, getStopsAsGeoJSON, getStoptimes, getTimeframes, getTimetableNotes, getTimetableNotesReferences, getTimetablePages, getTimetableStopOrders, getTimetables, getTransfers, getTranslations, getTripCapacities, getTripUpdates, getTrips, getTripsDatedVehicleJourneys, getVehiclePositions, importGtfs, isGtfsError, isGtfsValidationError, openDb, prepDirectory, untildify, unzip, updateGtfsRealtime };
@@ -7,9 +7,9 @@ import stripBomStream from "strip-bom-stream";
7
7
  import { temporaryDirectory } from "tempy";
8
8
  import mapSeries from "promise-map-series";
9
9
  import fs from "fs";
10
- import Database from "better-sqlite3";
10
+ import Database$1 from "better-sqlite3";
11
11
  import { homedir } from "node:os";
12
- import { cloneDeep, compact, filter, get, groupBy, last, noop, omit, omitBy, orderBy, pick, snakeCase, sortBy, without } from "lodash-es";
12
+ import { compact, filter, get, groupBy, last, noop, omit, omitBy, orderBy, pick, snakeCase, sortBy, without } from "lodash-es";
13
13
  import sanitize from "sanitize-filename";
14
14
  import StreamZip from "node-stream-zip";
15
15
  import { clearLine, cursorTo } from "node:readline";
@@ -281,7 +281,7 @@ function formatGtfsError(error, options = { verbosity: "developer" }) {
281
281
  //#region src/lib/db.ts
282
282
  const dbs = {};
283
283
  function setupDb(sqlitePath) {
284
- const db = new Database(untildify(sqlitePath));
284
+ const db = new Database$1(untildify(sqlitePath));
285
285
  db.pragma("journal_mode = OFF");
286
286
  db.pragma("synchronous = OFF");
287
287
  db.pragma("temp_store = MEMORY");
@@ -296,7 +296,10 @@ function openDb(config = null) {
296
296
  return setupDb(sqlitePath);
297
297
  }
298
298
  if (Object.keys(dbs).length === 0) return setupDb(":memory:");
299
- if (Object.keys(dbs).length === 1) return dbs[Object.keys(dbs)[0]];
299
+ if (Object.keys(dbs).length === 1) {
300
+ const filename = Object.keys(dbs)[0];
301
+ return dbs[filename];
302
+ }
300
303
  if (Object.keys(dbs).length > 1) throw new GtfsError("Multiple databases open, please specify which one to use.", {
301
304
  code: "GTFS_DB_OPERATION_FAILED",
302
305
  category: "database",
@@ -390,17 +393,16 @@ function consolidateShapes(shapeGroups) {
390
393
  for (const segments of segmentsArray) {
391
394
  consolidatedLineStrings.push([]);
392
395
  for (const segment of segments) {
393
- const key1 = segment.flat().join(",");
394
- const key2 = segment.reverse().flat().join(",");
396
+ const [[x1, y1], [x2, y2]] = segment;
397
+ const key = x1 < x2 || x1 === x2 && y1 <= y2 ? `${x1},${y1},${x2},${y2}` : `${x2},${y2},${x1},${y1}`;
395
398
  const currentLine = last(consolidatedLineStrings);
396
- if (!currentLine || keys.has(key1) || keys.has(key2)) {
399
+ if (!currentLine || keys.has(key)) {
397
400
  consolidatedLineStrings.push([]);
398
401
  continue;
399
402
  }
400
403
  if (currentLine.length === 0) currentLine.push(segment[0]);
401
404
  currentLine.push(segment[1]);
402
- keys.add(key1);
403
- keys.add(key2);
405
+ keys.add(key);
404
406
  }
405
407
  }
406
408
  return filter(consolidatedLineStrings, isValidLineString);
@@ -422,7 +424,7 @@ function formatHexColor(color) {
422
424
  * @returns {Record<string, unknown>} Formatted properties object
423
425
  */
424
426
  function formatProperties(properties) {
425
- const formattedProperties = cloneDeep(omitBy(properties, (value) => value == null));
427
+ const formattedProperties = omitBy(properties, (value) => value == null);
426
428
  const formattedRouteColor = formatHexColor(properties.route_color);
427
429
  const formattedRouteTextColor = formatHexColor(properties.route_text_color);
428
430
  if (formattedRouteColor) formattedProperties.route_color = formattedRouteColor;
@@ -1134,6 +1136,42 @@ const extractGtfsFiles = async (task) => {
1134
1136
  });
1135
1137
  }
1136
1138
  };
1139
+ /**
1140
+ * Reads agency.txt from disk after extraction to find the single agency_id for a feed.
1141
+ * Returns the raw agency_id string if there is exactly one agency with a non-empty
1142
+ * agency_id, or undefined otherwise.
1143
+ */
1144
+ const getSingleAgencyId = (downloadDir, csvOptions, logWarning) => new Promise((resolve) => {
1145
+ const filepath = path.join(downloadDir, "agency.txt");
1146
+ if (!existsSync(filepath)) {
1147
+ resolve(void 0);
1148
+ return;
1149
+ }
1150
+ const rows = [];
1151
+ const parser = parse({
1152
+ columns: true,
1153
+ relax_quotes: true,
1154
+ trim: true,
1155
+ skip_empty_lines: true,
1156
+ ...csvOptions
1157
+ });
1158
+ parser.on("readable", () => {
1159
+ let record;
1160
+ while (record = parser.read()) rows.push(record);
1161
+ });
1162
+ parser.on("end", () => {
1163
+ if (rows.length !== 1) {
1164
+ resolve(void 0);
1165
+ return;
1166
+ }
1167
+ resolve(rows[0].agency_id?.trim() || void 0);
1168
+ });
1169
+ parser.on("error", (err) => {
1170
+ logWarning(`Unable to parse agency.txt for \`fillEmptyAgencyId\`: ${err.message}`);
1171
+ resolve(void 0);
1172
+ });
1173
+ createReadStream(filepath).pipe(stripBomStream()).pipe(parser);
1174
+ });
1137
1175
  const createGtfsTables = (db) => {
1138
1176
  for (const model of Object.values(models_exports)) {
1139
1177
  if (!model.schema) continue;
@@ -1179,7 +1217,20 @@ const createGtfsIndexes = (db) => {
1179
1217
  }
1180
1218
  }
1181
1219
  };
1182
- const formatGtfsLine = (line, model, totalLineCount) => {
1220
+ const AGENCY_ID_BACKFILL_MODELS = /* @__PURE__ */ new Set([
1221
+ "agency",
1222
+ "routes",
1223
+ "fare_attributes",
1224
+ "trip_capacity",
1225
+ "rider_trip",
1226
+ "ridership"
1227
+ ]);
1228
+ function shouldBackfillAgencyId(model, formattedLine) {
1229
+ if (AGENCY_ID_BACKFILL_MODELS.has(model.filenameBase)) return true;
1230
+ if (model.filenameBase === "attributions") return formattedLine.route_id == null && formattedLine.trip_id == null;
1231
+ return false;
1232
+ }
1233
+ const formatGtfsLine = (line, model, totalLineCount, fillEmptyAgencyId, agencyId) => {
1183
1234
  const lineNumber = totalLineCount + 1;
1184
1235
  const formattedLine = {};
1185
1236
  const filenameBase = model.filenameBase;
@@ -1215,6 +1266,7 @@ const formatGtfsLine = (line, model, totalLineCount) => {
1215
1266
  if (type === "json") value = JSON.stringify(value);
1216
1267
  formattedLine[name] = value;
1217
1268
  }
1269
+ if (fillEmptyAgencyId && agencyId !== void 0 && formattedLine.agency_id == null && shouldBackfillAgencyId(model, formattedLine)) formattedLine.agency_id = agencyId;
1218
1270
  return formattedLine;
1219
1271
  };
1220
1272
  const BATCH_SIZE = 1e5;
@@ -1291,7 +1343,7 @@ const importGtfsFiles = async (db, task) => {
1291
1343
  let record;
1292
1344
  while (record = parser.read()) {
1293
1345
  totalLineCount += 1;
1294
- lines.push(formatGtfsLine(record, model, totalLineCount));
1346
+ lines.push(formatGtfsLine(record, model, totalLineCount, task.fillEmptyAgencyId, task.agencyId));
1295
1347
  if (lines.length >= BATCH_SIZE) {
1296
1348
  insertLines(lines);
1297
1349
  lines = [];
@@ -1388,7 +1440,7 @@ const importGtfsFiles = async (db, task) => {
1388
1440
  return;
1389
1441
  }
1390
1442
  totalLineCount += 1;
1391
- const line = formatGtfsLine({ geojson: data }, model, totalLineCount);
1443
+ const line = formatGtfsLine({ geojson: data }, model, totalLineCount, task.fillEmptyAgencyId, task.agencyId);
1392
1444
  try {
1393
1445
  insertLines([line]);
1394
1446
  task.log(`Importing - ${filename} - ${totalLineCount} lines imported\r`, true);
@@ -1463,6 +1515,8 @@ async function importGtfs(initialConfig) {
1463
1515
  ignoreErrors: config.ignoreErrors,
1464
1516
  sqlitePath: config.sqlitePath,
1465
1517
  prefix: agency.prefix,
1518
+ fillEmptyAgencyId: agency.fillEmptyAgencyId ?? false,
1519
+ agencyId: agency.agencyId,
1466
1520
  currentTimestamp: Math.floor(Date.now() / 1e3),
1467
1521
  log: log(config),
1468
1522
  logWarning: logWarning(config),
@@ -1474,6 +1528,13 @@ async function importGtfs(initialConfig) {
1474
1528
  await downloadGtfsFiles(task);
1475
1529
  } else Object.assign(task, { path: agency.path });
1476
1530
  await extractGtfsFiles(task);
1531
+ if (task.fillEmptyAgencyId) {
1532
+ const agencyIdFromGtfs = await getSingleAgencyId(task.downloadDir, task.csvOptions, task.logWarning);
1533
+ if (agencyIdFromGtfs !== void 0) {
1534
+ if (task.agencyId !== void 0 && task.agencyId !== agencyIdFromGtfs) task.logWarning(`\`agencyId\` "${task.agencyId}" does not match the \`agency_id\` "${agencyIdFromGtfs}" in agency.txt. Using the value from agency.txt.`);
1535
+ task.agencyId = agencyIdFromGtfs;
1536
+ } else if (task.agencyId === void 0) task.logWarning("`fillEmptyAgencyId` is set but a single `agency_id` could not be determined for this feed and no `agencyId` was provided in config. `agency_id` will not be backfilled.");
1537
+ }
1477
1538
  await importGtfsFiles(db, task);
1478
1539
  await updateGtfsRealtimeData(task);
1479
1540
  await rm(tempPath, { recursive: true });
@@ -2308,7 +2369,7 @@ function getVehiclePositions(query = {}, fields = [], orderBy = [], options = {}
2308
2369
 
2309
2370
  //#endregion
2310
2371
  //#region src/lib/gtfs-realtime/service-alerts.ts
2311
- const ENTITY_COLUMNS = new Set([
2372
+ const ENTITY_COLUMNS = /* @__PURE__ */ new Set([
2312
2373
  "alert_id",
2313
2374
  "stop_id",
2314
2375
  "route_id",
@@ -2414,4 +2475,4 @@ function getRunsPieces(query = {}, fields = [], orderBy = [], options = {}) {
2414
2475
 
2415
2476
  //#endregion
2416
2477
  export { getCalendars as $, getStopsAsGeoJSON as A, getNetworks as B, getCalendarAttributes as C, untildify as Ct, getTimeframes as D, getTransfers as E, getRouteAttributes as F, getFrequencies as G, getLocationGroupStops as H, getRoutes as I, getFareRules as J, getFeedInfo as K, getRouteNetworks as L, getStopAreas as M, getShapes as N, getStoptimes as O, getShapesAsGeoJSON as P, getFareAttributes as Q, getRiderCategories as R, getDirections as S, prepDirectory as St, getTranslations as T, formatError as Tt, getLocationGroups as U, getLocations as V, getLevels as W, getFareMedia as X, getFareProducts as Y, getFareLegRules as Z, getTimetableNotesReferences as _, formatGtfsError as _t, getDeadheads as a, getAgencies as at, getTimetableStopOrders as b, generateFolderName as bt, getVehiclePositions as c, importGtfs as ct, getTripCapacities as d, deleteDb as dt, getServiceIdsByDate as et, getRidership as f, openDb as ft, getTripsDatedVehicleJourneys as g, GtfsWarningCode as gt, getBoardAlights as h, GtfsErrorCode as ht, getDeadheadTimes as i, getAreas as it, getStopAttributes as j, getStops as k, getTripUpdates as l, updateGtfsRealtime as lt, getRideFeedInfo as m, GtfsErrorCategory as mt, getRunEvents as n, getBookingRules as nt, getServiceAlertInformedEntities as o, advancedQuery as ot, getRiderTrips as p, GtfsError as pt, getFareTransferRules as q, getOpsLocations as r, getAttributions as rt, getServiceAlerts as s, exportGtfs as st, getRunsPieces as t, getCalendarDates as tt, getStopTimeUpdates as u, closeDb as ut, getTimetableNotes as v, isGtfsError as vt, getTrips as w, unzip as wt, getTimetables as x, getConfig as xt, getTimetablePages as y, isGtfsValidationError as yt, getPathways as z };
2417
- //# sourceMappingURL=src-DuXcyA_N.js.map
2478
+ //# sourceMappingURL=src-gj91FR8c.js.map