gtfs-to-html 2.7.2 → 2.8.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.
- package/package.json +21 -8
- package/.eslintrc.json +0 -28
- package/.husky/pre-commit +0 -4
- package/CHANGELOG.md +0 -1018
- package/app/index.js +0 -138
- package/bin/gtfs-to-html.js +0 -48
- package/config-sample.json +0 -59
- package/docker/Dockerfile +0 -14
- package/docker/README.md +0 -5
- package/docker/docker-compose.yml +0 -10
- package/examples/stop_attributes.txt +0 -6
- package/examples/timetable_notes.txt +0 -8
- package/examples/timetable_notes_references.txt +0 -8
- package/examples/timetable_pages.txt +0 -3
- package/examples/timetable_stop_order.txt +0 -16
- package/examples/timetables.txt +0 -9
- package/index.js +0 -1
- package/lib/file-utils.js +0 -202
- package/lib/formatters.js +0 -518
- package/lib/geojson-utils.js +0 -96
- package/lib/gtfs-to-html.js +0 -214
- package/lib/log-utils.js +0 -215
- package/lib/template-functions.js +0 -192
- package/lib/time-utils.js +0 -90
- package/lib/utils.js +0 -1702
- package/views/default/css/overview_styles.css +0 -197
- package/views/default/css/timetable_pdf_styles.css +0 -7
- package/views/default/css/timetable_styles.css +0 -447
- package/views/default/formatting_functions.pug +0 -113
- package/views/default/js/system-map.js +0 -594
- package/views/default/js/timetable-map.js +0 -358
- package/views/default/js/timetable-menu.js +0 -63
- package/views/default/layout.pug +0 -11
- package/views/default/overview.pug +0 -27
- package/views/default/overview_full.pug +0 -16
- package/views/default/timetable_continuation_as.pug +0 -7
- package/views/default/timetable_continuation_from.pug +0 -7
- package/views/default/timetable_horizontal.pug +0 -42
- package/views/default/timetable_hourly.pug +0 -30
- package/views/default/timetable_menu.pug +0 -48
- package/views/default/timetable_note_symbol.pug +0 -5
- package/views/default/timetable_stop_name.pug +0 -13
- package/views/default/timetable_stoptime.pug +0 -17
- package/views/default/timetable_vertical.pug +0 -67
- package/views/default/timetablepage.pug +0 -66
- package/views/default/timetablepage_full.pug +0 -22
- package/www/README.md +0 -33
- package/www/babel.config.js +0 -3
- package/www/blog/2020-07-07-New-Documentation.md +0 -12
- package/www/blog/2020-08-20-Version-1.0.0.md +0 -29
- package/www/blog/2021-11-06-CSV-Export.md +0 -26
- package/www/docs/additional-files.md +0 -24
- package/www/docs/configuration.md +0 -568
- package/www/docs/current-usage.md +0 -48
- package/www/docs/custom-templates.md +0 -13
- package/www/docs/introduction.md +0 -39
- package/www/docs/logging-sql-queries.md +0 -12
- package/www/docs/previewing-html-output.md +0 -24
- package/www/docs/processing-large-gtfs.md +0 -10
- package/www/docs/quick-start.md +0 -136
- package/www/docs/related-libraries.md +0 -54
- package/www/docs/reviewing-changes.md +0 -29
- package/www/docs/stop-attributes.md +0 -30
- package/www/docs/support.md +0 -12
- package/www/docs/timetable-notes-references.md +0 -44
- package/www/docs/timetable-notes.md +0 -33
- package/www/docs/timetable-pages.md +0 -37
- package/www/docs/timetable-stop-order.md +0 -63
- package/www/docs/timetables.md +0 -64
- package/www/docusaurus.config.js +0 -104
- package/www/package.json +0 -21
- package/www/sidebars.js +0 -10
- package/www/src/css/custom.css +0 -25
- package/www/src/pages/index.js +0 -270
- package/www/src/pages/styles.module.css +0 -53
- package/www/static/.nojekyll +0 -0
- package/www/static/img/favicon.ico +0 -0
- package/www/static/img/gtfs-to-html-logo.svg +0 -18
- package/www/static/img/overview-example.jpg +0 -0
- package/www/static/img/timetable-example.jpg +0 -0
- package/www/static/img/undraw_happy_music.svg +0 -1
- package/www/static/img/undraw_proud_coder.svg +0 -1
- package/www/static/img/undraw_spreadsheets.svg +0 -1
- package/www/yarn.lock +0 -8351
package/lib/formatters.js
DELETED
|
@@ -1,518 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
clone,
|
|
3
|
-
find,
|
|
4
|
-
first,
|
|
5
|
-
flatMap,
|
|
6
|
-
groupBy,
|
|
7
|
-
last,
|
|
8
|
-
omit,
|
|
9
|
-
sortBy,
|
|
10
|
-
uniqBy,
|
|
11
|
-
zipObject,
|
|
12
|
-
} from 'lodash-es';
|
|
13
|
-
import moment from 'moment';
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
fromGTFSTime,
|
|
17
|
-
minutesAfterMidnight,
|
|
18
|
-
calendarToCalendarCode,
|
|
19
|
-
secondsAfterMidnight,
|
|
20
|
-
toGTFSTime,
|
|
21
|
-
updateTimeByOffset,
|
|
22
|
-
} from './time-utils.js';
|
|
23
|
-
import { isTimepoint } from './utils.js';
|
|
24
|
-
|
|
25
|
-
/*
|
|
26
|
-
* Replace all instances in a string with items from an object.
|
|
27
|
-
*/
|
|
28
|
-
function replaceAll(string, mapObject) {
|
|
29
|
-
const re = new RegExp(Object.keys(mapObject).join('|'), 'gi');
|
|
30
|
-
return string.replace(re, (matched) => mapObject[matched]);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/*
|
|
34
|
-
* Determine if value is null or empty string.
|
|
35
|
-
*/
|
|
36
|
-
export function isNullOrEmpty(value) {
|
|
37
|
-
return value === null || value === '';
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/*
|
|
41
|
-
* Format a date for display.
|
|
42
|
-
*/
|
|
43
|
-
export function formatDate(date, dateFormat) {
|
|
44
|
-
if (date.holiday_name) {
|
|
45
|
-
return date.holiday_name;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return moment(date.date, 'YYYYMMDD').format(dateFormat);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/*
|
|
52
|
-
* Convert time to seconds.
|
|
53
|
-
*/
|
|
54
|
-
export function timeToSeconds(time) {
|
|
55
|
-
return moment.duration(time).asSeconds();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/*
|
|
59
|
-
* Format a single stoptime.
|
|
60
|
-
*/
|
|
61
|
-
/* eslint-disable complexity */
|
|
62
|
-
function formatStopTime(stoptime, timetable, config) {
|
|
63
|
-
stoptime.classes = [];
|
|
64
|
-
|
|
65
|
-
if (stoptime.type === 'arrival' && stoptime.arrival_time) {
|
|
66
|
-
const arrivalTime = fromGTFSTime(stoptime.arrival_time);
|
|
67
|
-
stoptime.formatted_time = arrivalTime.format(config.timeFormat);
|
|
68
|
-
stoptime.classes.push(arrivalTime.format('a'));
|
|
69
|
-
} else if (stoptime.type === 'departure' && stoptime.departure_time) {
|
|
70
|
-
const departureTime = fromGTFSTime(stoptime.departure_time);
|
|
71
|
-
stoptime.formatted_time = departureTime.format(config.timeFormat);
|
|
72
|
-
stoptime.classes.push(departureTime.format('a'));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (stoptime.pickup_type === 1) {
|
|
76
|
-
stoptime.noPickup = true;
|
|
77
|
-
stoptime.classes.push('no-pickup');
|
|
78
|
-
if (timetable.noPickupSymbol !== null) {
|
|
79
|
-
timetable.noPickupSymbolUsed = true;
|
|
80
|
-
}
|
|
81
|
-
} else if (stoptime.pickup_type === 2 || stoptime.pickup_type === 3) {
|
|
82
|
-
stoptime.requestPickup = true;
|
|
83
|
-
stoptime.classes.push('request-pickup');
|
|
84
|
-
if (timetable.requestPickupSymbol !== null) {
|
|
85
|
-
timetable.requestPickupSymbolUsed = true;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (stoptime.drop_off_type === 1) {
|
|
90
|
-
stoptime.noDropoff = true;
|
|
91
|
-
stoptime.classes.push('no-drop-off');
|
|
92
|
-
if (timetable.noDropoffSymbol !== null) {
|
|
93
|
-
timetable.noDropoffSymbolUsed = true;
|
|
94
|
-
}
|
|
95
|
-
} else if (stoptime.drop_off_type === 2 || stoptime.drop_off_type === 3) {
|
|
96
|
-
stoptime.requestDropoff = true;
|
|
97
|
-
stoptime.classes.push('request-drop-off');
|
|
98
|
-
if (timetable.requestDropoffSymbol !== null) {
|
|
99
|
-
timetable.requestDropoffSymbolUsed = true;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (stoptime.timepoint === 0 || stoptime.departure_time === '') {
|
|
104
|
-
stoptime.interpolated = true;
|
|
105
|
-
stoptime.classes.push('interpolated');
|
|
106
|
-
if (timetable.interpolatedStopSymbol !== null) {
|
|
107
|
-
timetable.interpolatedStopSymbolUsed = true;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (stoptime.timepoint === null && stoptime.departure_time === null) {
|
|
112
|
-
stoptime.skipped = true;
|
|
113
|
-
stoptime.classes.push('skipped');
|
|
114
|
-
if (timetable.noServiceSymbol !== null) {
|
|
115
|
-
timetable.noServiceSymbolUsed = true;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (stoptime.timepoint === 1) {
|
|
120
|
-
stoptime.classes.push('timepoint');
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return stoptime;
|
|
124
|
-
}
|
|
125
|
-
/* eslint-enable complexity */
|
|
126
|
-
|
|
127
|
-
/*
|
|
128
|
-
* Find hourly times for each stop for hourly schedules.
|
|
129
|
-
*/
|
|
130
|
-
function filterHourlyTimes(stops) {
|
|
131
|
-
// Find all stoptimes within the first 60 minutes.
|
|
132
|
-
const firstStopTimes = [];
|
|
133
|
-
const firstTripMinutes = minutesAfterMidnight(stops[0].trips[0].arrival_time);
|
|
134
|
-
for (const trip of stops[0].trips) {
|
|
135
|
-
const minutes = minutesAfterMidnight(trip.arrival_time);
|
|
136
|
-
if (minutes >= firstTripMinutes + 60) {
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
firstStopTimes.push(fromGTFSTime(trip.arrival_time));
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Sort stoptimes by minutes for first stop.
|
|
144
|
-
const firstStopTimesAndIndex = firstStopTimes.map((time, idx) => ({
|
|
145
|
-
idx,
|
|
146
|
-
time,
|
|
147
|
-
}));
|
|
148
|
-
const sortedFirstStopTimesAndIndex = sortBy(firstStopTimesAndIndex, (item) =>
|
|
149
|
-
Number.parseInt(item.time.format('m'), 10),
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
// Filter and arrange stoptimes for all stops based on sort.
|
|
153
|
-
return stops.map((stop) => {
|
|
154
|
-
stop.hourlyTimes = sortedFirstStopTimesAndIndex.map((item) =>
|
|
155
|
-
fromGTFSTime(stop.trips[item.idx].arrival_time).format(':mm'),
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
return stop;
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/*
|
|
163
|
-
* Format a calendar's list of days for display using abbreviated day names.
|
|
164
|
-
*/
|
|
165
|
-
const days = [
|
|
166
|
-
'monday',
|
|
167
|
-
'tuesday',
|
|
168
|
-
'wednesday',
|
|
169
|
-
'thursday',
|
|
170
|
-
'friday',
|
|
171
|
-
'saturday',
|
|
172
|
-
'sunday',
|
|
173
|
-
];
|
|
174
|
-
export function formatDays(calendar, config) {
|
|
175
|
-
const daysShort = config.daysShortStrings;
|
|
176
|
-
let daysInARow = 0;
|
|
177
|
-
let dayString = '';
|
|
178
|
-
|
|
179
|
-
if (!calendar) {
|
|
180
|
-
return '';
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
for (let i = 0; i <= 6; i += 1) {
|
|
184
|
-
const currentDayOperating = calendar[days[i]] === 1;
|
|
185
|
-
const previousDayOperating = i > 0 ? calendar[days[i - 1]] === 1 : false;
|
|
186
|
-
const nextDayOperating = i < 6 ? calendar[days[i + 1]] === 1 : false;
|
|
187
|
-
|
|
188
|
-
if (currentDayOperating) {
|
|
189
|
-
if (dayString.length > 0) {
|
|
190
|
-
if (!previousDayOperating) {
|
|
191
|
-
dayString += ', ';
|
|
192
|
-
} else if (daysInARow === 1) {
|
|
193
|
-
dayString += '-';
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
daysInARow += 1;
|
|
198
|
-
|
|
199
|
-
if (
|
|
200
|
-
dayString.length === 0 ||
|
|
201
|
-
!nextDayOperating ||
|
|
202
|
-
i === 6 ||
|
|
203
|
-
!previousDayOperating
|
|
204
|
-
) {
|
|
205
|
-
dayString += daysShort[i];
|
|
206
|
-
}
|
|
207
|
-
} else {
|
|
208
|
-
daysInARow = 0;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (dayString.length === 0) {
|
|
213
|
-
dayString = 'No regular service days';
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return dayString;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/*
|
|
220
|
-
* Format a list of days for display using full names of days.
|
|
221
|
-
*/
|
|
222
|
-
export function formatDaysLong(dayList, config) {
|
|
223
|
-
const mapObject = zipObject(config.daysShortStrings, config.daysStrings);
|
|
224
|
-
|
|
225
|
-
return replaceAll(dayList, mapObject);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/*
|
|
229
|
-
* Format a trip.
|
|
230
|
-
*/
|
|
231
|
-
export function formatTrip(trip, timetable, calendars, config) {
|
|
232
|
-
trip.calendar = find(calendars, {
|
|
233
|
-
service_id: trip.service_id,
|
|
234
|
-
});
|
|
235
|
-
trip.dayList = formatDays(trip.calendar, config);
|
|
236
|
-
trip.dayListLong = formatDaysLong(trip.dayList, config);
|
|
237
|
-
|
|
238
|
-
if (timetable.routes.length === 1) {
|
|
239
|
-
trip.route_short_name = timetable.routes[0].route_short_name;
|
|
240
|
-
} else {
|
|
241
|
-
const route = timetable.routes.find(
|
|
242
|
-
(route) => route.route_id === trip.route_id,
|
|
243
|
-
);
|
|
244
|
-
trip.route_short_name = route.route_short_name;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return trip;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/*
|
|
251
|
-
* Format a frequency.
|
|
252
|
-
*/
|
|
253
|
-
export function formatFrequency(frequency, config) {
|
|
254
|
-
const startTime = fromGTFSTime(frequency.start_time);
|
|
255
|
-
const endTime = fromGTFSTime(frequency.end_time);
|
|
256
|
-
const headway = moment.duration(frequency.headway_secs, 'seconds');
|
|
257
|
-
frequency.start_formatted_time = startTime.format(config.timeFormat);
|
|
258
|
-
frequency.end_formatted_time = endTime.format(config.timeFormat);
|
|
259
|
-
frequency.headway_min = Math.round(headway.asMinutes());
|
|
260
|
-
return frequency;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/*
|
|
264
|
-
* Generate a timetable id.
|
|
265
|
-
*/
|
|
266
|
-
export function formatTimetableId(timetable) {
|
|
267
|
-
let timetableId = `${timetable.route_ids.join('_')}|${calendarToCalendarCode(
|
|
268
|
-
timetable,
|
|
269
|
-
)}`;
|
|
270
|
-
if (!isNullOrEmpty(timetable.direction_id)) {
|
|
271
|
-
timetableId += `|${timetable.direction_id}`;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return timetableId;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function createEmptyStoptime(stopId, tripId) {
|
|
278
|
-
return {
|
|
279
|
-
id: null,
|
|
280
|
-
trip_id: tripId,
|
|
281
|
-
arrival_time: null,
|
|
282
|
-
departure_time: null,
|
|
283
|
-
stop_id: stopId,
|
|
284
|
-
stop_sequence: null,
|
|
285
|
-
stop_headsign: null,
|
|
286
|
-
pickup_type: null,
|
|
287
|
-
drop_off_type: null,
|
|
288
|
-
continuous_pickup: null,
|
|
289
|
-
continuous_drop_off: null,
|
|
290
|
-
shape_dist_traveled: null,
|
|
291
|
-
timepoint: null,
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/*
|
|
296
|
-
* Format stops.
|
|
297
|
-
*/
|
|
298
|
-
export function formatStops(timetable, config) {
|
|
299
|
-
for (const trip of timetable.orderedTrips) {
|
|
300
|
-
let stopIndex = -1;
|
|
301
|
-
for (const [idx, stoptime] of trip.stoptimes.entries()) {
|
|
302
|
-
// Find a stop for the matching `stop_id` greater than the last `stopIndex`.
|
|
303
|
-
const stop = find(timetable.stops, (st, idx) => {
|
|
304
|
-
if (st.stop_id === stoptime.stop_id && idx > stopIndex) {
|
|
305
|
-
stopIndex = idx;
|
|
306
|
-
return true;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return false;
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
if (!stop) {
|
|
313
|
-
continue;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// If first stoptime of the trip, remove drop_off_type information
|
|
317
|
-
if (idx === 0) {
|
|
318
|
-
stoptime.drop_off_type = 0;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// If last stoptime of the trip, remove pickup_type information
|
|
322
|
-
if (idx === trip.stoptimes.length - 1) {
|
|
323
|
-
stoptime.pickup_type = 0;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// If showing arrival and departure times as separate columns/rows, add
|
|
327
|
-
// trip to the departure stop, unless it is the last stoptime of the trip.
|
|
328
|
-
if (stop.type === 'arrival' && idx < trip.stoptimes.length - 1) {
|
|
329
|
-
const departureStoptime = clone(stoptime);
|
|
330
|
-
departureStoptime.type = 'departure';
|
|
331
|
-
timetable.stops[stopIndex + 1].trips.push(
|
|
332
|
-
formatStopTime(departureStoptime, timetable, config),
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// Show times if it is an arrival stop and is the first stoptime for the trip.
|
|
337
|
-
if (!(stop.type === 'arrival' && idx === 0)) {
|
|
338
|
-
stoptime.type = 'arrival';
|
|
339
|
-
stop.trips.push(formatStopTime(stoptime, timetable, config));
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Fill in any missing stoptimes for this trip.
|
|
344
|
-
for (const stop of timetable.stops) {
|
|
345
|
-
const lastStopTime = last(stop.trips);
|
|
346
|
-
if (!lastStopTime || lastStopTime.trip_id !== trip.trip_id) {
|
|
347
|
-
stop.trips.push(
|
|
348
|
-
formatStopTime(
|
|
349
|
-
createEmptyStoptime(stop.stop_id, trip.trip_id),
|
|
350
|
-
timetable,
|
|
351
|
-
config,
|
|
352
|
-
),
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
if (timetable.orientation === 'hourly') {
|
|
359
|
-
timetable.stops = filterHourlyTimes(timetable.stops);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
for (const stop of timetable.stops) {
|
|
363
|
-
stop.is_timepoint = stop.trips.some((stoptime) => isTimepoint(stoptime));
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
return timetable.stops;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/*
|
|
370
|
-
* Formats a stop name.
|
|
371
|
-
*/
|
|
372
|
-
export function formatStopName(stop) {
|
|
373
|
-
return `${stop.stop_name}${
|
|
374
|
-
stop.type === 'arrival'
|
|
375
|
-
? ' (Arrival)'
|
|
376
|
-
: stop.type === 'departure'
|
|
377
|
-
? ' (Departure)'
|
|
378
|
-
: ''
|
|
379
|
-
}`;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
/*
|
|
383
|
-
* Formats trip "Continues from".
|
|
384
|
-
*/
|
|
385
|
-
export function formatTripContinuesFrom(trip) {
|
|
386
|
-
return trip.continues_from_route
|
|
387
|
-
? trip.continues_from_route.route.route_short_name
|
|
388
|
-
: '';
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
/*
|
|
392
|
-
* Formats trip "Continues as".
|
|
393
|
-
*/
|
|
394
|
-
export function formatTripContinuesAs(trip) {
|
|
395
|
-
return trip.continues_as_route
|
|
396
|
-
? trip.continues_as_route.route.route_short_name
|
|
397
|
-
: '';
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/*
|
|
401
|
-
* Change all stoptimes of a trip so the first trip starts at midnight. Useful
|
|
402
|
-
* for hourly schedules.
|
|
403
|
-
*/
|
|
404
|
-
export function resetStoptimesToMidnight(trip) {
|
|
405
|
-
const offsetSeconds = secondsAfterMidnight(
|
|
406
|
-
first(trip.stoptimes).departure_time,
|
|
407
|
-
);
|
|
408
|
-
if (offsetSeconds > 0) {
|
|
409
|
-
for (const stoptime of trip.stoptimes) {
|
|
410
|
-
stoptime.departure_time = toGTFSTime(
|
|
411
|
-
fromGTFSTime(stoptime.departure_time).subtract(
|
|
412
|
-
offsetSeconds,
|
|
413
|
-
'seconds',
|
|
414
|
-
),
|
|
415
|
-
);
|
|
416
|
-
stoptime.arrival_time = toGTFSTime(
|
|
417
|
-
fromGTFSTime(stoptime.arrival_time).subtract(offsetSeconds, 'seconds'),
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
return trip;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/*
|
|
426
|
-
* Change all stoptimes of a trip by a specified number of seconds. Useful for
|
|
427
|
-
* hourly schedules.
|
|
428
|
-
*/
|
|
429
|
-
export function updateStoptimesByOffset(trip, offsetSeconds) {
|
|
430
|
-
return trip.stoptimes.map((stoptime) => {
|
|
431
|
-
delete stoptime._id;
|
|
432
|
-
stoptime.departure_time = updateTimeByOffset(
|
|
433
|
-
stoptime.departure_time,
|
|
434
|
-
offsetSeconds,
|
|
435
|
-
);
|
|
436
|
-
stoptime.arrival_time = updateTimeByOffset(
|
|
437
|
-
stoptime.arrival_time,
|
|
438
|
-
offsetSeconds,
|
|
439
|
-
);
|
|
440
|
-
stoptime.trip_id = trip.trip_id;
|
|
441
|
-
return stoptime;
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
/*
|
|
446
|
-
* Format a route color as a hex color.
|
|
447
|
-
*/
|
|
448
|
-
export function formatRouteColor(route) {
|
|
449
|
-
// Defaults to #000000 (black) if no color is provided.
|
|
450
|
-
return route.route_color ? `#${route.route_color}` : '#000000';
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
/*
|
|
454
|
-
* Format a route text color as a hex color.
|
|
455
|
-
*/
|
|
456
|
-
export function formatRouteTextColor(route) {
|
|
457
|
-
// Defaults to #FFFFFF (white) if no color is provided.
|
|
458
|
-
return route.route_text_color ? `#${route.route_text_color}` : '#FFFFFF';
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/*
|
|
462
|
-
* Format a label for a timetable.
|
|
463
|
-
*/
|
|
464
|
-
export function formatTimetableLabel(timetable) {
|
|
465
|
-
if (!isNullOrEmpty(timetable.timetable_label)) {
|
|
466
|
-
return timetable.timetable_label;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
let timetableLabel = '';
|
|
470
|
-
|
|
471
|
-
if (timetable.routes && timetable.routes.length > 0) {
|
|
472
|
-
timetableLabel += 'Route ';
|
|
473
|
-
if (!isNullOrEmpty(timetable.routes[0].route_short_name)) {
|
|
474
|
-
timetableLabel += timetable.routes[0].route_short_name;
|
|
475
|
-
} else if (!isNullOrEmpty(timetable.routes[0].route_long_name)) {
|
|
476
|
-
timetableLabel += timetable.routes[0].route_long_name;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
if (timetable.stops && timetable.stops.length > 0) {
|
|
481
|
-
const firstStop = timetable.stops[0].stop_name;
|
|
482
|
-
const lastStop = timetable.stops[timetable.stops.length - 1].stop_name;
|
|
483
|
-
if (firstStop === lastStop) {
|
|
484
|
-
if (!isNullOrEmpty(timetable.routes[0].route_long_name)) {
|
|
485
|
-
timetableLabel += ` - ${timetable.routes[0].route_long_name}`;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
timetableLabel += ' - Loop';
|
|
489
|
-
} else {
|
|
490
|
-
timetableLabel += ` - ${firstStop} to ${lastStop}`;
|
|
491
|
-
}
|
|
492
|
-
} else if (timetable.direction_name !== null) {
|
|
493
|
-
timetableLabel += ` to ${timetable.direction_name}`;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
return timetableLabel;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
/*
|
|
500
|
-
* Merge timetables with same `timetable_id`.
|
|
501
|
-
*/
|
|
502
|
-
export function mergeTimetablesWithSameId(timetables) {
|
|
503
|
-
if (timetables.length === 0) {
|
|
504
|
-
return [];
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
const mergedTimetables = groupBy(timetables, 'timetable_id');
|
|
508
|
-
|
|
509
|
-
return Object.values(mergedTimetables).map((timetableGroup) => {
|
|
510
|
-
const mergedTimetable = omit(timetableGroup[0], 'route_id');
|
|
511
|
-
|
|
512
|
-
mergedTimetable.route_ids = timetableGroup.map(
|
|
513
|
-
(timetable) => timetable.route_id,
|
|
514
|
-
);
|
|
515
|
-
|
|
516
|
-
return mergedTimetable;
|
|
517
|
-
});
|
|
518
|
-
}
|
package/lib/geojson-utils.js
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { getShapesAsGeoJSON, getStopsAsGeoJSON } from 'gtfs';
|
|
2
|
-
import { flatMap } from 'lodash-es';
|
|
3
|
-
import simplify from '@turf/simplify';
|
|
4
|
-
import { featureCollection, round } from '@turf/helpers';
|
|
5
|
-
|
|
6
|
-
/*
|
|
7
|
-
* Merge any number of geojson objects into one. Only works for `FeatureCollection`.
|
|
8
|
-
*/
|
|
9
|
-
const mergeGeojson = (...geojsons) =>
|
|
10
|
-
featureCollection(flatMap(geojsons, (geojson) => geojson.features));
|
|
11
|
-
|
|
12
|
-
/*
|
|
13
|
-
* Truncate a geojson coordinates to a specific number of decimal places.
|
|
14
|
-
*/
|
|
15
|
-
const truncateGeoJSONDecimals = (geojson, config) => {
|
|
16
|
-
for (const feature of geojson.features) {
|
|
17
|
-
if (feature.geometry.coordinates) {
|
|
18
|
-
if (feature.geometry.type.toLowerCase() === 'point') {
|
|
19
|
-
feature.geometry.coordinates = feature.geometry.coordinates.map(
|
|
20
|
-
(number) => round(number, config.coordinatePrecision),
|
|
21
|
-
);
|
|
22
|
-
} else if (feature.geometry.type.toLowerCase() === 'linestring') {
|
|
23
|
-
feature.geometry.coordinates = feature.geometry.coordinates.map(
|
|
24
|
-
(coordinate) =>
|
|
25
|
-
coordinate.map((number) =>
|
|
26
|
-
round(number, config.coordinatePrecision),
|
|
27
|
-
),
|
|
28
|
-
);
|
|
29
|
-
} else if (feature.geometry.type.toLowerCase() === 'multilinestring') {
|
|
30
|
-
feature.geometry.coordinates = feature.geometry.coordinates.map(
|
|
31
|
-
(linestring) =>
|
|
32
|
-
linestring.map((coordinate) =>
|
|
33
|
-
coordinate.map((number) =>
|
|
34
|
-
round(number, config.coordinatePrecision),
|
|
35
|
-
),
|
|
36
|
-
),
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return geojson;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
/*
|
|
46
|
-
* Simplify geojson to a specific tolerance
|
|
47
|
-
*/
|
|
48
|
-
const simplifyGeoJSON = (geojson, config) => {
|
|
49
|
-
try {
|
|
50
|
-
const simplifiedGeojson = simplify(geojson, {
|
|
51
|
-
tolerance: 1 / 10 ** config.coordinatePrecision,
|
|
52
|
-
highQuality: true,
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
return truncateGeoJSONDecimals(simplifiedGeojson, config);
|
|
56
|
-
} catch {
|
|
57
|
-
config.logWarning('Unable to simplify geojson');
|
|
58
|
-
|
|
59
|
-
return truncateGeoJSONDecimals(geojson, config);
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
/*
|
|
64
|
-
* Get the geoJSON for a timetable.
|
|
65
|
-
*/
|
|
66
|
-
export function getTimetableGeoJSON(timetable, config) {
|
|
67
|
-
const shapesGeojsons = timetable.route_ids.map((routeId) =>
|
|
68
|
-
getShapesAsGeoJSON({
|
|
69
|
-
route_id: routeId,
|
|
70
|
-
direction_id: timetable.direction_id,
|
|
71
|
-
trip_id: timetable.orderedTrips.map((trip) => trip.trip_id),
|
|
72
|
-
}),
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
const stopsGeojsons = timetable.route_ids.map((routeId) =>
|
|
76
|
-
getStopsAsGeoJSON({
|
|
77
|
-
route_id: routeId,
|
|
78
|
-
direction_id: timetable.direction_id,
|
|
79
|
-
trip_id: timetable.orderedTrips.map((trip) => trip.trip_id),
|
|
80
|
-
}),
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
const geojson = mergeGeojson(...shapesGeojsons, ...stopsGeojsons);
|
|
84
|
-
return simplifyGeoJSON(geojson, config);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/*
|
|
88
|
-
* Get the geoJSON for an agency (all routes and stops).
|
|
89
|
-
*/
|
|
90
|
-
export function getAgencyGeoJSON(config) {
|
|
91
|
-
const shapesGeojsons = getShapesAsGeoJSON();
|
|
92
|
-
const stopsGeojsons = getStopsAsGeoJSON();
|
|
93
|
-
|
|
94
|
-
const geojson = mergeGeojson(shapesGeojsons, stopsGeojsons);
|
|
95
|
-
return simplifyGeoJSON(geojson, config);
|
|
96
|
-
}
|