gtfs-to-html 2.9.0 → 2.9.2

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 (40) hide show
  1. package/config-sample.json +68 -0
  2. package/dist/app/index.js +2 -1
  3. package/dist/app/index.js.map +1 -1
  4. package/dist/bin/gtfs-to-html.js +16 -9
  5. package/dist/bin/gtfs-to-html.js.map +1 -1
  6. package/dist/index.js +16 -9
  7. package/dist/index.js.map +1 -1
  8. package/docker/Dockerfile +14 -0
  9. package/docker/README.md +5 -0
  10. package/docker/config.json +21 -0
  11. package/docker/docker-compose.yml +10 -0
  12. package/examples/stop_attributes.txt +6 -0
  13. package/examples/timetable_notes.txt +8 -0
  14. package/examples/timetable_notes_references.txt +8 -0
  15. package/examples/timetable_pages.txt +3 -0
  16. package/examples/timetable_stop_order.txt +16 -0
  17. package/examples/timetables.txt +9 -0
  18. package/package.json +8 -3
  19. package/views/default/css/overview_styles.css +198 -0
  20. package/views/default/css/timetable_pdf_styles.css +69 -0
  21. package/views/default/css/timetable_styles.css +522 -0
  22. package/views/default/formatting_functions.pug +104 -0
  23. package/views/default/js/system-map.js +594 -0
  24. package/views/default/js/timetable-map.js +753 -0
  25. package/views/default/js/timetable-menu.js +57 -0
  26. package/views/default/layout.pug +11 -0
  27. package/views/default/overview.pug +27 -0
  28. package/views/default/overview_full.pug +16 -0
  29. package/views/default/timetable_continuation_as.pug +7 -0
  30. package/views/default/timetable_continuation_from.pug +7 -0
  31. package/views/default/timetable_horizontal.pug +42 -0
  32. package/views/default/timetable_hourly.pug +30 -0
  33. package/views/default/timetable_map.pug +15 -0
  34. package/views/default/timetable_menu.pug +48 -0
  35. package/views/default/timetable_note_symbol.pug +5 -0
  36. package/views/default/timetable_stop_name.pug +13 -0
  37. package/views/default/timetable_stoptime.pug +17 -0
  38. package/views/default/timetable_vertical.pug +67 -0
  39. package/views/default/timetablepage.pug +64 -0
  40. package/views/default/timetablepage_full.pug +25 -0
@@ -0,0 +1,522 @@
1
+ /* Base styles */
2
+ body {
3
+ color: #666;
4
+ font-family: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji',
5
+ 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
6
+ font-feature-settings: normal;
7
+ line-height: inherit;
8
+ margin: 0;
9
+ }
10
+
11
+ *,
12
+ ::before,
13
+ ::after {
14
+ box-sizing: border-box;
15
+ border-width: 0;
16
+ border-style: solid;
17
+ border-color: #e5e7eb;
18
+ }
19
+
20
+ h1,
21
+ h2,
22
+ h3,
23
+ h4,
24
+ h5,
25
+ h6 {
26
+ line-height: inherit;
27
+ font-weight: inherit;
28
+ color: #333;
29
+ margin: 0;
30
+ }
31
+
32
+ a {
33
+ text-decoration: none;
34
+ }
35
+
36
+ a:hover {
37
+ text-decoration: underline;
38
+ }
39
+
40
+ /* Timetable Styles */
41
+
42
+ .timetable-page {
43
+ margin-left: 1rem;
44
+ margin-right: 1rem;
45
+ }
46
+
47
+ @media (min-width: 640px) {
48
+ .timetable-page {
49
+ max-width: 640px;
50
+ }
51
+ }
52
+
53
+ @media (min-width: 768px) {
54
+ .timetable-page {
55
+ max-width: 768px;
56
+ margin-left: auto;
57
+ margin-right: auto;
58
+ }
59
+ }
60
+
61
+ @media (min-width: 1024px) {
62
+ .timetable-page {
63
+ max-width: 1024px;
64
+ }
65
+ }
66
+
67
+ @media (min-width: 1280px) {
68
+ .timetable-page {
69
+ max-width: 1280px;
70
+ }
71
+ }
72
+
73
+ @media (min-width: 1536px) {
74
+ .timetable-page {
75
+ max-width: 1536px;
76
+ }
77
+ }
78
+
79
+ .timetable-page h1 {
80
+ font-size: 1.5rem;
81
+ line-height: 2rem;
82
+ padding-top: 1rem;
83
+ gap: 0.24rem;
84
+ display: flex;
85
+ align-items: center;
86
+ }
87
+
88
+ .timetable-page h2 {
89
+ font-size: 1.25rem;
90
+ line-height: 1.75rem;
91
+ }
92
+
93
+ .timetable-page h3 {
94
+ font-weight: 600;
95
+ margin-bottom: 0.25rem;
96
+ }
97
+
98
+ .timetable-page .effective-date {
99
+ margin-top: 0.5rem;
100
+ color: rgb(75 85 99);
101
+ }
102
+
103
+ .timetable-page .sr-only {
104
+ position: absolute;
105
+ width: 1px;
106
+ height: 1px;
107
+ padding: 0;
108
+ margin: -1px;
109
+ overflow: hidden;
110
+ clip: rect(0, 0, 0, 0);
111
+ white-space: nowrap;
112
+ border-width: 0;
113
+ }
114
+
115
+ .timetable-page .hidden {
116
+ display: none;
117
+ }
118
+
119
+ .timetable-page .timetable .timetable-label {
120
+ margin-bottom: 0.5rem;
121
+ }
122
+
123
+ .timetable-page .timetable .timetable-footer {
124
+ display: grid;
125
+ grid-template-columns: repeat(1, minmax(0, 1fr));
126
+ gap: 1rem;
127
+ }
128
+
129
+ @media (min-width: 768px) {
130
+ .timetable-page .timetable .timetable-footer {
131
+ grid-template-columns: repeat(3, minmax(0, 1fr));
132
+ }
133
+ }
134
+
135
+ /* Menu Styles */
136
+
137
+ .timetable-page .timetable-simple-menu {
138
+ margin-top: 0.75rem;
139
+ margin-bottom: 0.75rem;
140
+ }
141
+
142
+ .timetable-page .timetable-jump-menu {
143
+ margin-top: 0.75rem;
144
+ margin-bottom: 0.75rem;
145
+ display: grid;
146
+ grid-template-columns: repeat(1, minmax(0, 1fr));
147
+ gap: 1rem;
148
+ }
149
+
150
+ @media (min-width: 768px) {
151
+ .timetable-page .timetable-jump-menu {
152
+ grid-template-columns: repeat(3, minmax(0, 1fr));
153
+ }
154
+ }
155
+
156
+ .timetable-page .timetable-jump-menu a {
157
+ width: 100%;
158
+ margin-bottom: 0.5rem;
159
+ }
160
+
161
+ .timetable-page .timetable-radio-menu {
162
+ margin-top: 0.75rem;
163
+ margin-bottom: 0.75rem;
164
+ display: grid;
165
+ grid-template-columns: repeat(1, minmax(0, 1fr));
166
+ gap: 1rem;
167
+ }
168
+
169
+ @media (min-width: 768px) {
170
+ .timetable-page .timetable-radio-menu {
171
+ grid-template-columns: repeat(3, minmax(0, 1fr));
172
+ }
173
+ }
174
+
175
+ .timetable-page .timetable-radio-menu label {
176
+ width: 100%;
177
+ margin-bottom: 0.5rem;
178
+ }
179
+
180
+ .timetable-page .menu-type-radio .timetable {
181
+ display: none;
182
+ }
183
+
184
+ .timetable-page .btn-blue {
185
+ color: rgb(255 255 255);
186
+ padding: 0.75rem 1.5rem;
187
+ background-color: rgb(37 99 235);
188
+ border-radius: 0.375rem;
189
+ justify-content: center;
190
+ align-items: center;
191
+ display: inline-flex;
192
+ cursor: pointer;
193
+ text-decoration: none;
194
+ }
195
+
196
+ .timetable-page .btn-blue:hover {
197
+ background-color: rgb(29 78 216);
198
+ text-decoration: none;
199
+ }
200
+
201
+ .timetable-page .btn-gray {
202
+ color: rgb(75 85 99);
203
+ padding: 0.75rem 1.5rem;
204
+ background-color: rgb(209 213 219);
205
+ border-radius: 0.375rem;
206
+ justify-content: center;
207
+ align-items: center;
208
+ display: inline-flex;
209
+ cursor: pointer;
210
+ text-decoration: none;
211
+ }
212
+
213
+ .timetable-page .btn-gray:hover {
214
+ background-color: rgb(201, 206, 213);
215
+ text-decoration: none;
216
+ }
217
+
218
+ .timetable-page .btn-sm {
219
+ padding: 0.25rem 1rem;
220
+ border-radius: 0.25rem;
221
+ }
222
+
223
+ .timetable-page .timetable {
224
+ margin-bottom: 2.5rem;
225
+ }
226
+
227
+ .timetable-page .timetable .table-vertical .stop-header {
228
+ text-align: center;
229
+ line-height: 1.15;
230
+ }
231
+
232
+ .timetable-page .timetable .run-header {
233
+ text-align: center;
234
+ line-height: 1.15;
235
+ }
236
+
237
+ .timetable-page .timetable .run-footer {
238
+ text-align: center;
239
+ }
240
+
241
+ .timetable-page .timetable .run-footer .continues-as-route {
242
+ font-weight: bold;
243
+ }
244
+
245
+ .timetable-page .timetable .stop-code {
246
+ font-weight: normal;
247
+ }
248
+
249
+ .timetable-page .timetable .stop-time {
250
+ text-align: center;
251
+ }
252
+
253
+ .timetable-page .timetable .stop-time.pm {
254
+ font-weight: bold;
255
+ }
256
+
257
+ .timetable-page .timetable a.symbol {
258
+ padding-left: 4px;
259
+ color: #212529;
260
+ text-decoration: none;
261
+ }
262
+
263
+ .timetable-page .timetable .trip-frequency {
264
+ text-align: center;
265
+ font-weight: bold;
266
+ }
267
+
268
+ .timetable-page .timetable .city-row {
269
+ font-size: 1.5em;
270
+ color: #415d86;
271
+ }
272
+
273
+ .timetable-page .timetable th.city-column {
274
+ font-size: 1.5em;
275
+ text-align: center;
276
+ }
277
+
278
+ .timetable-page .timetable .table-container {
279
+ overflow-x: scroll;
280
+ margin: 20px 0;
281
+ }
282
+
283
+ .timetable-page .timetable .table-container::-webkit-scrollbar {
284
+ -webkit-appearance: none;
285
+ height: 8px;
286
+ }
287
+
288
+ .timetable-page .timetable .table-container::-webkit-scrollbar-track {
289
+ background-color: rgba(57, 57, 57, 0.2);
290
+ border-radius: 4px;
291
+ }
292
+
293
+ .timetable-page .timetable .table-container::-webkit-scrollbar-thumb {
294
+ border-radius: 4px;
295
+ background-color: rgba(156, 156, 156, 0.8);
296
+ }
297
+
298
+ .timetable-page .timetable .table-container table {
299
+ text-indent: 0;
300
+ border-color: inherit;
301
+ border-collapse: collapse;
302
+ }
303
+
304
+ .timetable-page .timetable thead tr {
305
+ background: #bae6fd;
306
+ }
307
+
308
+ .timetable-page .timetable thead tr,
309
+ .timetable-page .timetable thead tr a {
310
+ color: #222222;
311
+ }
312
+
313
+ .timetable-page .timetable th {
314
+ text-align: left;
315
+ }
316
+
317
+ .timetable-page .timetable td,
318
+ .timetable-page .timetable th {
319
+ padding: 0;
320
+ }
321
+
322
+ .timetable-page .timetable table > thead > tr > th,
323
+ .timetable-page .timetable table > tbody > tr > th,
324
+ .timetable-page .timetable table > tfoot > tr > th,
325
+ .timetable-page .timetable table > thead > tr > td,
326
+ .timetable-page .timetable table > tbody > tr > td,
327
+ .timetable-page .timetable table > tfoot > tr > td {
328
+ padding: 8px;
329
+ line-height: 1.42857143;
330
+ vertical-align: top;
331
+ border: 1px solid #dddddd;
332
+ }
333
+
334
+ .timetable-page .timetable table > thead > tr > th {
335
+ vertical-align: top;
336
+ border-bottom: 2px solid #dddddd;
337
+ }
338
+
339
+ .timetable-page .timetable table > caption + thead > tr:first-child > th,
340
+ .timetable-page .timetable table > colgroup + thead > tr:first-child > th,
341
+ .timetable-page .timetable table > thead:first-child > tr:first-child > th,
342
+ .timetable-page .timetable table > caption + thead > tr:first-child > td,
343
+ .timetable-page .timetable table > colgroup + thead > tr:first-child > td,
344
+ .timetable-page .timetable table > thead:first-child > tr:first-child > td {
345
+ border-top: 0;
346
+ }
347
+
348
+ .timetable-page .timetable table > tbody + tbody {
349
+ border-top: 2px solid #dddddd;
350
+ }
351
+
352
+ .timetable-page .timetable table > thead > tr > th,
353
+ .timetable-page .timetable table > thead > tr > td {
354
+ border-bottom-width: 2px;
355
+ }
356
+
357
+ .timetable-page .timetable table > tbody > tr:nth-of-type(odd) {
358
+ background-color: #f9f9f9;
359
+ }
360
+
361
+ .timetable-page .table-horizontal tbody tr th.stop-name-container {
362
+ min-width: 175px;
363
+ }
364
+
365
+ @media screen and (min-width: 768px) {
366
+ .timetable-page .table-horizontal tbody tr th.stop-name-container {
367
+ min-width: 250px;
368
+ }
369
+ }
370
+
371
+ .timetable-page .table-hourly {
372
+ width: auto;
373
+ }
374
+
375
+ .timetable-page .timetable .table-vertical .trip-row .trip-notes {
376
+ text-wrap: nowrap;
377
+ }
378
+
379
+ .timetable-page .route-color-swatch {
380
+ min-width: 29px;
381
+ height: 29px;
382
+ border-radius: 50%;
383
+ text-align: center;
384
+ line-height: 26px;
385
+ font-size: 12px;
386
+ letter-spacing: -0.5px;
387
+ padding: 0 5px;
388
+ flex: none;
389
+ flex-shrink: 0;
390
+ }
391
+
392
+ .timetable-page .route-color-swatch-large {
393
+ min-width: 46px;
394
+ height: 46px;
395
+ border-radius: 50%;
396
+ text-align: center;
397
+ line-height: 46px;
398
+ font-size: 20px;
399
+ font-weight: bold;
400
+ letter-spacing: -1px;
401
+ padding: 0 6px;
402
+ flex: none;
403
+ flex-shrink: 0;
404
+ }
405
+
406
+ /* Map Styles */
407
+
408
+ .timetable-page .map-container {
409
+ position: relative;
410
+ }
411
+
412
+ .timetable-page .map {
413
+ min-height: 350px;
414
+ }
415
+
416
+ @media screen and (min-width: 768px) {
417
+ .timetable-page .map {
418
+ min-height: 450px;
419
+ }
420
+ }
421
+
422
+ .timetable-page .map .mapboxgl-popup-content .popup-title {
423
+ margin: 0 20px 5px 0;
424
+ font-size: 1rem;
425
+ font-weight: 700;
426
+ line-height: 1;
427
+ }
428
+
429
+ .timetable-page .map .mapboxgl-popup-content .popup-label {
430
+ margin-right: 0.25rem;
431
+ }
432
+
433
+ .timetable-page .map .mapboxgl-popup-content .route-list {
434
+ margin-bottom: 0.5rem;
435
+ }
436
+
437
+ .timetable-page .map .mapboxgl-popup-content .map-route-item {
438
+ display: flex;
439
+ align-items: center;
440
+ font-size: 0.75rem;
441
+ line-height: 1;
442
+ margin-bottom: 0.5rem;
443
+ gap: 0.5rem;
444
+ }
445
+
446
+ .timetable-page .map .mapboxgl-popup-content .map-route-item:hover {
447
+ text-decoration: none;
448
+ }
449
+
450
+ .timetable-page
451
+ .map
452
+ .mapboxgl-popup-content
453
+ a.map-route-item
454
+ .underline-hover:hover {
455
+ text-decoration: underline;
456
+ }
457
+
458
+ .timetable-page .map .mapboxgl-popup-content .mapboxgl-popup-close-button {
459
+ padding: 0 5px;
460
+ }
461
+
462
+ .timetable-page .map-legend {
463
+ max-width: 30%;
464
+ background-color: #fff;
465
+ border-radius: 3px;
466
+ bottom: 30px;
467
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
468
+ padding: 10px;
469
+ position: absolute;
470
+ left: 10px;
471
+ bottom: 35px;
472
+ z-index: 1;
473
+ }
474
+
475
+ .timetable-page .map-legend .legend-item {
476
+ padding: 5px 0;
477
+ display: flex;
478
+ flex-direction: row;
479
+ align-items: start;
480
+ gap: 4px;
481
+ }
482
+
483
+ .timetable-page .map-legend .legend-item .legend-icon {
484
+ width: 22px;
485
+ padding-top: 3px;
486
+ display: flex;
487
+ flex-direction: row;
488
+ align-items: center;
489
+ justify-content: center;
490
+ flex-shrink: 0;
491
+ }
492
+
493
+ .timetable-page .map-legend .legend-item .legend-text {
494
+ font-size: 12px;
495
+ }
496
+
497
+ .timetable-page .stop-marker {
498
+ background: #ffffff;
499
+ border: 2px solid #3f4a5c;
500
+ border-radius: 50%;
501
+ width: 12px;
502
+ height: 12px;
503
+ }
504
+
505
+ .timetable-page .vehicle-marker {
506
+ width: 20px;
507
+ height: 20px;
508
+ border-radius: 50%;
509
+ background: #d52f3e;
510
+ border: 1px solid #50161c;
511
+ display: flex;
512
+ align-items: center;
513
+ justify-content: center;
514
+ }
515
+
516
+ .timetable-page .vehicle-marker .vehicle-marker-arrow {
517
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512'%3E%3Cpath d='M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2 160 448c0 17.7 14.3 32 32 32s32-14.3 32-32l0-306.7L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z'/%3E%3C/svg%3E");
518
+ background-repeat: no-repeat;
519
+ background-position: center;
520
+ width: 14px;
521
+ height: 14px;
522
+ }
@@ -0,0 +1,104 @@
1
+ -
2
+ function formatFrequencyWarning(frequencies) {
3
+ let warning = 'Trip times shown below are an example only. ';
4
+ frequencies.forEach((frequency, idx) => {
5
+ if (idx === 0) {
6
+ warning += 'This route runs every ';
7
+ } else {
8
+ warning += ' and ';
9
+ }
10
+ warning += `${frequency.headway_min} minutes between ${frequency.start_formatted_time} and ${frequency.end_formatted_time}`;
11
+ });
12
+ warning += '.';
13
+ return warning;
14
+ }
15
+
16
+ function getAgencyTimetableGroups(timetablePages, agencies) {
17
+ const agencyIds = [];
18
+ for (const timetablePage of timetablePages) {
19
+ agencyIds.push(...timetablePage.agency_ids);
20
+ }
21
+
22
+ const uniqueAgencyIds = _.uniq(_.compact(agencyIds));
23
+
24
+ if (uniqueAgencyIds.length === 0) {
25
+ return [{
26
+ agency: _.first(agencies),
27
+ timetablePages
28
+ }];
29
+ }
30
+
31
+ return _.orderBy(uniqueAgencyIds.map(agencyId => {
32
+ return {
33
+ agency: agencies.find(agency => agency.agency_id === agencyId) || _.first(agencies),
34
+ timetablePages: timetablePages.filter(timetablePage => timetablePage.agency_ids.includes(agencyId))
35
+ };
36
+ }), timetableGroup => timetableGroup.agency.agency_name.toLowerCase());
37
+ }
38
+
39
+ function prepareMapData(timetablePage, config) {
40
+ const routes = {}
41
+ const stops = {}
42
+ const tripIds = []
43
+ const geojsons = {}
44
+
45
+ for (const timetable of timetablePage.consolidatedTimetables) {
46
+ tripIds.push(...timetable.orderedTrips.map(trip => trip.trip_id))
47
+
48
+ const minifiedGeojson = {
49
+ type: 'FeatureCollection',
50
+ features: []
51
+ }
52
+
53
+ for (const feature of timetable.geojson.features) {
54
+ if (feature.geometry.type.toLowerCase() === 'point') {
55
+ for (const route of feature.properties.routes) {
56
+ routes[route.route_id] = route
57
+ }
58
+
59
+ const stop = _.pick(feature.properties, ['stop_id', 'stop_code', 'stop_name'])
60
+ stops[stop.stop_id] = stop
61
+
62
+ feature.properties = {
63
+ route_ids: feature.properties.routes.map(route => route.route_id),
64
+ stop_id: feature.properties.stop_id,
65
+ }
66
+ } else if (feature.geometry.type.toLowerCase() === 'linestring') {
67
+ feature.properties = {
68
+ route_color: feature.properties.route_color
69
+ }
70
+ } else if (feature.geometry.type.toLowerCase() === 'multilinestring') {
71
+ feature.properties = {
72
+ route_color: feature.properties.route_color
73
+ }
74
+ }
75
+ minifiedGeojson.features.push(feature)
76
+ }
77
+
78
+ geojsons[formatHtmlId(timetable.timetable_id)] = minifiedGeojson
79
+ }
80
+
81
+ return {
82
+ geojsons,
83
+ tripIds: _.uniq(tripIds),
84
+ routes,
85
+ stops,
86
+ gtfsRealtimeUrls: _.pick(config.agencies.find(agency => agency.realtimeVehiclePositions?.url), ['realtimeAlerts', 'realtimeTripUpdates', 'realtimeVehiclePositions']),
87
+ }
88
+ }
89
+
90
+ function getRouteColorsAsCss(route) {
91
+ if (route && route.route_color) {
92
+ return `background: #${route.route_color}; color: #${route.route_text_color ?? 'ffffff'};`
93
+ }
94
+
95
+ return ''
96
+ }
97
+
98
+ function formatRouteName(route) {
99
+ if (route.route_long_name === null || route.route_long_name === '') {
100
+ return `Route ${route.route_short_name}`;
101
+ }
102
+
103
+ return route.route_long_name;
104
+ }