minotor 1.0.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.
Files changed (131) hide show
  1. package/.cspell.json +43 -0
  2. package/.czrc +3 -0
  3. package/.editorconfig +10 -0
  4. package/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
  5. package/.github/ISSUE_TEMPLATE/config.yml +5 -0
  6. package/.github/ISSUE_TEMPLATE/feature_request.md +29 -0
  7. package/.github/PULL_REQUEST_TEMPLATE.md +4 -0
  8. package/.github/workflows/minotor.yml +85 -0
  9. package/.prettierrc +7 -0
  10. package/.releaserc.json +27 -0
  11. package/CHANGELOG.md +6 -0
  12. package/LICENSE +21 -0
  13. package/README.md +166 -0
  14. package/dist/bundle.cjs.js +16507 -0
  15. package/dist/bundle.cjs.js.map +1 -0
  16. package/dist/bundle.esm.js +16496 -0
  17. package/dist/bundle.esm.js.map +1 -0
  18. package/dist/bundle.umd.js +2 -0
  19. package/dist/bundle.umd.js.map +1 -0
  20. package/dist/cli/__tests__/minotor.test.d.ts +1 -0
  21. package/dist/cli/minotor.d.ts +5 -0
  22. package/dist/cli/repl.d.ts +1 -0
  23. package/dist/cli/utils.d.ts +3 -0
  24. package/dist/cli.mjs +20504 -0
  25. package/dist/cli.mjs.map +1 -0
  26. package/dist/gtfs/__tests__/parser.test.d.ts +1 -0
  27. package/dist/gtfs/__tests__/routes.test.d.ts +1 -0
  28. package/dist/gtfs/__tests__/services.test.d.ts +1 -0
  29. package/dist/gtfs/__tests__/stops.test.d.ts +1 -0
  30. package/dist/gtfs/__tests__/time.test.d.ts +1 -0
  31. package/dist/gtfs/__tests__/transfers.test.d.ts +1 -0
  32. package/dist/gtfs/__tests__/trips.test.d.ts +1 -0
  33. package/dist/gtfs/__tests__/utils.test.d.ts +1 -0
  34. package/dist/gtfs/parser.d.ts +34 -0
  35. package/dist/gtfs/profiles/__tests__/ch.test.d.ts +1 -0
  36. package/dist/gtfs/profiles/ch.d.ts +2 -0
  37. package/dist/gtfs/profiles/standard.d.ts +2 -0
  38. package/dist/gtfs/routes.d.ts +11 -0
  39. package/dist/gtfs/services.d.ts +19 -0
  40. package/dist/gtfs/stops.d.ts +20 -0
  41. package/dist/gtfs/time.d.ts +17 -0
  42. package/dist/gtfs/transfers.d.ts +22 -0
  43. package/dist/gtfs/trips.d.ts +26 -0
  44. package/dist/gtfs/utils.d.ts +21 -0
  45. package/dist/index.d.ts +11 -0
  46. package/dist/routing/__tests__/router.test.d.ts +1 -0
  47. package/dist/routing/plotter.d.ts +11 -0
  48. package/dist/routing/query.d.ts +35 -0
  49. package/dist/routing/result.d.ts +28 -0
  50. package/dist/routing/route.d.ts +25 -0
  51. package/dist/routing/router.d.ts +33 -0
  52. package/dist/stops/__tests__/io.test.d.ts +1 -0
  53. package/dist/stops/__tests__/stopFinder.test.d.ts +1 -0
  54. package/dist/stops/i18n.d.ts +10 -0
  55. package/dist/stops/io.d.ts +4 -0
  56. package/dist/stops/proto/stops.d.ts +53 -0
  57. package/dist/stops/stops.d.ts +16 -0
  58. package/dist/stops/stopsIndex.d.ts +52 -0
  59. package/dist/timetable/__tests__/io.test.d.ts +1 -0
  60. package/dist/timetable/__tests__/timetable.test.d.ts +1 -0
  61. package/dist/timetable/duration.d.ts +51 -0
  62. package/dist/timetable/io.d.ts +8 -0
  63. package/dist/timetable/proto/timetable.d.ts +122 -0
  64. package/dist/timetable/time.d.ts +98 -0
  65. package/dist/timetable/timetable.d.ts +82 -0
  66. package/dist/umdIndex.d.ts +9 -0
  67. package/eslint.config.mjs +52 -0
  68. package/package.json +109 -0
  69. package/rollup.config.js +44 -0
  70. package/src/cli/__tests__/minotor.test.ts +23 -0
  71. package/src/cli/minotor.ts +112 -0
  72. package/src/cli/repl.ts +200 -0
  73. package/src/cli/utils.ts +36 -0
  74. package/src/gtfs/__tests__/parser.test.ts +591 -0
  75. package/src/gtfs/__tests__/resources/sample-feed/agency.txt +2 -0
  76. package/src/gtfs/__tests__/resources/sample-feed/calendar.txt +3 -0
  77. package/src/gtfs/__tests__/resources/sample-feed/calendar_dates.txt +2 -0
  78. package/src/gtfs/__tests__/resources/sample-feed/fare_attributes.txt +3 -0
  79. package/src/gtfs/__tests__/resources/sample-feed/fare_rules.txt +5 -0
  80. package/src/gtfs/__tests__/resources/sample-feed/frequencies.txt +12 -0
  81. package/src/gtfs/__tests__/resources/sample-feed/routes.txt +6 -0
  82. package/src/gtfs/__tests__/resources/sample-feed/sample-feed.zip +0 -0
  83. package/src/gtfs/__tests__/resources/sample-feed/shapes.txt +1 -0
  84. package/src/gtfs/__tests__/resources/sample-feed/stop_times.txt +34 -0
  85. package/src/gtfs/__tests__/resources/sample-feed/stops.txt +10 -0
  86. package/src/gtfs/__tests__/resources/sample-feed/trips.txt +13 -0
  87. package/src/gtfs/__tests__/resources/sample-feed.zip +0 -0
  88. package/src/gtfs/__tests__/routes.test.ts +63 -0
  89. package/src/gtfs/__tests__/services.test.ts +209 -0
  90. package/src/gtfs/__tests__/stops.test.ts +177 -0
  91. package/src/gtfs/__tests__/time.test.ts +27 -0
  92. package/src/gtfs/__tests__/transfers.test.ts +117 -0
  93. package/src/gtfs/__tests__/trips.test.ts +463 -0
  94. package/src/gtfs/__tests__/utils.test.ts +13 -0
  95. package/src/gtfs/parser.ts +154 -0
  96. package/src/gtfs/profiles/__tests__/ch.test.ts +43 -0
  97. package/src/gtfs/profiles/ch.ts +70 -0
  98. package/src/gtfs/profiles/standard.ts +39 -0
  99. package/src/gtfs/routes.ts +48 -0
  100. package/src/gtfs/services.ts +98 -0
  101. package/src/gtfs/stops.ts +112 -0
  102. package/src/gtfs/time.ts +33 -0
  103. package/src/gtfs/transfers.ts +102 -0
  104. package/src/gtfs/trips.ts +228 -0
  105. package/src/gtfs/utils.ts +42 -0
  106. package/src/index.ts +28 -0
  107. package/src/routing/__tests__/router.test.ts +760 -0
  108. package/src/routing/plotter.ts +70 -0
  109. package/src/routing/query.ts +74 -0
  110. package/src/routing/result.ts +108 -0
  111. package/src/routing/route.ts +94 -0
  112. package/src/routing/router.ts +262 -0
  113. package/src/stops/__tests__/io.test.ts +43 -0
  114. package/src/stops/__tests__/stopFinder.test.ts +185 -0
  115. package/src/stops/i18n.ts +40 -0
  116. package/src/stops/io.ts +94 -0
  117. package/src/stops/proto/stops.proto +26 -0
  118. package/src/stops/proto/stops.ts +445 -0
  119. package/src/stops/stops.ts +24 -0
  120. package/src/stops/stopsIndex.ts +151 -0
  121. package/src/timetable/__tests__/io.test.ts +175 -0
  122. package/src/timetable/__tests__/timetable.test.ts +180 -0
  123. package/src/timetable/duration.ts +85 -0
  124. package/src/timetable/io.ts +265 -0
  125. package/src/timetable/proto/timetable.proto +76 -0
  126. package/src/timetable/proto/timetable.ts +1304 -0
  127. package/src/timetable/time.ts +192 -0
  128. package/src/timetable/timetable.ts +286 -0
  129. package/src/umdIndex.ts +14 -0
  130. package/tsconfig.build.json +4 -0
  131. package/tsconfig.json +21 -0
@@ -0,0 +1,200 @@
1
+ import repl from 'node:repl';
2
+
3
+ import fs from 'fs';
4
+
5
+ import { Query, Router, StopsIndex, Time, Timetable } from '../index.js';
6
+ import { plotGraphToDotFile, prettyPrintRoute } from './utils.js';
7
+
8
+ export const startRepl = (stopsPath: string, timetablePath: string) => {
9
+ const stopsIndex = StopsIndex.fromData(fs.readFileSync(stopsPath));
10
+ const timetable = Timetable.fromData(fs.readFileSync(timetablePath));
11
+ console.log(
12
+ 'Enter your stop (.find) or routing (.route) queries. Type ".exit" to quit.',
13
+ );
14
+ const replServer = repl.start({
15
+ prompt: 'minotor> ',
16
+ ignoreUndefined: true,
17
+ });
18
+ replServer.context.stopFinder = stopsIndex;
19
+ replServer.defineCommand('find', {
20
+ help: 'Find stops by name using .find <query>',
21
+ action(query: string) {
22
+ this.clearBufferedCommand();
23
+ const stops = stopsIndex.findStopsByName(query);
24
+ stops.forEach((stop) => {
25
+ console.log(`${stop.name} (${stop.id})`);
26
+ });
27
+ this.displayPrompt();
28
+ },
29
+ });
30
+ replServer.defineCommand('route', {
31
+ help: 'Find a route using .route from <stationIdOrName> to <stationIdOrName> at <HH:mm> [with <N> transfers]',
32
+ action(routeQuery: string) {
33
+ this.clearBufferedCommand();
34
+ const parts = routeQuery.split(' ').filter(Boolean);
35
+ const withTransfersIndex = parts.indexOf('with');
36
+ const maxTransfers =
37
+ withTransfersIndex !== -1 && parts[withTransfersIndex + 1] !== undefined
38
+ ? parseInt(parts[withTransfersIndex + 1] as string)
39
+ : 4;
40
+ const atTime = parts
41
+ .slice(
42
+ withTransfersIndex === -1
43
+ ? parts.indexOf('at') + 1
44
+ : parts.indexOf('at') + 1,
45
+ withTransfersIndex === -1 ? parts.length : withTransfersIndex,
46
+ )
47
+ .join(' ');
48
+ const fromIndex = parts.indexOf('from');
49
+ const toIndex = parts.indexOf('to');
50
+ const fromId = parts.slice(fromIndex + 1, toIndex).join(' ');
51
+ const toId = parts
52
+ .slice(
53
+ toIndex + 1,
54
+ withTransfersIndex === -1 ? parts.indexOf('at') : parts.indexOf('at'),
55
+ )
56
+ .join(' ');
57
+
58
+ if (!fromId || !toId || !atTime) {
59
+ console.log(
60
+ 'Usage: .route from <stationIdOrName> to <stationIdOrName> at <HH:mm> [with <N> transfers]',
61
+ );
62
+ this.displayPrompt();
63
+ return;
64
+ }
65
+
66
+ const fromStop =
67
+ stopsIndex.findStopById(fromId) ||
68
+ stopsIndex.findStopsByName(fromId)[0];
69
+ const toStop =
70
+ stopsIndex.findStopById(toId) || stopsIndex.findStopsByName(toId)[0];
71
+
72
+ if (!fromStop) {
73
+ console.log(`No stop found for 'from' ID or name: ${fromId}`);
74
+ this.displayPrompt();
75
+ return;
76
+ }
77
+
78
+ if (!toStop) {
79
+ console.log(`No stop found for 'to' ID or name: ${toId}`);
80
+ this.displayPrompt();
81
+ return;
82
+ }
83
+
84
+ const departureTime = Time.fromString(atTime);
85
+
86
+ try {
87
+ const query = new Query.Builder()
88
+ .from(fromStop.id)
89
+ .to([toStop.id])
90
+ .departureTime(departureTime)
91
+ .maxTransfers(maxTransfers)
92
+ .build();
93
+
94
+ const router = new Router(timetable, stopsIndex);
95
+
96
+ const result = router.route(query);
97
+ const arrivalTime = result.arrivalAt(toStop.id);
98
+ if (arrivalTime === undefined) {
99
+ console.log(`Destination not reachable`);
100
+ } else {
101
+ console.log(
102
+ `Arriving to ${toStop.name} at ${arrivalTime.time.toString()} with ${arrivalTime.legNumber - 1} transfers from ${stopsIndex.findStopById(arrivalTime.origin)?.name}.`,
103
+ );
104
+ }
105
+ const bestRoute = result.bestRoute(toStop.id);
106
+
107
+ if (bestRoute) {
108
+ console.log(`Found route from ${fromStop.name} to ${toStop.name}:`);
109
+ prettyPrintRoute(bestRoute);
110
+ } else {
111
+ console.log('No route found');
112
+ }
113
+ } catch (error) {
114
+ console.log('Error querying route:', error);
115
+ }
116
+
117
+ this.displayPrompt();
118
+ },
119
+ });
120
+ replServer.defineCommand('plot', {
121
+ help: 'Plot a network graph using .plot from <stationId> to <stationId> at <HH:mm> [with <N> transfers] [to <graph.dot>]',
122
+ action(routeQuery: string) {
123
+ this.clearBufferedCommand();
124
+ const parts = routeQuery.split(' ').filter(Boolean);
125
+ const withTransfersIndex = parts.indexOf('with');
126
+ const maxTransfers =
127
+ withTransfersIndex !== -1 && parts[withTransfersIndex + 1] !== undefined
128
+ ? parseInt(parts[withTransfersIndex + 1] as string)
129
+ : 1;
130
+ const atTimeIndex = parts.indexOf('at');
131
+ const atTime = parts
132
+ .slice(
133
+ atTimeIndex + 1,
134
+ withTransfersIndex === -1
135
+ ? parts.indexOf('to', atTimeIndex) >= 0
136
+ ? parts.indexOf('to', atTimeIndex)
137
+ : parts.length
138
+ : withTransfersIndex,
139
+ )
140
+ .join(' ');
141
+ const fromIndex = parts.indexOf('from');
142
+ const toIndex = parts.indexOf('to');
143
+ const toFileIndex =
144
+ toIndex !== -1 && parts.indexOf('to', toIndex + 1) !== -1
145
+ ? parts.indexOf('to', toIndex + 1)
146
+ : -1;
147
+ const fromId = parts.slice(fromIndex + 1, toIndex).join(' ');
148
+ const toId = parts.slice(toIndex + 1, atTimeIndex).join(' ');
149
+ const outputFile =
150
+ toFileIndex !== -1
151
+ ? parts.slice(toFileIndex + 1).join(' ')
152
+ : `${fromId.replace(/ /g, '')}-${toId.replace(/ /g, '')}-${atTime.replace(/:/g, '')}.dot`;
153
+
154
+ if (!fromId || !toId || !atTime || isNaN(maxTransfers)) {
155
+ console.log(
156
+ 'Usage: .plot from <stationId> to <stationId> at <HH:mm> [with <N> transfers] [to <graph.dot>]',
157
+ );
158
+ this.displayPrompt();
159
+ return;
160
+ }
161
+
162
+ const fromStop =
163
+ stopsIndex.findStopById(fromId) ||
164
+ stopsIndex.findStopsByName(fromId)[0];
165
+ const toStop =
166
+ stopsIndex.findStopById(toId) || stopsIndex.findStopsByName(toId)[0];
167
+
168
+ if (!fromStop) {
169
+ console.log(`No stop found for 'from' ID or name: ${fromId}`);
170
+ this.displayPrompt();
171
+ return;
172
+ }
173
+
174
+ if (!toStop) {
175
+ console.log(`No stop found for 'to' ID or name: ${toId}`);
176
+ this.displayPrompt();
177
+ return;
178
+ }
179
+
180
+ const departureTime = Time.fromString(atTime);
181
+ try {
182
+ const query = new Query.Builder()
183
+ .from(fromStop.id)
184
+ .to([toStop.id])
185
+ .departureTime(departureTime)
186
+ .maxTransfers(maxTransfers)
187
+ .build();
188
+
189
+ const router = new Router(timetable, stopsIndex);
190
+
191
+ const result = router.route(query);
192
+ plotGraphToDotFile(result, outputFile);
193
+ } catch (error) {
194
+ console.log('Error plotting route:', error);
195
+ }
196
+
197
+ this.displayPrompt();
198
+ },
199
+ });
200
+ };
@@ -0,0 +1,36 @@
1
+ import fs from 'fs';
2
+
3
+ import { Plotter, Result, Route } from '../index.js';
4
+
5
+ export const prettyPrintRoute = (route: Route): void => {
6
+ route.legs.forEach((leg, index) => {
7
+ const fromStop = `From: ${leg.from.name}${leg.from.platform ? ' (Pl. ' + leg.from.platform + ')' : ''}`;
8
+ const toStop = `To: ${leg.to.name}${leg.to.platform ? ' (Pl. ' + leg.to.platform + ')' : ''}`;
9
+ let transferDetails = '';
10
+ let travelDetails = '';
11
+
12
+ if ('minTransferTime' in leg) {
13
+ transferDetails = `Minimum Transfer Time: ${leg.minTransferTime?.toString()}`;
14
+ }
15
+ if ('route' in leg && 'departureTime' in leg && 'arrivalTime' in leg) {
16
+ travelDetails = `Route: ${leg.route.type} ${leg.route.name}, Departure: ${leg.departureTime.toString()}, Arrival: ${leg.arrivalTime.toString()}`;
17
+ }
18
+
19
+ console.log(`Leg ${index + 1}:`);
20
+ console.log(` ${fromStop}`);
21
+ console.log(` ${toStop}`);
22
+ if (transferDetails) {
23
+ console.log(` ${transferDetails}`);
24
+ }
25
+ if (travelDetails) {
26
+ console.log(` ${travelDetails}`);
27
+ }
28
+ console.log('');
29
+ });
30
+ };
31
+
32
+ export const plotGraphToDotFile = (result: Result, filePath: string): void => {
33
+ const plotter = new Plotter(result);
34
+ const dotContent = plotter.plotDotGraph();
35
+ fs.writeFileSync(filePath, dotContent);
36
+ };