gtfs-to-html 2.5.6 → 2.5.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.
- package/CHANGELOG.md +11 -0
- package/lib/formatters.js +28 -15
- package/lib/utils.js +19 -15
- package/package.json +7 -7
- package/views/default/timetable_horizontal.pug +1 -1
- package/views/default/timetable_vertical.pug +1 -1
- package/www/blog/2021-11-06-CSV-Export.md +1 -1
- package/www/docs/configuration.md +136 -136
- package/www/docs/logging-sql-queries.md +2 -2
- package/www/docs/quick-start.md +41 -38
- package/www/package.json +5 -14
- package/www/yarn.lock +3012 -2131
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.5.7] - 2023-11-07
|
|
9
|
+
|
|
10
|
+
### Updated
|
|
11
|
+
|
|
12
|
+
- Dependency updates
|
|
13
|
+
- Docusaurus 3.0 for documentation site
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Add is_timepoint value to each stop
|
|
18
|
+
|
|
8
19
|
## [2.5.6] - 2023-08-23
|
|
9
20
|
|
|
10
21
|
### Updated
|
package/lib/formatters.js
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
toGTFSTime,
|
|
21
21
|
updateTimeByOffset,
|
|
22
22
|
} from './time-utils.js';
|
|
23
|
+
import { isTimepoint } from './utils.js';
|
|
23
24
|
|
|
24
25
|
/*
|
|
25
26
|
* Replace all instances in a string with items from an object.
|
|
@@ -141,13 +142,13 @@ function filterHourlyTimes(stops) {
|
|
|
141
142
|
time,
|
|
142
143
|
}));
|
|
143
144
|
const sortedFirstStopTimesAndIndex = sortBy(firstStopTimesAndIndex, (item) =>
|
|
144
|
-
Number.parseInt(item.time.format('m'), 10)
|
|
145
|
+
Number.parseInt(item.time.format('m'), 10),
|
|
145
146
|
);
|
|
146
147
|
|
|
147
148
|
// Filter and arrange stoptimes for all stops based on sort.
|
|
148
149
|
return stops.map((stop) => {
|
|
149
150
|
stop.hourlyTimes = sortedFirstStopTimesAndIndex.map((item) =>
|
|
150
|
-
fromGTFSTime(stop.trips[item.idx].arrival_time).format(':mm')
|
|
151
|
+
fromGTFSTime(stop.trips[item.idx].arrival_time).format(':mm'),
|
|
151
152
|
);
|
|
152
153
|
|
|
153
154
|
return stop;
|
|
@@ -234,7 +235,7 @@ export function formatTrip(trip, timetable, calendars, config) {
|
|
|
234
235
|
trip.route_short_name = timetable.routes[0].route_short_name;
|
|
235
236
|
} else {
|
|
236
237
|
const route = timetable.routes.find(
|
|
237
|
-
(route) => route.route_id === trip.route_id
|
|
238
|
+
(route) => route.route_id === trip.route_id,
|
|
238
239
|
);
|
|
239
240
|
trip.route_short_name = route.route_short_name;
|
|
240
241
|
}
|
|
@@ -274,7 +275,7 @@ export function formatFrequency(frequency, config) {
|
|
|
274
275
|
*/
|
|
275
276
|
export function formatTimetableId(timetable) {
|
|
276
277
|
let timetableId = `${timetable.route_ids.join('_')}|${calendarToCalendarCode(
|
|
277
|
-
timetable
|
|
278
|
+
timetable,
|
|
278
279
|
)}`;
|
|
279
280
|
if (!isNullOrEmpty(timetable.direction_id)) {
|
|
280
281
|
timetableId += `|${timetable.direction_id}`;
|
|
@@ -328,7 +329,7 @@ export function formatStops(timetable, config) {
|
|
|
328
329
|
const departureStoptime = clone(stoptime);
|
|
329
330
|
departureStoptime.type = 'departure';
|
|
330
331
|
timetable.stops[stopIndex + 1].trips.push(
|
|
331
|
-
formatStopTime(departureStoptime, timetable, config)
|
|
332
|
+
formatStopTime(departureStoptime, timetable, config),
|
|
332
333
|
);
|
|
333
334
|
}
|
|
334
335
|
|
|
@@ -343,8 +344,13 @@ export function formatStops(timetable, config) {
|
|
|
343
344
|
for (const stop of timetable.stops) {
|
|
344
345
|
const lastStopTime = last(stop.trips);
|
|
345
346
|
if (!lastStopTime || lastStopTime.trip_id !== trip.trip_id) {
|
|
346
|
-
|
|
347
|
-
|
|
347
|
+
stop.trips.push(
|
|
348
|
+
formatStopTime(
|
|
349
|
+
createEmptyStoptime(stop.stop_id, trip.trip_id),
|
|
350
|
+
timetable,
|
|
351
|
+
config,
|
|
352
|
+
),
|
|
353
|
+
);
|
|
348
354
|
}
|
|
349
355
|
}
|
|
350
356
|
}
|
|
@@ -353,6 +359,10 @@ export function formatStops(timetable, config) {
|
|
|
353
359
|
timetable.stops = filterHourlyTimes(timetable.stops);
|
|
354
360
|
}
|
|
355
361
|
|
|
362
|
+
for (const stop of timetable.stops) {
|
|
363
|
+
stop.is_timepoint = stop.trips.some((stoptime) => isTimepoint(stoptime));
|
|
364
|
+
}
|
|
365
|
+
|
|
356
366
|
return timetable.stops;
|
|
357
367
|
}
|
|
358
368
|
|
|
@@ -393,15 +403,18 @@ export function formatTripContinuesAs(trip) {
|
|
|
393
403
|
*/
|
|
394
404
|
export function resetStoptimesToMidnight(trip) {
|
|
395
405
|
const offsetSeconds = secondsAfterMidnight(
|
|
396
|
-
first(trip.stoptimes).departure_time
|
|
406
|
+
first(trip.stoptimes).departure_time,
|
|
397
407
|
);
|
|
398
408
|
if (offsetSeconds > 0) {
|
|
399
409
|
for (const stoptime of trip.stoptimes) {
|
|
400
410
|
stoptime.departure_time = toGTFSTime(
|
|
401
|
-
fromGTFSTime(stoptime.departure_time).subtract(
|
|
411
|
+
fromGTFSTime(stoptime.departure_time).subtract(
|
|
412
|
+
offsetSeconds,
|
|
413
|
+
'seconds',
|
|
414
|
+
),
|
|
402
415
|
);
|
|
403
416
|
stoptime.arrival_time = toGTFSTime(
|
|
404
|
-
fromGTFSTime(stoptime.arrival_time).subtract(offsetSeconds, 'seconds')
|
|
417
|
+
fromGTFSTime(stoptime.arrival_time).subtract(offsetSeconds, 'seconds'),
|
|
405
418
|
);
|
|
406
419
|
}
|
|
407
420
|
}
|
|
@@ -418,11 +431,11 @@ export function updateStoptimesByOffset(trip, offsetSeconds) {
|
|
|
418
431
|
delete stoptime._id;
|
|
419
432
|
stoptime.departure_time = updateTimeByOffset(
|
|
420
433
|
stoptime.departure_time,
|
|
421
|
-
offsetSeconds
|
|
434
|
+
offsetSeconds,
|
|
422
435
|
);
|
|
423
436
|
stoptime.arrival_time = updateTimeByOffset(
|
|
424
437
|
stoptime.arrival_time,
|
|
425
|
-
offsetSeconds
|
|
438
|
+
offsetSeconds,
|
|
426
439
|
);
|
|
427
440
|
stoptime.trip_id = trip.trip_id;
|
|
428
441
|
return stoptime;
|
|
@@ -483,9 +496,9 @@ export function formatTimetablePageLabel(timetablePage) {
|
|
|
483
496
|
const routes = uniqBy(
|
|
484
497
|
flatMap(
|
|
485
498
|
timetablePage.consolidatedTimetables,
|
|
486
|
-
(timetable) => timetable.routes
|
|
499
|
+
(timetable) => timetable.routes,
|
|
487
500
|
),
|
|
488
|
-
'route_id'
|
|
501
|
+
'route_id',
|
|
489
502
|
);
|
|
490
503
|
const timetablePageLabel = routes.map((route) => formatRouteName(route));
|
|
491
504
|
|
|
@@ -509,7 +522,7 @@ export function mergeTimetablesWithSameId(timetables) {
|
|
|
509
522
|
const mergedTimetable = omit(timetableGroup[0], 'route_id');
|
|
510
523
|
|
|
511
524
|
mergedTimetable.route_ids = timetableGroup.map(
|
|
512
|
-
(timetable) => timetable.route_id
|
|
525
|
+
(timetable) => timetable.route_id,
|
|
513
526
|
);
|
|
514
527
|
|
|
515
528
|
return mergedTimetable;
|
package/lib/utils.js
CHANGED
|
@@ -86,7 +86,7 @@ const { version } = JSON.parse(
|
|
|
86
86
|
/*
|
|
87
87
|
* Determine if a stoptime is a timepoint.
|
|
88
88
|
*/
|
|
89
|
-
const isTimepoint = (stoptime) => {
|
|
89
|
+
export const isTimepoint = (stoptime) => {
|
|
90
90
|
if (isNullOrEmpty(stoptime.timepoint)) {
|
|
91
91
|
return (
|
|
92
92
|
!isNullOrEmpty(stoptime.arrival_time) &&
|
|
@@ -101,13 +101,15 @@ const isTimepoint = (stoptime) => {
|
|
|
101
101
|
* Find the longest trip (most stops) in a group of trips and return stoptimes.
|
|
102
102
|
*/
|
|
103
103
|
const getLongestTripStoptimes = (trips, config) => {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
104
|
+
const filteredTripStoptimes = trips.map((trip) =>
|
|
105
|
+
trip.stoptimes.filter((stoptime) => {
|
|
106
|
+
// If `showOnlyTimepoint` is true, then filter out all non-timepoints.
|
|
107
|
+
if (config.showOnlyTimepoint === true) {
|
|
108
|
+
return isTimepoint(stoptime);
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
111
113
|
|
|
112
114
|
return maxBy(filteredTripStoptimes, (stoptimes) => size(stoptimes));
|
|
113
115
|
};
|
|
@@ -674,13 +676,15 @@ const getStopOrder = (timetable, config) => {
|
|
|
674
676
|
const stopGraph = [];
|
|
675
677
|
|
|
676
678
|
for (const trip of timetable.orderedTrips) {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
679
|
+
const sortedStopIds = trip.stoptimes
|
|
680
|
+
.filter((stoptime) => {
|
|
681
|
+
// If `showOnlyTimepoint` is true, then filter out all non-timepoints.
|
|
682
|
+
if (config.showOnlyTimepoint === true) {
|
|
683
|
+
return isTimepoint(stoptime);
|
|
684
|
+
}
|
|
685
|
+
return true;
|
|
686
|
+
})
|
|
687
|
+
.map((stoptime) => stoptime.stop_id);
|
|
684
688
|
|
|
685
689
|
for (const [index, stopId] of sortedStopIds.entries()) {
|
|
686
690
|
if (index === sortedStopIds.length - 1) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gtfs-to-html",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Build human readable transit timetables as HTML, PDF or CSV from GTFS",
|
|
6
6
|
"keywords": [
|
|
@@ -37,19 +37,19 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@turf/helpers": "^6.5.0",
|
|
39
39
|
"@turf/simplify": "^6.5.0",
|
|
40
|
-
"archiver": "^6.0.
|
|
40
|
+
"archiver": "^6.0.1",
|
|
41
41
|
"cli-table": "^0.3.11",
|
|
42
42
|
"copy-dir": "^1.3.0",
|
|
43
|
-
"csv-stringify": "^6.4.
|
|
43
|
+
"csv-stringify": "^6.4.4",
|
|
44
44
|
"express": "^4.18.2",
|
|
45
45
|
"gtfs": "^4.5.0",
|
|
46
|
-
"js-beautify": "^1.14.
|
|
46
|
+
"js-beautify": "^1.14.11",
|
|
47
47
|
"lodash-es": "^4.17.21",
|
|
48
48
|
"moment": "^2.29.4",
|
|
49
49
|
"morgan": "^1.10.0",
|
|
50
50
|
"pretty-error": "^4.0.0",
|
|
51
51
|
"pug": "^3.0.2",
|
|
52
|
-
"puppeteer": "^21.
|
|
52
|
+
"puppeteer": "^21.5.0",
|
|
53
53
|
"sanitize-filename": "^1.6.3",
|
|
54
54
|
"sqlstring": "^2.3.3",
|
|
55
55
|
"timer-machine": "^1.1.0",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"husky": "^8.0.3",
|
|
63
|
-
"lint-staged": "^
|
|
64
|
-
"prettier": "^3.0.
|
|
63
|
+
"lint-staged": "^15.0.2",
|
|
64
|
+
"prettier": "^3.0.3"
|
|
65
65
|
},
|
|
66
66
|
"engines": {
|
|
67
67
|
"node": ">= 18.0.0"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
th(scope="row" colspan=`${stop.trips.length + 1}`)= stop.stop_city
|
|
29
29
|
- previousCity = stop.stop_city
|
|
30
30
|
|
|
31
|
-
tr.stop-row(id=`stop_id_${formatHtmlId(stop.stop_id)}` data-stop-id=`${stop.stop_id}`)
|
|
31
|
+
tr.stop-row(id=`stop_id_${formatHtmlId(stop.stop_id)}` data-stop-id=`${stop.stop_id}` data-is-timepoint=`${stop.is_timepoint}`)
|
|
32
32
|
th.stop-name-container(scope="row")
|
|
33
33
|
include timetable_stop_name.pug
|
|
34
34
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
caption.sr-only= `${timetable.timetable_label} | ${timetable.dayList}`
|
|
13
13
|
colgroup
|
|
14
14
|
each stop, idx in timetable.stops
|
|
15
|
-
col(id=`stop_id_${formatHtmlId(stop.stop_id)}` class=`stop-${idx}` data-stop-id=`${stop.stop_id}`)
|
|
15
|
+
col(id=`stop_id_${formatHtmlId(stop.stop_id)}` class=`stop-${idx}` data-stop-id=`${stop.stop_id}` data-is-timepoint=`${stop.is_timepoint}`)
|
|
16
16
|
thead
|
|
17
17
|
tr
|
|
18
18
|
if timetable.has_continues_from_route
|
|
@@ -11,7 +11,7 @@ GTFS-to-HTML Version 2.3.0 adds support for exporting timetables as CSV. Setting
|
|
|
11
11
|
|
|
12
12
|
An example of a CSV timetable:
|
|
13
13
|
|
|
14
|
-
```
|
|
14
|
+
```csv
|
|
15
15
|
,San Francisco Ferry Building,Vallejo Ferry Terminal,Mare Island Ferry Terminal
|
|
16
16
|
Run #1,10:30am,11:30am,
|
|
17
17
|
Run #2,11:30am,12:30pm,
|