Skip to content

Commit 8ae76e0

Browse files
committed
Timetable label functions, version bump
1 parent e9504b1 commit 8ae76e0

15 files changed

+135
-142
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ An example of this file is located in [examples/timetables.txt](examples/timetab
297297
| `friday` | A binary value that indicates whether this timetable should include service on Fridays. Valid options are `0` and `1`. |
298298
| `saturday` | A binary value that indicates whether this timetable should include service on Saturdays. Valid options are `0` and `1`. |
299299
| `sunday` | A binary value that indicates whether this timetable should include service on Sundays. Valid options are `0` and `1`. |
300-
| `timetable_label` | A short text label describing the timetable, for instance "Route 4 Northbound Mon-Fri". Optional, defaults to route name. |
300+
| `timetable_label` | A short text label describing the timetable, for instance "Route 4 Northbound Mon-Fri". Optional, defaults to route name and first and last stops. |
301301
| `service_notes` | Text shown on the timetable about the service represented. Optional. |
302302
| `orientation` | Determines if the top row should be a list of trips or stops. Valid options are `vertical`, `horizontal` or `hourly`. `vertical` shows stops across the top row with each row being a list of stop times for each trip. `horizontal` shows trips across the top row with each row being stop times for a specific stop. `hourly` is for routes that run the same time each hour and will print a simplified schedule showing minutes after the hour for each stop. `horizontal` orientation is best for routes with lots of stops and fewer trips while `vertical` orientation is best for routes with lots of trips and a smaller number of stops. Default is `vertical` |
303303
| `timetable_page_id` | The timetable page to include this timetable on |

bin/gtfs-to-html.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const argv = require('yargs')
2929
.argv;
3030

3131
const gtfsToHtml = require('../');
32+
const logUtils = require('../lib/log-utils');
3233
const utils = require('../lib/utils');
3334

3435
function handleError(err) {
@@ -57,7 +58,7 @@ getConfig()
5758
handleError(err);
5859
})
5960
.then(async config => {
60-
const log = utils.log(config);
61+
const log = logUtils.log(config);
6162

6263
mongoose.Promise = global.Promise;
6364
mongoose.connect(config.mongoUrl, {useMongoClient: true});

lib/formatters.js

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -112,18 +112,3 @@ exports.formatTimetableId = (route, direction) => {
112112
}
113113
return timetableId;
114114
};
115-
116-
exports.formatTimetableLabel = (route, direction) => {
117-
const timetableLabelParts = [];
118-
if (route.route_short_name !== '' && route.route_short_name !== undefined) {
119-
timetableLabelParts.push(route.route_short_name);
120-
}
121-
if (route.route_long_name !== '' && route.route_long_name !== undefined) {
122-
timetableLabelParts.push(route.route_long_name);
123-
}
124-
125-
if (direction) {
126-
timetableLabelParts.push(`to ${direction.trip_headsign}`);
127-
}
128-
return timetableLabelParts.join(' ');
129-
};

lib/gtfs-to-html.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ const Timer = require('timer-machine');
99

1010
const fileUtils = require('./file-utils');
1111
const formatters = require('./formatters');
12+
const logUtils = require('./log-utils');
1213
const utils = require('./utils');
1314

1415
module.exports = config => {
15-
const log = utils.log(config);
16+
const log = logUtils.log(config);
1617

1718
if (!config.agencies || config.agencies.length === 0) {
1819
throw new Error('No agencies defined in `config.json`');
@@ -76,7 +77,7 @@ module.exports = config => {
7677
const html = await utils.generateOverviewHTML(agencyKey, timetablePages, config);
7778
await fs.writeFile(path.join(exportPath, 'index.html'), html);
7879

79-
const logText = await utils.generateLogText(agency, outputStats);
80+
const logText = await logUtils.generateLogText(agency, outputStats);
8081
await fs.writeFile(path.join(exportPath, 'log.txt'), logText.join('\n'));
8182

8283
// Zip output, if specified

lib/log-utils.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const _ = require('lodash');
2+
const gtfs = require('gtfs');
3+
4+
exports.generateLogText = async (agency, outputStats) => {
5+
const results = await gtfs.getFeedInfo({agency_key: agency.agency_key});
6+
const feedVersion = results ? results.feed_version : 'Unknown';
7+
8+
const logText = [
9+
`Feed Version: ${feedVersion}`,
10+
`Date Generated: ${new Date()}`,
11+
`Timetable Page Count ${outputStats.timetablePages}`,
12+
`Timetable Count: ${outputStats.timetables}`,
13+
`Calendar Service ID Count: ${outputStats.calendars}`,
14+
`Route Count: ${outputStats.routes}`,
15+
`Trip Count: ${outputStats.trips}`,
16+
`Stop Count: ${outputStats.stops}`
17+
];
18+
19+
if (agency.url) {
20+
logText.push(`Source: ${agency.url}`);
21+
} else if (agency.path) {
22+
logText.push(`Source: ${agency.path}`);
23+
}
24+
25+
return logText;
26+
};
27+
28+
exports.log = config => {
29+
if (config.verbose === false) {
30+
return _.noop;
31+
}
32+
33+
return (text, overwrite) => {
34+
return process.stdout.write(`${overwrite !== true ? '\n' : ''}${text}`);
35+
};
36+
};

lib/utils.js

Lines changed: 32 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,6 @@ function filterAndSortCalendarDates(calendarDates, startDate, endDate) {
5555
return _.sortBy(filteredDates, 'date');
5656
}
5757

58-
function filterAndSortTimetables(timetables, timetablePageId) {
59-
const selectedTimetables = _.filter(timetables, {timetable_page_id: timetablePageId});
60-
if (!selectedTimetables || selectedTimetables.length === 0) {
61-
throw new Error(`No timetables found for timetable_page_id=${timetablePageId}`);
62-
}
63-
64-
return _.sortBy(selectedTimetables, 'timetable_sequence');
65-
}
66-
6758
function sortTrips(trips) {
6859
// Find the first stop_id that all trips have in common, otherwise use the first stoptime
6960
const longestTrip = _.maxBy(trips, trip => _.size(trip.stoptimes));
@@ -115,6 +106,26 @@ function filterStoptimes(stoptimes) {
115106
});
116107
}
117108

109+
const filterAndSortTimetables = async (timetables, timetablePageId) => {
110+
const selectedTimetables = _.filter(timetables, {timetable_page_id: timetablePageId});
111+
if (!selectedTimetables || selectedTimetables.length === 0) {
112+
throw new Error(`No timetables found for timetable_page_id=${timetablePageId}`);
113+
}
114+
115+
for (const timetable of selectedTimetables) {
116+
const routes = await gtfs.getRoutes({
117+
agency_key: timetable.agency_key,
118+
route_id: timetable.route_id
119+
});
120+
if (!routes || routes.length === 0) {
121+
throw new Error(`No route found for route_id=${timetable.route_id}, timetable_id=${timetable.timetable_id}`);
122+
}
123+
timetable.route = _.first(routes);
124+
}
125+
126+
return _.sortBy(selectedTimetables, 'timetable_sequence');
127+
};
128+
118129
const getSpecialDates = async timetable => {
119130
const results = await gtfs.getCalendarDates({
120131
agency_key: timetable.agency_key,
@@ -215,48 +226,7 @@ const processStops = async timetable => {
215226
}
216227
};
217228

218-
const formatTimetable = async timetable => {
219-
if (!timetable.timetable_label) {
220-
const routes = await gtfs.getRoutes({
221-
agency_key: timetable.agency_key,
222-
route_id: timetable.route_id
223-
});
224-
if (!routes || routes.length === 0) {
225-
throw new Error(`No route found for route_id=${timetable.route_id}, timetable_id=${timetable.timetable_id}`);
226-
}
227-
let direction;
228-
if (timetable.direction_id !== null && timetable.direction_id !== '') {
229-
const directions = await gtfs.getDirectionsByRoute({
230-
agency_key: timetable.agency_key,
231-
route_id: timetable.route_id,
232-
direction_id: timetable.direction_id
233-
});
234-
direction = _.first(directions);
235-
}
236-
timetable.timetable_label = formatters.formatTimetableLabel(routes[0], direction);
237-
}
238-
return timetable;
239-
};
240-
241229
const formatTimetablePage = async timetablePage => {
242-
// If timetable_page_label not set, use first route to name it
243-
if (timetablePage.timetable_page_label === '' || timetablePage.timetable_page_label === undefined) {
244-
const routes = await gtfs.getRoutes({
245-
agency_key: timetablePage.agency_key,
246-
route_id: timetablePage.timetables[0].route_id
247-
});
248-
249-
if (routes.length === 0) {
250-
throw new Error(`No route found for route_id=${timetablePage.timetables[0].route_id}, timetable_id=${timetablePage.timetables[0].timetable_id}`);
251-
}
252-
const route = routes[0];
253-
254-
timetablePage.timetable_page_label = formatters.formatTimetableLabel(route);
255-
}
256-
257-
// Format each timetable in timetable_page
258-
timetablePage.timetables = await Promise.all(timetablePage.timetables.map(timetable => formatTimetable(timetable)));
259-
260230
// Summarize timetables in timetablePage
261231
timetablePage.dayList = formatters.formatDays(getDaysFromCalendars(timetablePage.timetables));
262232
timetablePage.dayLists = _.uniq(timetablePage.timetables.map(timetable => timetable.dayList));
@@ -279,12 +249,13 @@ const convertTimetableToTimetablePage = async timetable => {
279249

280250
const route = routes[0];
281251
const filename = await fileUtils.generateFileName(timetable, route);
282-
const formattedTimetable = await formatTimetable(timetable);
252+
timetable.route = route;
253+
283254
return {
284-
agency_key: formattedTimetable.agency_key,
285-
timetable_page_id: formattedTimetable.timetable_id,
286-
timetable_page_label: formattedTimetable.timetable_label,
287-
timetables: [formattedTimetable],
255+
agency_key: timetable.agency_key,
256+
timetable_page_id: timetable.timetable_id,
257+
timetable_page_label: timetable.timetable_label,
258+
timetables: [timetable],
288259
filename
289260
};
290261
};
@@ -293,11 +264,11 @@ const convertRouteToTimetablePage = async (route, direction) => {
293264
const timetable = {
294265
agency_key: route.agency_key,
295266
timetable_id: formatters.formatTimetableId(route, direction),
296-
timetable_label: formatters.formatTimetableLabel(route, direction),
297267
route_id: route.route_id,
298268
direction_id: direction.direction_id,
299269
start_date: parseInt(formatters.toGTFSDate(moment()), 10),
300-
end_date: parseInt(formatters.toGTFSDate(moment().add(1, 'week')), 10)
270+
end_date: parseInt(formatters.toGTFSDate(moment().add(1, 'week')), 10),
271+
route: route
301272
};
302273

303274
const tripQuery = {
@@ -581,30 +552,6 @@ exports.generateOverviewHTML = async (agencyKey, timetablePages, config) => {
581552
return html;
582553
};
583554

584-
exports.generateLogText = async (agency, outputStats) => {
585-
const results = await gtfs.getFeedInfo({agency_key: agency.agency_key});
586-
const feedVersion = results ? results.feed_version : 'Unknown';
587-
588-
const logText = [
589-
`Feed Version: ${feedVersion}`,
590-
`Date Generated: ${new Date()}`,
591-
`Timetable Page Count ${outputStats.timetablePages}`,
592-
`Timetable Count: ${outputStats.timetables}`,
593-
`Calendar Service ID Count: ${outputStats.calendars}`,
594-
`Route Count: ${outputStats.routes}`,
595-
`Trip Count: ${outputStats.trips}`,
596-
`Stop Count: ${outputStats.stops}`
597-
];
598-
599-
if (agency.url) {
600-
logText.push(`Source: ${agency.url}`);
601-
} else if (agency.path) {
602-
logText.push(`Source: ${agency.path}`);
603-
}
604-
605-
return logText;
606-
};
607-
608555
exports.getTimetablePage = async (agencyKey, timetablePageId) => {
609556
let timetables = await gtfs.getTimetables({agency_key: agencyKey});
610557
timetables = timetables.map(timetable => timetable.toObject());
@@ -654,7 +601,7 @@ exports.getTimetablePage = async (agencyKey, timetablePageId) => {
654601
timetablePages = [await convertTimetableToTimetablePage(timetable)];
655602
} else {
656603
for (const timetablePage of timetablePages) {
657-
timetablePage.timetables = filterAndSortTimetables(timetables, timetablePage.timetable_page_id);
604+
timetablePage.timetables = await filterAndSortTimetables(timetables, timetablePage.timetable_page_id);
658605
}
659606
}
660607

@@ -679,21 +626,11 @@ exports.getTimetablePages = async agencyKey => {
679626
}));
680627
} else {
681628
// Otherwise, use timetable pages provided
682-
timetablePages = timetablePages.map(timetablePage => {
683-
timetablePage.timetables = filterAndSortTimetables(timetables, timetablePage.timetable_page_id);
629+
timetablePages = await Promise.all(timetablePages.map(async timetablePage => {
630+
timetablePage.timetables = await filterAndSortTimetables(timetables, timetablePage.timetable_page_id);
684631
return timetablePage;
685-
});
632+
}));
686633
}
687634

688635
return Promise.all(timetablePages.map(timetablePage => formatTimetablePage(timetablePage)));
689636
};
690-
691-
exports.log = config => {
692-
if (config.verbose === false) {
693-
return _.noop;
694-
}
695-
696-
return (text, overwrite) => {
697-
return process.stdout.write(`${overwrite !== true ? '\n' : ''}${text}`);
698-
};
699-
};

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "gtfs-to-html",
33
"description": "Build transit timetables in HTML from a GTFS file",
4-
"version": "0.11.1",
4+
"version": "0.11.2",
55
"keywords": [
66
"transit",
77
"gtfs",
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
- function getTimetableLabel(timetable) {
2+
- if (timetable.timetable_label !== '' && timetable.timetable_label !== undefined) {
3+
- return timetable.timetable_label;
4+
- }
5+
- var timetableLabel = `Route `;
6+
- if (timetable.route.route_short_name !== '' && timetable.route.route_short_name !== undefined) {
7+
- timetableLabel += timetable.route.route_short_name;
8+
- } else if (timetable.route.route_long_name !== '' && timetable.route.route_long_name !== undefined) {
9+
- timetableLabel += timetable.route.route_long_name;
10+
- }
11+
- timetableLabel += ` - ${timetable.stops[0].stop_name} to ${timetable.stops[timetable.stops.length - 1].stop_name}`;
12+
- return timetableLabel;
13+
- }
14+
15+
- function getTimetablePageLabel(timetablePage) {
16+
- if (timetablePage.timetable_page_label !== '' && timetablePage.timetable_page_label !== undefined) {
17+
- return timetablePage.timetable_page_label;
18+
- }
19+
- // Get route info from first timetable
20+
- var route = timetablePage.timetables[0].route;
21+
- var timetableLabel = 'Route ';
22+
- if (route.route_short_name !== '' && route.route_short_name !== undefined) {
23+
- timetableLabel += `${route.route_short_name} - `;
24+
- }
25+
- if (route.route_long_name !== '' && route.route_long_name !== undefined) {
26+
- timetableLabel += route.route_long_name;
27+
- }
28+
- return timetableLabel;
29+
- }
30+
31+
- function getTimetableSummary(timetable) {
32+
- var summary = `This table shows schedules for a selection of key stops on the route for ${getTimetableLabel(timetable)} ${timetable.dayList}.`;
33+
- if (timetable.orientation = 'vertical') {
34+
- summary += ' Stops and their schedule times are listed in the columns.';
35+
- } else if (timetable.orientation = 'horizontal') {
36+
- summary += ' Schedule times are listed in rows, starting with the stop name in the first cell of the row.';
37+
- } else if (timetable.orientation = 'hourly') {
38+
- summary += ' Schedule times are listed in rows, starting with the stop name in the first cell of the row and the minutes after the hour in the second row.';
39+
- }
40+
- return summary;
41+
- }
42+
43+
- function formatStopName(stop) {
44+
- return `${stop.stop_name}${stop.type === 'arrival' ? ' (Arrival)' : stop.type === 'departure' ? ' (Departure)' : ''}`;
45+
- }
46+
47+
- function formatTripName(trip, idx, showDayList) {
48+
- return `${trip.trip_short_name || `Run #${idx + 1}`}${showDayList ? ` ${trip.dayList}` : ''}`;
49+
- }

views/timetable/overview.pug

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
include formatting_functions.pug
2+
13
.row
24
.col-md-12
35
h1.overview-label= `${agency.agency_name} Routes`
@@ -13,7 +15,7 @@
1315
h4.list-group-item-heading
1416
if timetablePage.routeColors.length === 1
1517
.route-color-swatch(style=`background-color: #${timetablePage.routeColors[0]}`)
16-
span= timetablePage.timetable_page_label
18+
span= getTimetablePageLabel(timetablePage)
1719
.list-group-item-text= timetablePage.dayList
1820
if config.showMap
1921
.col-md-9

views/timetable/timetable_horizontal.pug

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.table-container
22
table.table.table-striped.table-horizontal(summary= getTimetableSummary(timetable))
3-
caption.sr-only= `${timetable.timetable_label} | ${timetable.dayList}`
3+
caption.sr-only= `${getTimetableLabel(timetable)} | ${timetable.dayList}`
44
colgroup
55
col
66
each trip, idx in timetable.orderedTrips

views/timetable/timetable_hourly.pug

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
.table-container
77
table.table.table-striped.table-hourly(summary= getTimetableSummary(timetable))
8-
caption.sr-only= `${timetable.timetable_label} | ${timetable.dayList}`
8+
caption.sr-only= `${getTimetableLabel(timetable)} | ${timetable.dayList}`
99
thead
1010
tr
1111
th.stop-header(scope="col") Stop

0 commit comments

Comments
 (0)