gtfs-to-html 2.5.9 → 2.6.1
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 +27 -0
- package/config-sample.json +13 -4
- package/lib/file-utils.js +19 -25
- package/lib/formatters.js +37 -12
- package/lib/gtfs-to-html.js +2 -2
- package/lib/utils.js +13 -11
- package/package.json +11 -11
- package/public/css/timetable_styles.css +8 -8
- package/public/js/system-map.js +31 -31
- package/public/js/timetable-map.js +22 -17
- package/views/default/overview.pug +4 -4
- package/views/default/timetable_menu.pug +1 -1
- package/views/default/timetablepage.pug +10 -9
- package/www/docs/configuration.md +9 -0
- package/www/package.json +2 -2
- package/www/yarn.lock +1173 -1148
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,33 @@ 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.6.1] - 2024-03-26
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Fix for missing stops
|
|
13
|
+
|
|
14
|
+
### Updated
|
|
15
|
+
- Dependency updates
|
|
16
|
+
- Day List selector label wording
|
|
17
|
+
- Updates to timetable map
|
|
18
|
+
- Filename format for CSV export files
|
|
19
|
+
|
|
20
|
+
## [2.6.0] - 2024-02-27
|
|
21
|
+
|
|
22
|
+
### Updated
|
|
23
|
+
|
|
24
|
+
- Improved timetable_page_label for pages with a single timetable
|
|
25
|
+
- Better default timetable styles
|
|
26
|
+
- Default route_color and route_text_color values
|
|
27
|
+
- Better system map layer ordering
|
|
28
|
+
- Dependency updates
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- `showCalendarExceptions` configuration option
|
|
33
|
+
- Add "timepoint" class to stoptimes
|
|
34
|
+
|
|
8
35
|
## [2.5.9] - 2024-01-24
|
|
9
36
|
|
|
10
37
|
### Updated
|
package/config-sample.json
CHANGED
|
@@ -11,14 +11,22 @@
|
|
|
11
11
|
"coordinatePrecision": 5,
|
|
12
12
|
"dateFormat": "MMM D, YYYY",
|
|
13
13
|
"daysShortStrings": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
|
|
14
|
-
"daysStrings": [
|
|
14
|
+
"daysStrings": [
|
|
15
|
+
"Monday",
|
|
16
|
+
"Tuesday",
|
|
17
|
+
"Wednesday",
|
|
18
|
+
"Thursday",
|
|
19
|
+
"Friday",
|
|
20
|
+
"Saturday",
|
|
21
|
+
"Sunday"
|
|
22
|
+
],
|
|
15
23
|
"defaultOrientation": "vertical",
|
|
16
24
|
"effectiveDate": "July 8, 2016",
|
|
17
25
|
"interpolatedStopSymbol": "•",
|
|
18
26
|
"interpolatedStopText": "Estimated time of arrival",
|
|
19
|
-
"linkStopUrls":
|
|
27
|
+
"linkStopUrls": false,
|
|
20
28
|
"mapboxAccessToken": "YOUR MAPBOX ACCESS TOKEN",
|
|
21
|
-
"menuType": "
|
|
29
|
+
"menuType": "jump",
|
|
22
30
|
"noDropoffSymbol": "‡",
|
|
23
31
|
"noDropoffText": "No drop off available",
|
|
24
32
|
"noHead": false,
|
|
@@ -34,8 +42,9 @@
|
|
|
34
42
|
"serviceNotProvidedOnText": "Service not provided on",
|
|
35
43
|
"serviceProvidedOnText": "Service provided on",
|
|
36
44
|
"showArrivalOnDifference": 0.2,
|
|
45
|
+
"showCalendarExceptions": true,
|
|
37
46
|
"showMap": true,
|
|
38
|
-
"showOnlyTimepoint":
|
|
47
|
+
"showOnlyTimepoint": true,
|
|
39
48
|
"showRouteTitle": true,
|
|
40
49
|
"showStopCity": false,
|
|
41
50
|
"showStopDescription": false,
|
package/lib/file-utils.js
CHANGED
|
@@ -12,7 +12,12 @@ import puppeteer from 'puppeteer';
|
|
|
12
12
|
import sanitize from 'sanitize-filename';
|
|
13
13
|
import untildify from 'untildify';
|
|
14
14
|
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
isNullOrEmpty,
|
|
17
|
+
formatDays,
|
|
18
|
+
formatRouteColor,
|
|
19
|
+
formatRouteTextColor,
|
|
20
|
+
} from './formatters.js';
|
|
16
21
|
import * as templateFunctions from './template-functions.js';
|
|
17
22
|
|
|
18
23
|
/*
|
|
@@ -22,12 +27,12 @@ export async function getConfig(argv) {
|
|
|
22
27
|
try {
|
|
23
28
|
const data = await readFile(
|
|
24
29
|
path.resolve(untildify(argv.configPath)),
|
|
25
|
-
'utf8'
|
|
30
|
+
'utf8',
|
|
26
31
|
).catch((error) => {
|
|
27
32
|
console.error(
|
|
28
33
|
new Error(
|
|
29
|
-
`Cannot find configuration file at \`${argv.configPath}\`. Use config-sample.json as a starting point, pass --configPath option
|
|
30
|
-
)
|
|
34
|
+
`Cannot find configuration file at \`${argv.configPath}\`. Use config-sample.json as a starting point, pass --configPath option`,
|
|
35
|
+
),
|
|
31
36
|
);
|
|
32
37
|
throw error;
|
|
33
38
|
});
|
|
@@ -45,8 +50,8 @@ export async function getConfig(argv) {
|
|
|
45
50
|
} catch (error) {
|
|
46
51
|
console.error(
|
|
47
52
|
new Error(
|
|
48
|
-
`Cannot parse configuration file at \`${argv.configPath}\`. Check to ensure that it is valid JSON
|
|
49
|
-
)
|
|
53
|
+
`Cannot parse configuration file at \`${argv.configPath}\`. Check to ensure that it is valid JSON.`,
|
|
54
|
+
),
|
|
50
55
|
);
|
|
51
56
|
throw error;
|
|
52
57
|
}
|
|
@@ -65,14 +70,14 @@ function getTemplatePath(templateFileName, config) {
|
|
|
65
70
|
if (config.templatePath !== undefined) {
|
|
66
71
|
return path.join(
|
|
67
72
|
untildify(config.templatePath),
|
|
68
|
-
`${fullTemplateFileName}.pug
|
|
73
|
+
`${fullTemplateFileName}.pug`,
|
|
69
74
|
);
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
return path.join(
|
|
73
78
|
fileURLToPath(import.meta.url),
|
|
74
79
|
'../../views/default',
|
|
75
|
-
`${fullTemplateFileName}.pug
|
|
80
|
+
`${fullTemplateFileName}.pug`,
|
|
76
81
|
);
|
|
77
82
|
}
|
|
78
83
|
|
|
@@ -86,7 +91,7 @@ export async function prepDirectory(exportPath) {
|
|
|
86
91
|
} catch (error) {
|
|
87
92
|
if (error.code === 'ENOENT') {
|
|
88
93
|
throw new Error(
|
|
89
|
-
`Unable to write to ${exportPath}. Try running this command from a writable directory
|
|
94
|
+
`Unable to write to ${exportPath}. Try running this command from a writable directory.`,
|
|
90
95
|
);
|
|
91
96
|
}
|
|
92
97
|
|
|
@@ -100,7 +105,7 @@ export async function prepDirectory(exportPath) {
|
|
|
100
105
|
export function copyStaticAssets(exportPath) {
|
|
101
106
|
const staticAssetPath = path.join(
|
|
102
107
|
fileURLToPath(import.meta.url),
|
|
103
|
-
'../../public'
|
|
108
|
+
'../../public',
|
|
104
109
|
);
|
|
105
110
|
copydir.sync(path.join(staticAssetPath, 'css'), path.join(exportPath, 'css'));
|
|
106
111
|
copydir.sync(path.join(staticAssetPath, 'js'), path.join(exportPath, 'js'));
|
|
@@ -127,7 +132,7 @@ export function zipFolder(exportPath) {
|
|
|
127
132
|
/*
|
|
128
133
|
* Generate the filename for a given timetable.
|
|
129
134
|
*/
|
|
130
|
-
export function generateFileName(timetable, config) {
|
|
135
|
+
export function generateFileName(timetable, config, extension = 'html') {
|
|
131
136
|
let filename = timetable.timetable_id;
|
|
132
137
|
|
|
133
138
|
for (const route of timetable.routes) {
|
|
@@ -140,24 +145,11 @@ export function generateFileName(timetable, config) {
|
|
|
140
145
|
filename += `_${timetable.direction_id}`;
|
|
141
146
|
}
|
|
142
147
|
|
|
143
|
-
filename += `_${formatDays(timetable, config).replace(/\s/g, '')}
|
|
148
|
+
filename += `_${formatDays(timetable, config).replace(/\s/g, '')}.${extension}`;
|
|
144
149
|
|
|
145
150
|
return sanitize(filename).toLowerCase();
|
|
146
151
|
}
|
|
147
152
|
|
|
148
|
-
/*
|
|
149
|
-
* Generate the filename for a CSV timetable.
|
|
150
|
-
*/
|
|
151
|
-
export function generateCSVFileName(timetable, timetablePage) {
|
|
152
|
-
let filename = timetablePage.filename.replace(/.html$/, '');
|
|
153
|
-
|
|
154
|
-
if (timetablePage.timetables.length > 1) {
|
|
155
|
-
filename += `_${timetable.direction_id}`;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return sanitize(`${filename}.csv`);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
153
|
/*
|
|
162
154
|
* Generates the folder name for a timetable page based on the date.
|
|
163
155
|
*/
|
|
@@ -181,6 +173,8 @@ export async function renderTemplate(templateFileName, templateVars, config) {
|
|
|
181
173
|
const html = await renderFile(templatePath, {
|
|
182
174
|
_,
|
|
183
175
|
...templateFunctions,
|
|
176
|
+
formatRouteColor,
|
|
177
|
+
formatRouteTextColor,
|
|
184
178
|
...templateVars,
|
|
185
179
|
});
|
|
186
180
|
|
package/lib/formatters.js
CHANGED
|
@@ -116,6 +116,10 @@ function formatStopTime(stoptime, timetable, config) {
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
if (stoptime.timepoint === 1) {
|
|
120
|
+
stoptime.classes.push('timepoint');
|
|
121
|
+
}
|
|
122
|
+
|
|
119
123
|
return stoptime;
|
|
120
124
|
}
|
|
121
125
|
/* eslint-enable complexity */
|
|
@@ -374,8 +378,8 @@ export function formatStopName(stop) {
|
|
|
374
378
|
stop.type === 'arrival'
|
|
375
379
|
? ' (Arrival)'
|
|
376
380
|
: stop.type === 'departure'
|
|
377
|
-
|
|
378
|
-
|
|
381
|
+
? ' (Departure)'
|
|
382
|
+
: ''
|
|
379
383
|
}`;
|
|
380
384
|
}
|
|
381
385
|
|
|
@@ -442,6 +446,22 @@ export function updateStoptimesByOffset(trip, offsetSeconds) {
|
|
|
442
446
|
});
|
|
443
447
|
}
|
|
444
448
|
|
|
449
|
+
/*
|
|
450
|
+
* Format a route color as a hex color.
|
|
451
|
+
*/
|
|
452
|
+
export function formatRouteColor(route) {
|
|
453
|
+
// Defaults to #000000 (black) if no color is provided.
|
|
454
|
+
return route.route_color ? `#${route.route_color}` : '#000000';
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/*
|
|
458
|
+
* Format a route text color as a hex color.
|
|
459
|
+
*/
|
|
460
|
+
export function formatRouteTextColor(route) {
|
|
461
|
+
// Defaults to #FFFFFF (white) if no color is provided.
|
|
462
|
+
return route.route_text_color ? `#${route.route_text_color}` : '#FFFFFF';
|
|
463
|
+
}
|
|
464
|
+
|
|
445
465
|
/*
|
|
446
466
|
* Format a label for a timetable.
|
|
447
467
|
*/
|
|
@@ -488,21 +508,26 @@ export function formatTimetablePageLabel(timetablePage) {
|
|
|
488
508
|
return timetablePage.timetable_page_label;
|
|
489
509
|
}
|
|
490
510
|
|
|
491
|
-
// Get label from first timetable.
|
|
492
511
|
if (
|
|
493
512
|
timetablePage.consolidatedTimetables &&
|
|
494
513
|
timetablePage.consolidatedTimetables.length > 0
|
|
495
514
|
) {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
515
|
+
if (timetablePage.consolidatedTimetables.length === 1) {
|
|
516
|
+
// Get label from first timetable if there is only one
|
|
517
|
+
return timetablePage.consolidatedTimetables[0].timetable_label;
|
|
518
|
+
} else {
|
|
519
|
+
// Otherwise, use route names from all timetables
|
|
520
|
+
const routes = uniqBy(
|
|
521
|
+
flatMap(
|
|
522
|
+
timetablePage.consolidatedTimetables,
|
|
523
|
+
(timetable) => timetable.routes,
|
|
524
|
+
),
|
|
525
|
+
'route_id',
|
|
526
|
+
);
|
|
527
|
+
const timetablePageLabel = routes.map((route) => formatRouteName(route));
|
|
504
528
|
|
|
505
|
-
|
|
529
|
+
return timetablePageLabel.join(' and ');
|
|
530
|
+
}
|
|
506
531
|
}
|
|
507
532
|
|
|
508
533
|
return 'Unknown';
|
package/lib/gtfs-to-html.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
generateFolderName,
|
|
13
13
|
renderPdf,
|
|
14
14
|
zipFolder,
|
|
15
|
-
|
|
15
|
+
generateFileName,
|
|
16
16
|
} from './file-utils.js';
|
|
17
17
|
import {
|
|
18
18
|
log,
|
|
@@ -139,7 +139,7 @@ const gtfsToHtml = async (initialConfig) => {
|
|
|
139
139
|
const csvPath = path.join(
|
|
140
140
|
exportPath,
|
|
141
141
|
datePath,
|
|
142
|
-
|
|
142
|
+
generateFileName(timetable, config, 'csv'),
|
|
143
143
|
);
|
|
144
144
|
await writeFile(csvPath, csv);
|
|
145
145
|
}
|
package/lib/utils.js
CHANGED
|
@@ -472,7 +472,7 @@ const convertTimetableToTimetablePage = (timetable, config) => {
|
|
|
472
472
|
});
|
|
473
473
|
}
|
|
474
474
|
|
|
475
|
-
const filename = generateFileName(timetable, config);
|
|
475
|
+
const filename = generateFileName(timetable, config, 'html');
|
|
476
476
|
|
|
477
477
|
return {
|
|
478
478
|
timetable_page_id: timetable.timetable_id,
|
|
@@ -855,6 +855,10 @@ const getAllStationStopIds = (stopId) => {
|
|
|
855
855
|
stop_id: stopId,
|
|
856
856
|
});
|
|
857
857
|
|
|
858
|
+
if (stops.length === 0) {
|
|
859
|
+
throw new Error(`No stop found for stop_id=${stopId}`);
|
|
860
|
+
}
|
|
861
|
+
|
|
858
862
|
const stop = stops[0];
|
|
859
863
|
|
|
860
864
|
if (isNullOrEmpty(stop.parent_station)) {
|
|
@@ -1153,11 +1157,11 @@ const getTripsForTimetable = (timetable, calendars, config) => {
|
|
|
1153
1157
|
|
|
1154
1158
|
for (const trip of formattedTrips) {
|
|
1155
1159
|
for (const stoptime of trip.stoptimes) {
|
|
1156
|
-
const
|
|
1157
|
-
|
|
1158
|
-
)
|
|
1159
|
-
|
|
1160
|
-
|
|
1160
|
+
const stop = stops.find((stop) => stop.stop_id === stoptime.stop_id);
|
|
1161
|
+
|
|
1162
|
+
if (stop?.parent_station) {
|
|
1163
|
+
stoptime.stop_id = stop.parent_station;
|
|
1164
|
+
}
|
|
1161
1165
|
}
|
|
1162
1166
|
}
|
|
1163
1167
|
}
|
|
@@ -1413,11 +1417,12 @@ export function setDefaultConfig(initialConfig) {
|
|
|
1413
1417
|
interpolatedStopSymbol: '•',
|
|
1414
1418
|
interpolatedStopText: 'Estimated time of arrival',
|
|
1415
1419
|
gtfsToHtmlVersion: version,
|
|
1420
|
+
linkStopUrls: false,
|
|
1416
1421
|
menuType: 'jump',
|
|
1417
1422
|
noDropoffSymbol: '‡',
|
|
1418
1423
|
noDropoffText: 'No drop off available',
|
|
1419
1424
|
noHead: false,
|
|
1420
|
-
noPickupSymbol: '
|
|
1425
|
+
noPickupSymbol: '**',
|
|
1421
1426
|
noPickupText: 'No pickup available',
|
|
1422
1427
|
noServiceSymbol: '-',
|
|
1423
1428
|
noServiceText: 'No service at this stop',
|
|
@@ -1429,6 +1434,7 @@ export function setDefaultConfig(initialConfig) {
|
|
|
1429
1434
|
serviceNotProvidedOnText: 'Service not provided on',
|
|
1430
1435
|
serviceProvidedOnText: 'Service provided on',
|
|
1431
1436
|
showArrivalOnDifference: 0.2,
|
|
1437
|
+
showCalendarExceptions: true,
|
|
1432
1438
|
showMap: false,
|
|
1433
1439
|
showOnlyTimepoint: false,
|
|
1434
1440
|
showRouteTitle: true,
|
|
@@ -1482,10 +1488,6 @@ export function getFormattedTimetablePage(timetablePageId, config) {
|
|
|
1482
1488
|
['route_color', 'route_text_color', 'agency_id'],
|
|
1483
1489
|
);
|
|
1484
1490
|
|
|
1485
|
-
timetablePage.routeColors = timetableRoutes.map((route) => route.route_color);
|
|
1486
|
-
timetablePage.routeTextColors = timetableRoutes.map(
|
|
1487
|
-
(route) => route.route_text_color,
|
|
1488
|
-
);
|
|
1489
1491
|
timetablePage.agency_ids = compact(
|
|
1490
1492
|
timetableRoutes.map((route) => route.agency_id),
|
|
1491
1493
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gtfs-to-html",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Build human readable transit timetables as HTML, PDF or CSV from GTFS",
|
|
6
6
|
"keywords": [
|
|
@@ -37,31 +37,31 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@turf/helpers": "^6.5.0",
|
|
39
39
|
"@turf/simplify": "^6.5.0",
|
|
40
|
-
"archiver": "^
|
|
40
|
+
"archiver": "^7.0.1",
|
|
41
41
|
"cli-table": "^0.3.11",
|
|
42
42
|
"copy-dir": "^1.3.0",
|
|
43
|
-
"csv-stringify": "^6.4.
|
|
44
|
-
"express": "^4.
|
|
45
|
-
"gtfs": "^4.
|
|
46
|
-
"js-beautify": "^1.
|
|
43
|
+
"csv-stringify": "^6.4.6",
|
|
44
|
+
"express": "^4.19.2",
|
|
45
|
+
"gtfs": "^4.9.0",
|
|
46
|
+
"js-beautify": "^1.15.1",
|
|
47
47
|
"lodash-es": "^4.17.21",
|
|
48
48
|
"moment": "^2.30.1",
|
|
49
49
|
"morgan": "^1.10.0",
|
|
50
50
|
"pretty-error": "^4.0.0",
|
|
51
51
|
"pug": "^3.0.2",
|
|
52
|
-
"puppeteer": "^
|
|
52
|
+
"puppeteer": "^22.6.1",
|
|
53
53
|
"sanitize-filename": "^1.6.3",
|
|
54
54
|
"sqlstring": "^2.3.3",
|
|
55
55
|
"timer-machine": "^1.1.0",
|
|
56
56
|
"toposort": "^2.0.2",
|
|
57
57
|
"untildify": "^5.0.0",
|
|
58
58
|
"yargs": "^17.7.2",
|
|
59
|
-
"yoctocolors": "^
|
|
59
|
+
"yoctocolors": "^2.0.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"husky": "^
|
|
63
|
-
"lint-staged": "^15.2.
|
|
64
|
-
"prettier": "^3.2.
|
|
62
|
+
"husky": "^9.0.11",
|
|
63
|
+
"lint-staged": "^15.2.2",
|
|
64
|
+
"prettier": "^3.2.5"
|
|
65
65
|
},
|
|
66
66
|
"engines": {
|
|
67
67
|
"node": ">= 18.0.0"
|
|
@@ -163,22 +163,22 @@ a:hover {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
.route-color-swatch {
|
|
166
|
-
min-width:
|
|
167
|
-
height:
|
|
168
|
-
border-radius:
|
|
166
|
+
min-width: 34px;
|
|
167
|
+
height: 34px;
|
|
168
|
+
border-radius: 17px;
|
|
169
169
|
text-align: center;
|
|
170
|
-
line-height:
|
|
170
|
+
line-height: 34px;
|
|
171
171
|
font-size: 14px;
|
|
172
172
|
letter-spacing: -0.5px;
|
|
173
173
|
padding: 0 5px;
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
.route-color-swatch-large {
|
|
177
|
-
min-width:
|
|
178
|
-
height:
|
|
179
|
-
border-radius:
|
|
177
|
+
min-width: 46px;
|
|
178
|
+
height: 46px;
|
|
179
|
+
border-radius: 23px;
|
|
180
180
|
text-align: center;
|
|
181
|
-
line-height:
|
|
181
|
+
line-height: 46px;
|
|
182
182
|
font-size: 20px;
|
|
183
183
|
font-weight: bold;
|
|
184
184
|
letter-spacing: -1px;
|
package/public/js/system-map.js
CHANGED
|
@@ -3,33 +3,33 @@
|
|
|
3
3
|
|
|
4
4
|
const maps = {};
|
|
5
5
|
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
if (route.route_short_name !== undefined) {
|
|
9
|
-
routeName += route.route_short_name;
|
|
10
|
-
} else if (route.route_long_name !== undefined) {
|
|
11
|
-
routeName += route.route_long_name;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return routeName;
|
|
6
|
+
function formatRouteColor(route) {
|
|
7
|
+
return route.route_color || '#000000';
|
|
15
8
|
}
|
|
16
9
|
|
|
17
10
|
function formatRoute(route) {
|
|
18
11
|
const html = route.route_url
|
|
19
|
-
? $('<a>').attr('href', route.route_url)
|
|
12
|
+
? $('<a>').attr('href', route.route_url).addClass('hover:no-underline')
|
|
20
13
|
: $('<div>');
|
|
21
14
|
|
|
22
|
-
html.addClass('route-item text-
|
|
15
|
+
html.addClass('route-item text-xs mb-2');
|
|
23
16
|
|
|
24
17
|
if (route.route_color) {
|
|
18
|
+
// Only add color swatch if route has a color
|
|
25
19
|
$('<div>')
|
|
26
|
-
.addClass('
|
|
27
|
-
.
|
|
20
|
+
.addClass('flex items-center gap-2')
|
|
21
|
+
.html([
|
|
22
|
+
$('<div>')
|
|
23
|
+
.addClass('route-color-swatch flex-shrink-0 text-white')
|
|
24
|
+
.css('backgroundColor', formatRouteColor(route))
|
|
25
|
+
.text(route.route_short_name || ''),
|
|
26
|
+
$('<div>')
|
|
27
|
+
.addClass('hover:underline')
|
|
28
|
+
.text(route.route_long_name || ''),
|
|
29
|
+
])
|
|
28
30
|
.appendTo(html);
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
$('<span>').text(formatRouteName(route)).appendTo(html);
|
|
32
|
-
|
|
33
33
|
return html.prop('outerHTML');
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -60,7 +60,7 @@ function formatStopPopup(feature) {
|
|
|
60
60
|
$('<strong>').text(feature.properties.stop_code).appendTo(html);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
$('<div>').text('Routes Served:').appendTo(html);
|
|
63
|
+
$('<div>').addClass('text-sm').text('Routes Served:').appendTo(html);
|
|
64
64
|
|
|
65
65
|
$(html).append(routes.map((route) => formatRoute(route)));
|
|
66
66
|
|
|
@@ -83,7 +83,7 @@ function getBounds(geojson) {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
function createSystemMap(id, geojson) {
|
|
86
|
-
const defaultRouteColor = '#
|
|
86
|
+
const defaultRouteColor = '#000000';
|
|
87
87
|
const lineLayout = {
|
|
88
88
|
'line-join': 'round',
|
|
89
89
|
'line-cap': 'round',
|
|
@@ -220,54 +220,54 @@ function createSystemMap(id, geojson) {
|
|
|
220
220
|
firstSymbolId,
|
|
221
221
|
);
|
|
222
222
|
|
|
223
|
-
// Add
|
|
223
|
+
// Add route lines next
|
|
224
224
|
map.addLayer(
|
|
225
225
|
{
|
|
226
|
-
id:
|
|
226
|
+
id: 'routes',
|
|
227
227
|
type: 'line',
|
|
228
228
|
source: {
|
|
229
229
|
type: 'geojson',
|
|
230
230
|
data: geojson,
|
|
231
231
|
},
|
|
232
232
|
paint: {
|
|
233
|
-
'line-color': '
|
|
233
|
+
'line-color': ['coalesce', ['get', 'route_color'], defaultRouteColor],
|
|
234
234
|
'line-opacity': 1,
|
|
235
235
|
'line-width': {
|
|
236
|
-
base:
|
|
236
|
+
base: 4,
|
|
237
237
|
stops: [
|
|
238
|
-
[14,
|
|
239
|
-
[18,
|
|
238
|
+
[14, 6],
|
|
239
|
+
[18, 16],
|
|
240
240
|
],
|
|
241
241
|
},
|
|
242
242
|
},
|
|
243
243
|
layout: lineLayout,
|
|
244
|
-
filter: ['
|
|
244
|
+
filter: ['has', 'route_id'],
|
|
245
245
|
},
|
|
246
246
|
firstSymbolId,
|
|
247
247
|
);
|
|
248
248
|
|
|
249
|
-
// Add route
|
|
249
|
+
// Add highlighted route white outlines next
|
|
250
250
|
map.addLayer(
|
|
251
251
|
{
|
|
252
|
-
id:
|
|
252
|
+
id: `highlighted-route-outlines`,
|
|
253
253
|
type: 'line',
|
|
254
254
|
source: {
|
|
255
255
|
type: 'geojson',
|
|
256
256
|
data: geojson,
|
|
257
257
|
},
|
|
258
258
|
paint: {
|
|
259
|
-
'line-color':
|
|
259
|
+
'line-color': '#FFFFFF',
|
|
260
260
|
'line-opacity': 1,
|
|
261
261
|
'line-width': {
|
|
262
|
-
base:
|
|
262
|
+
base: 10,
|
|
263
263
|
stops: [
|
|
264
|
-
[14,
|
|
265
|
-
[18,
|
|
264
|
+
[14, 16],
|
|
265
|
+
[18, 40],
|
|
266
266
|
],
|
|
267
267
|
},
|
|
268
268
|
},
|
|
269
269
|
layout: lineLayout,
|
|
270
|
-
filter: ['
|
|
270
|
+
filter: ['==', ['get', 'route_id'], 'none'],
|
|
271
271
|
},
|
|
272
272
|
firstSymbolId,
|
|
273
273
|
);
|
|
@@ -3,33 +3,38 @@
|
|
|
3
3
|
|
|
4
4
|
const maps = {};
|
|
5
5
|
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
if (route.route_short_name !== undefined) {
|
|
9
|
-
routeName += route.route_short_name;
|
|
10
|
-
} else if (route.route_long_name !== undefined) {
|
|
11
|
-
routeName += route.route_long_name;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return routeName;
|
|
6
|
+
function formatRouteColor(route) {
|
|
7
|
+
return route.route_color || '#000000';
|
|
15
8
|
}
|
|
16
9
|
|
|
17
10
|
function formatRoute(route) {
|
|
18
11
|
const html = route.route_url
|
|
19
|
-
? $('<a>').attr('href', route.route_url)
|
|
12
|
+
? $('<a>').attr('href', route.route_url).addClass('hover:no-underline')
|
|
20
13
|
: $('<div>');
|
|
21
14
|
|
|
22
|
-
html.addClass('route-item text-
|
|
15
|
+
html.addClass('route-item text-xs mb-2');
|
|
23
16
|
|
|
24
17
|
if (route.route_color) {
|
|
18
|
+
// Only add color swatch if route has a color
|
|
25
19
|
$('<div>')
|
|
26
|
-
.addClass('
|
|
27
|
-
.
|
|
20
|
+
.addClass('flex items-center gap-2')
|
|
21
|
+
.html([
|
|
22
|
+
$('<div>')
|
|
23
|
+
.addClass('route-color-swatch flex-shrink-0 text-white')
|
|
24
|
+
.css('backgroundColor', formatRouteColor(route))
|
|
25
|
+
.text(route.route_short_name || ''),
|
|
26
|
+
$('<div>')
|
|
27
|
+
.addClass('hover:underline')
|
|
28
|
+
.text(route.route_long_name || ''),
|
|
29
|
+
])
|
|
30
|
+
.appendTo(html);
|
|
31
|
+
} else {
|
|
32
|
+
$('<div>')
|
|
33
|
+
.addClass('hover:underline')
|
|
34
|
+
.text(route.route_long_name || '')
|
|
28
35
|
.appendTo(html);
|
|
29
36
|
}
|
|
30
37
|
|
|
31
|
-
$('<span>').text(formatRouteName(route)).appendTo(html);
|
|
32
|
-
|
|
33
38
|
return html.prop('outerHTML');
|
|
34
39
|
}
|
|
35
40
|
|
|
@@ -48,7 +53,7 @@ function formatStopPopup(feature, routes) {
|
|
|
48
53
|
$('<strong>').text(feature.properties.stop_code).appendTo(html);
|
|
49
54
|
}
|
|
50
55
|
|
|
51
|
-
$('<div>').text('Routes Served:').appendTo(html);
|
|
56
|
+
$('<div>').addClass('text-sm mb-2').text('Routes Served:').appendTo(html);
|
|
52
57
|
|
|
53
58
|
$(html).append(routeIds.map((routeId) => formatRoute(routes[routeId])));
|
|
54
59
|
|
|
@@ -71,7 +76,7 @@ function getBounds(geojson) {
|
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
function createMap(id, geojson, routes) {
|
|
74
|
-
const defaultRouteColor = '#
|
|
79
|
+
const defaultRouteColor = '#000000';
|
|
75
80
|
const lineLayout = {
|
|
76
81
|
'line-join': 'round',
|
|
77
82
|
'line-cap': 'round',
|
|
@@ -10,12 +10,12 @@ include formatting_functions.pug
|
|
|
10
10
|
h1.text-2xl.pt-4.pb-2= `${formatAgencyName(timetablePageGroup.agency)} Routes`
|
|
11
11
|
each timetablePage in timetablePageGroup.timetablePages
|
|
12
12
|
if config.allowEmptyTimetables || timetablePage.consolidatedTimetables.length > 0
|
|
13
|
-
a.block.p-2.border-b.border-slate-200(class="hover:bg-slate-
|
|
13
|
+
a.block.p-2.border-b.border-slate-200(class="hover:bg-slate-100 hover:no-underline" href=`${timetablePage.relativePath}` data-route-ids=`${timetablePage.route_ids ? timetablePage.route_ids.join(',') : ''}`)
|
|
14
14
|
.text-lg.text-gray-800.leading-none= timetablePage.timetable_page_label
|
|
15
15
|
each route in _.uniqBy(_.flatMap(timetablePage.consolidatedTimetables, timetable => timetable.routes), 'route_id')
|
|
16
|
-
.flex.my-1
|
|
17
|
-
.route-color-swatch.flex-none
|
|
18
|
-
.
|
|
16
|
+
.flex.my-1.items-center.gap-1
|
|
17
|
+
.route-color-swatch.flex-none(style=`background-color: ${formatRouteColor(route)}; color: ${formatRouteTextColor(route)};`)= route.route_short_name || ''
|
|
18
|
+
.text-gray-600.leading-none= formatRouteName(route)
|
|
19
19
|
.inline-flex.items-center.justify-center.px-2.py-1.text-xs.font-bold.leading-none.text-slate-800.bg-slate-200.rounded-full= timetablePage.dayList
|
|
20
20
|
if config.showMap
|
|
21
21
|
.map.ml-4.h-full.w-full(id="system_map")
|
|
@@ -42,7 +42,7 @@ if timetablePage.consolidatedTimetables.length > 1
|
|
|
42
42
|
span= directionName
|
|
43
43
|
div(hidden=timetablePage.dayLists.length <= 1)
|
|
44
44
|
#day_list_selector
|
|
45
|
-
h3.font-bold
|
|
45
|
+
h3.font-bold Day of Week
|
|
46
46
|
each dayList, idx in timetablePage.dayLists
|
|
47
47
|
label.cursor-pointer.mb-2.w-full.flex.items-center.justify-center.px-8.py-3.border.border-transparent.text-base.rounded-md(class=idx === 0 ? 'text-white bg-blue-600': 'text-gray-600 bg-gray-300')
|
|
48
48
|
input.hidden(type="radio" name="dayList" autocomplete="off" value=dayList checked=(idx === 0))
|