gtfs 4.15.5 → 4.15.7

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.
@@ -18,68 +18,6 @@ import { omit, snakeCase } from "lodash-es";
18
18
  import sanitize from "sanitize-filename";
19
19
  import untildify from "untildify";
20
20
  import StreamZip from "node-stream-zip";
21
- async function getConfig(argv2) {
22
- let config;
23
- let data;
24
- if (argv2.configPath) {
25
- try {
26
- data = await readFile(path.resolve(untildify(argv2.configPath)), "utf8");
27
- } catch (error) {
28
- throw new Error(
29
- `Cannot find configuration file at \`${argv2.configPath}\`. Use config-sample.json as a starting point.`
30
- );
31
- }
32
- try {
33
- config = Object.assign(JSON.parse(data), argv2);
34
- } catch (error) {
35
- throw new Error(
36
- `Cannot parse configuration file at \`${argv2.configPath}\`. Check to ensure that it is valid JSON.`
37
- );
38
- }
39
- } else if (argv2.gtfsPath || argv2.gtfsUrl || argv2.sqlitePath) {
40
- const agencies = [];
41
- if (argv2.gtfsPath) {
42
- agencies.push({
43
- path: argv2.gtfsPath
44
- });
45
- }
46
- if (argv2.gtfsUrl) {
47
- agencies.push({
48
- url: argv2.gtfsUrl
49
- });
50
- }
51
- config = {
52
- agencies,
53
- ...omit(argv2, ["path", "url"])
54
- };
55
- } else if (existsSync(path.resolve("./config.json"))) {
56
- try {
57
- data = await readFile(path.resolve("./config.json"), "utf8");
58
- } catch (error) {
59
- throw new Error(
60
- `Cannot open configuration file at \`${path.resolve("./config.json")}\`. Check to ensure that it exists. Use config-sample.json as a starting point.`
61
- );
62
- }
63
- try {
64
- config = Object.assign(JSON.parse(data), argv2);
65
- console.log("Using configuration from ./config.json");
66
- } catch (error) {
67
- throw new Error(
68
- `Cannot parse configuration file at \`${path.resolve("./config.json")}\`. Check to ensure that it is valid JSON.`
69
- );
70
- }
71
- } else {
72
- throw new Error(
73
- "Cannot find configuration file. Use config-sample.json as a starting point, pass --configPath option."
74
- );
75
- }
76
- return config;
77
- }
78
- async function unzip(zipfilePath, exportPath) {
79
- const zip = new StreamZip.async({ file: zipfilePath });
80
- await zip.extract(null, exportPath);
81
- await zip.close();
82
- }
83
21
 
84
22
  // src/lib/log-utils.ts
85
23
  import { clearLine, cursorTo } from "node:readline";
@@ -92,8 +30,8 @@ function log(config) {
92
30
  if (config.logFunction) {
93
31
  return config.logFunction;
94
32
  }
95
- return (text, overwrite) => {
96
- if (overwrite === true && process.stdout.isTTY) {
33
+ return (text, overwrite = false) => {
34
+ if (overwrite && process.stdout.isTTY) {
97
35
  clearLine(process.stdout, 0);
98
36
  cursorTo(process.stdout, 0);
99
37
  } else {
@@ -123,16 +61,61 @@ ${formatError(text)}
123
61
  };
124
62
  }
125
63
  function formatWarning(text) {
126
- const warningMessage = `${colors.underline("Warning")}: ${text}`;
127
- return colors.yellow(warningMessage);
64
+ return colors.yellow(`${colors.underline("Warning")}: ${text}`);
128
65
  }
129
66
  function formatError(error) {
130
67
  const messageText = error instanceof Error ? error.message : error;
131
- const errorMessage = `${colors.underline("Error")}: ${messageText.replace(
132
- "Error: ",
133
- ""
134
- )}`;
135
- return colors.red(errorMessage);
68
+ const cleanMessage = messageText.replace(/^Error:\s*/i, "");
69
+ return colors.red(`${colors.underline("Error")}: ${cleanMessage}`);
70
+ }
71
+
72
+ // src/lib/file-utils.ts
73
+ async function getConfig(argv2) {
74
+ let config;
75
+ let data;
76
+ try {
77
+ if (argv2.configPath) {
78
+ const configPath = path.resolve(untildify(argv2.configPath));
79
+ data = await readFile(configPath, "utf8");
80
+ config = Object.assign(JSON.parse(data), argv2);
81
+ } else if (argv2.gtfsPath || argv2.gtfsUrl || argv2.sqlitePath) {
82
+ const agencies = [
83
+ ...argv2.gtfsPath ? [{ path: argv2.gtfsPath }] : [],
84
+ ...argv2.gtfsUrl ? [{ url: argv2.gtfsUrl }] : []
85
+ ];
86
+ config = {
87
+ agencies,
88
+ ...omit(argv2, ["path", "url"])
89
+ };
90
+ } else if (existsSync(path.resolve("./config.json"))) {
91
+ data = await readFile(path.resolve("./config.json"), "utf8");
92
+ config = Object.assign(JSON.parse(data), argv2);
93
+ log(config)("Using configuration from ./config.json");
94
+ } else {
95
+ throw new Error(
96
+ "Cannot find configuration file. Use config-sample.json as a starting point, pass --configPath option."
97
+ );
98
+ }
99
+ return config;
100
+ } catch (error) {
101
+ if (error instanceof SyntaxError) {
102
+ throw new Error(
103
+ `Cannot parse configuration file. Check to ensure that it is valid JSON. Error: ${error.message}`
104
+ );
105
+ }
106
+ throw error;
107
+ }
108
+ }
109
+ async function unzip(zipfilePath, exportPath) {
110
+ try {
111
+ const zip = new StreamZip.async({ file: zipfilePath });
112
+ await zip.extract(null, exportPath);
113
+ await zip.close();
114
+ } catch (error) {
115
+ throw new Error(
116
+ `Failed to extract zip file: ${error instanceof Error ? error.message : "Unknown error"}`
117
+ );
118
+ }
136
119
  }
137
120
 
138
121
  // src/lib/import-gtfs.ts
@@ -283,7 +266,8 @@ var attributions = {
283
266
  {
284
267
  name: "attribution_id",
285
268
  type: "text",
286
- prefix: true
269
+ prefix: true,
270
+ primary: true
287
271
  },
288
272
  {
289
273
  name: "agency_id",
@@ -716,26 +700,31 @@ var fareRules = {
716
700
  name: "fare_id",
717
701
  type: "text",
718
702
  required: true,
703
+ primary: true,
719
704
  prefix: true
720
705
  },
721
706
  {
722
707
  name: "route_id",
723
708
  type: "text",
709
+ primary: true,
724
710
  prefix: true
725
711
  },
726
712
  {
727
713
  name: "origin_id",
728
714
  type: "text",
715
+ primary: true,
729
716
  prefix: true
730
717
  },
731
718
  {
732
719
  name: "destination_id",
733
720
  type: "text",
721
+ primary: true,
734
722
  prefix: true
735
723
  },
736
724
  {
737
725
  name: "contains_id",
738
726
  type: "text",
727
+ primary: true,
739
728
  prefix: true
740
729
  }
741
730
  ]
@@ -942,14 +931,16 @@ var locationGroupStops = {
942
931
  type: "text",
943
932
  prefix: true,
944
933
  index: true,
945
- required: true
934
+ required: true,
935
+ primary: true
946
936
  },
947
937
  {
948
938
  name: "stop_id",
949
939
  type: "text",
950
940
  required: true,
951
941
  prefix: true,
952
- index: true
942
+ index: true,
943
+ primary: true
953
944
  }
954
945
  ]
955
946
  };
@@ -1210,12 +1201,14 @@ var stopAreas = {
1210
1201
  name: "area_id",
1211
1202
  type: "text",
1212
1203
  required: true,
1204
+ primary: true,
1213
1205
  prefix: true
1214
1206
  },
1215
1207
  {
1216
1208
  name: "stop_id",
1217
1209
  type: "text",
1218
1210
  required: true,
1211
+ primary: true,
1219
1212
  prefix: true
1220
1213
  }
1221
1214
  ]
@@ -1439,16 +1432,19 @@ var timeframes = {
1439
1432
  },
1440
1433
  {
1441
1434
  name: "start_time",
1442
- type: "text"
1435
+ type: "text",
1436
+ primary: true
1443
1437
  },
1444
1438
  {
1445
1439
  name: "end_time",
1446
- type: "text"
1440
+ type: "text",
1441
+ primary: true
1447
1442
  },
1448
1443
  {
1449
1444
  name: "service_id",
1450
1445
  type: "text",
1451
1446
  required: true,
1447
+ primary: true,
1452
1448
  index: true,
1453
1449
  prefix: true
1454
1450
  }
@@ -1635,21 +1631,19 @@ var timetables = {
1635
1631
  filenameExtension: "txt",
1636
1632
  nonstandard: true,
1637
1633
  schema: [
1638
- {
1639
- name: "id",
1640
- type: "integer",
1641
- primary: true,
1642
- prefix: true
1643
- },
1644
1634
  {
1645
1635
  name: "timetable_id",
1646
1636
  type: "text",
1647
- prefix: true
1637
+ prefix: true,
1638
+ required: true,
1639
+ primary: true
1648
1640
  },
1649
1641
  {
1650
1642
  name: "route_id",
1651
1643
  type: "text",
1652
- prefix: true
1644
+ prefix: true,
1645
+ required: true,
1646
+ primary: true
1653
1647
  },
1654
1648
  {
1655
1649
  name: "direction_id",
@@ -1783,6 +1777,7 @@ var timetablePages = {
1783
1777
  name: "timetable_page_id",
1784
1778
  type: "text",
1785
1779
  primary: true,
1780
+ required: true,
1786
1781
  prefix: true
1787
1782
  },
1788
1783
  {
@@ -1802,28 +1797,28 @@ var timetableStopOrder = {
1802
1797
  filenameExtension: "txt",
1803
1798
  nonstandard: true,
1804
1799
  schema: [
1805
- {
1806
- name: "id",
1807
- type: "integer",
1808
- primary: true,
1809
- prefix: true
1810
- },
1811
1800
  {
1812
1801
  name: "timetable_id",
1813
1802
  type: "text",
1814
1803
  index: true,
1815
- prefix: true
1804
+ prefix: true,
1805
+ required: true,
1806
+ primary: true
1816
1807
  },
1817
1808
  {
1818
1809
  name: "stop_id",
1819
1810
  type: "text",
1820
- prefix: true
1811
+ prefix: true,
1812
+ required: true,
1813
+ primary: true
1821
1814
  },
1822
1815
  {
1823
1816
  name: "stop_sequence",
1824
1817
  type: "integer",
1825
1818
  min: 0,
1826
- index: true
1819
+ index: true,
1820
+ required: true,
1821
+ primary: true
1827
1822
  }
1828
1823
  ]
1829
1824
  };
@@ -1838,7 +1833,8 @@ var timetableNotes = {
1838
1833
  name: "note_id",
1839
1834
  type: "text",
1840
1835
  primary: true,
1841
- prefix: true
1836
+ prefix: true,
1837
+ required: true
1842
1838
  },
1843
1839
  {
1844
1840
  name: "symbol",
@@ -1847,7 +1843,8 @@ var timetableNotes = {
1847
1843
  {
1848
1844
  name: "note",
1849
1845
  type: "text",
1850
- nocase: true
1846
+ nocase: true,
1847
+ required: true
1851
1848
  }
1852
1849
  ]
1853
1850
  };
@@ -1861,37 +1858,39 @@ var timetableNotesReferences = {
1861
1858
  {
1862
1859
  name: "note_id",
1863
1860
  type: "text",
1864
- prefix: true
1861
+ prefix: true,
1862
+ required: true,
1863
+ primary: true
1865
1864
  },
1866
1865
  {
1867
1866
  name: "timetable_id",
1868
1867
  type: "text",
1869
- index: true,
1870
- prefix: true
1868
+ prefix: true,
1869
+ primary: true
1871
1870
  },
1872
1871
  {
1873
1872
  name: "route_id",
1874
1873
  type: "text",
1875
- index: true,
1876
- prefix: true
1874
+ prefix: true,
1875
+ primary: true
1877
1876
  },
1878
1877
  {
1879
1878
  name: "trip_id",
1880
1879
  type: "text",
1881
- index: true,
1882
- prefix: true
1880
+ prefix: true,
1881
+ primary: true
1883
1882
  },
1884
1883
  {
1885
1884
  name: "stop_id",
1886
1885
  type: "text",
1887
- index: true,
1888
- prefix: true
1886
+ prefix: true,
1887
+ primary: true
1889
1888
  },
1890
1889
  {
1891
1890
  name: "stop_sequence",
1892
1891
  type: "integer",
1893
1892
  min: 0,
1894
- index: true
1893
+ primary: true
1895
1894
  },
1896
1895
  {
1897
1896
  name: "show_on_stoptime",
@@ -3291,10 +3290,10 @@ import { feature, featureCollection } from "@turf/helpers";
3291
3290
  function isValidJSON(string) {
3292
3291
  try {
3293
3292
  JSON.parse(string);
3293
+ return true;
3294
3294
  } catch (error) {
3295
3295
  return false;
3296
3296
  }
3297
- return true;
3298
3297
  }
3299
3298
 
3300
3299
  // src/lib/import-gtfs-realtime.ts
@@ -3324,7 +3323,8 @@ function setDefaultConfig(initialConfig) {
3324
3323
  sqlitePath: ":memory:",
3325
3324
  ignoreDuplicates: false,
3326
3325
  ignoreErrors: false,
3327
- gtfsRealtimeExpirationSeconds: 0
3326
+ gtfsRealtimeExpirationSeconds: 0,
3327
+ verbose: true
3328
3328
  };
3329
3329
  return {
3330
3330
  ...defaults,
@@ -3333,14 +3333,17 @@ function setDefaultConfig(initialConfig) {
3333
3333
  }
3334
3334
  function convertLongTimeToDate(longDate) {
3335
3335
  const { high, low, unsigned } = longDate;
3336
- return new Date(new Long(low, high, unsigned).toInt() * 1e3).toISOString();
3336
+ return new Date(
3337
+ Long.fromBits(low, high, unsigned).toNumber() * 1e3
3338
+ ).toISOString();
3337
3339
  }
3338
3340
  function calculateSecondsFromMidnight(time) {
3339
- const split = time.split(":").map((d) => Number.parseInt(d, 10));
3340
- if (split.length !== 3) {
3341
+ if (!time || typeof time !== "string") return null;
3342
+ const [hours, minutes, seconds] = time.split(":").map(Number);
3343
+ if ([hours, minutes, seconds].some(isNaN) || hours >= 24 || minutes >= 60 || seconds >= 60) {
3341
3344
  return null;
3342
3345
  }
3343
- return split[0] * 3600 + split[1] * 60 + split[2];
3346
+ return hours * 3600 + minutes * 60 + seconds;
3344
3347
  }
3345
3348
  function padLeadingZeros(time) {
3346
3349
  const split = time.split(":").map((d) => String(Number(d)).padStart(2, "0"));
@@ -3670,18 +3673,31 @@ var createGtfsTables = (db) => {
3670
3673
  return;
3671
3674
  }
3672
3675
  const columns = model.schema.map((column) => {
3673
- let check = "";
3676
+ const checks = [];
3674
3677
  if (column.min !== void 0 && column.max) {
3675
- check = `CHECK( ${column.name} >= ${column.min} AND ${column.name} <= ${column.max} )`;
3678
+ checks.push(
3679
+ `${column.name} >= ${column.min} AND ${column.name} <= ${column.max}`
3680
+ );
3676
3681
  } else if (column.min) {
3677
- check = `CHECK( ${column.name} >= ${column.min} )`;
3682
+ checks.push(`${column.name} >= ${column.min}`);
3678
3683
  } else if (column.max) {
3679
- check = `CHECK( ${column.name} <= ${column.max} )`;
3684
+ checks.push(`${column.name} <= ${column.max}`);
3685
+ }
3686
+ if (column.type === "integer") {
3687
+ checks.push(
3688
+ `(TYPEOF(${column.name}) = 'integer' OR ${column.name} IS NULL)`
3689
+ );
3690
+ }
3691
+ if (column.type === "real") {
3692
+ checks.push(
3693
+ `(TYPEOF(${column.name}) = 'real' OR ${column.name} IS NULL)`
3694
+ );
3680
3695
  }
3681
3696
  const required = column.required ? "NOT NULL" : "";
3682
3697
  const columnDefault = column.default ? "DEFAULT " + column.default : "";
3683
3698
  const columnCollation = column.nocase ? "COLLATE NOCASE" : "";
3684
- return `${column.name} ${column.type} ${check} ${required} ${columnDefault} ${columnCollation}`;
3699
+ const checkClause = checks.length > 0 ? `CHECK(${checks.join(" AND ")})` : "";
3700
+ return `${column.name} ${column.type} ${checkClause} ${required} ${columnDefault} ${columnCollation}`;
3685
3701
  });
3686
3702
  const primaryColumns = model.schema.filter((column) => column.primary);
3687
3703
  if (primaryColumns.length > 0) {
@@ -3724,38 +3740,15 @@ var formatGtfsLine = (line, model, totalLineCount) => {
3724
3740
  }
3725
3741
  continue;
3726
3742
  }
3727
- switch (type) {
3728
- case "date":
3729
- value = value.replace(/-/g, "");
3730
- if (value.length !== 8) {
3731
- throw new Error(
3732
- `Invalid date in ${filenameBase}.${filenameExtension} for ${name} on line ${lineNumber}.`
3733
- );
3734
- }
3735
- value = parseInt(value, 10);
3736
- break;
3737
- case "integer":
3738
- value = parseInt(value, 10);
3739
- break;
3740
- case "real":
3741
- value = parseFloat(value);
3742
- break;
3743
- }
3744
- if (Number.isNaN(value)) {
3745
- formattedLine[name] = null;
3746
- continue;
3743
+ if (type === "date") {
3744
+ value = value.replace(/-/g, "");
3745
+ if (value.length !== 8) {
3746
+ throw new Error(
3747
+ `Invalid date in ${filenameBase}.${filenameExtension} for ${name} on line ${lineNumber}.`
3748
+ );
3749
+ }
3747
3750
  }
3748
3751
  formattedLine[name] = value;
3749
- if (min !== void 0 && value < min) {
3750
- throw new Error(
3751
- `Invalid value in ${filenameBase}.${filenameExtension} for ${name} on line ${lineNumber}: below minimum value of ${min}.`
3752
- );
3753
- }
3754
- if (max !== void 0 && value > max) {
3755
- throw new Error(
3756
- `Invalid value in ${filenameBase}.${filenameExtension} for ${name} on line ${lineNumber}: above maximum value of ${max}.`
3757
- );
3758
- }
3759
3752
  }
3760
3753
  for (const [timeColumnName, timestampColumnName] of TIME_COLUMN_PAIRS) {
3761
3754
  const value = formattedLine[timeColumnName];
@@ -3824,7 +3817,7 @@ var importGtfsFiles = (db, task) => mapSeries2(
3824
3817
  );
3825
3818
  }
3826
3819
  task.logWarning(
3827
- `Check ${filename} for invalid data on row ${rowNumber}.`
3820
+ `Check ${filename} for invalid data on line ${rowNumber + 1}.`
3828
3821
  );
3829
3822
  throw error;
3830
3823
  }