gtfs-to-html 2.9.13 → 2.9.15
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/README.md +4 -1
- package/dist/app/index.js +105 -56
- package/dist/app/index.js.map +1 -1
- package/dist/bin/gtfs-to-html.js +42 -26
- package/dist/bin/gtfs-to-html.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +42 -26
- package/dist/index.js.map +1 -1
- package/package.json +15 -9
- package/views/default/css/overview_styles.css +4 -2
- package/views/default/css/timetable_styles.css +3 -1
- package/views/default/js/system-map.js +427 -393
- package/views/default/js/timetable-map.js +353 -271
package/README.md
CHANGED
|
@@ -59,8 +59,10 @@ You can now use `gtfs-to-html` without actually downloading any code or doing an
|
|
|
59
59
|
|
|
60
60
|
Many transit agencies use `gtfs-to-html` to generate the schedule pages used on their websites, including:
|
|
61
61
|
|
|
62
|
-
- [Advance Transit](https://advancetransit.com)
|
|
62
|
+
- [Advance Transit (Vermont)](https://advancetransit.com)
|
|
63
|
+
- [Basin Transit (Morongo Basin, California)](https://basin-transit.com/)
|
|
63
64
|
- [Brockton Area Transit Authority](https://ridebat.com)
|
|
65
|
+
- [BusWay – CIRA (Aveiro, Portugal)](https://busway-cira.pt)
|
|
64
66
|
- [Capital Transit (Helena, Montana)](http://www.ridethecapitalt.org)
|
|
65
67
|
- [Capital Transit (Juneau, Alaska)](https://juneaucapitaltransit.org)
|
|
66
68
|
- [Central Transit (Ellensburg, Washington)](https://centraltransit.org)
|
|
@@ -69,6 +71,7 @@ Many transit agencies use `gtfs-to-html` to generate the schedule pages used on
|
|
|
69
71
|
- [Greater Attleboro-Taunton Regional Transit Authority](https://www.gatra.org)
|
|
70
72
|
- [Humboldt Transit Authority](http://hta.org)
|
|
71
73
|
- [Kings Area Rural Transit (KART)](https://www.kartbus.org)
|
|
74
|
+
- [Lowell Regional Transit Authority](https://lrta.com)
|
|
72
75
|
- [Madera County Connection](http://mcctransit.com)
|
|
73
76
|
- [Marin Transit](https://marintransit.org)
|
|
74
77
|
- [Morongo Basin Transit Authority](https://mbtabus.com)
|
package/dist/app/index.js
CHANGED
|
@@ -13,7 +13,6 @@ import yargs from "yargs";
|
|
|
13
13
|
import { hideBin } from "yargs/helpers";
|
|
14
14
|
import { openDb as openDb2 } from "gtfs";
|
|
15
15
|
import express from "express";
|
|
16
|
-
import logger from "morgan";
|
|
17
16
|
import untildify2 from "untildify";
|
|
18
17
|
|
|
19
18
|
// src/lib/formatters.ts
|
|
@@ -133,7 +132,7 @@ import toposort from "toposort";
|
|
|
133
132
|
// src/lib/file-utils.ts
|
|
134
133
|
import { dirname, join, resolve } from "node:path";
|
|
135
134
|
import { fileURLToPath } from "node:url";
|
|
136
|
-
import _ from "lodash-es";
|
|
135
|
+
import * as _ from "lodash-es";
|
|
137
136
|
import archiver from "archiver";
|
|
138
137
|
import beautify from "js-beautify";
|
|
139
138
|
import { renderFile } from "pug";
|
|
@@ -376,7 +375,7 @@ function getAgencyGeoJSON(config2) {
|
|
|
376
375
|
}
|
|
377
376
|
|
|
378
377
|
// package.json
|
|
379
|
-
var version = "2.9.
|
|
378
|
+
var version = "2.9.15";
|
|
380
379
|
|
|
381
380
|
// src/lib/utils.ts
|
|
382
381
|
var isTimepoint = (stoptime) => {
|
|
@@ -430,12 +429,12 @@ var deduplicateTrips = (trips, commonStopId) => {
|
|
|
430
429
|
}) : trip.stoptimes[0];
|
|
431
430
|
const similarTrips = deduplicatedTrips.filter((trip2) => {
|
|
432
431
|
const stoptime = find(trip2.stoptimes, {
|
|
433
|
-
stop_id: selectedStoptime
|
|
432
|
+
stop_id: selectedStoptime?.stop_id
|
|
434
433
|
});
|
|
435
434
|
if (!stoptime) {
|
|
436
435
|
return false;
|
|
437
436
|
}
|
|
438
|
-
return stoptime.departure_time === selectedStoptime
|
|
437
|
+
return stoptime.departure_time === selectedStoptime?.departure_time;
|
|
439
438
|
});
|
|
440
439
|
const tripIsUnique = every2(similarTrips, (similarTrip) => {
|
|
441
440
|
const similarTripStoptimes = similarTrip.stoptimes.map(
|
|
@@ -467,8 +466,10 @@ var sortTrips = (trips, config2) => {
|
|
|
467
466
|
if (trip.stoptimes.length === 0) {
|
|
468
467
|
continue;
|
|
469
468
|
}
|
|
470
|
-
trip.firstStoptime = timeToSeconds(
|
|
471
|
-
trip.lastStoptime = timeToSeconds(
|
|
469
|
+
trip.firstStoptime = timeToSeconds(trip.stoptimes[0].departure_time);
|
|
470
|
+
trip.lastStoptime = timeToSeconds(
|
|
471
|
+
trip.stoptimes[trip.stoptimes.length - 1].departure_time
|
|
472
|
+
);
|
|
472
473
|
}
|
|
473
474
|
sortedTrips = sortBy(
|
|
474
475
|
trips,
|
|
@@ -480,8 +481,10 @@ var sortTrips = (trips, config2) => {
|
|
|
480
481
|
if (trip.stoptimes.length === 0) {
|
|
481
482
|
continue;
|
|
482
483
|
}
|
|
483
|
-
trip.firstStoptime = timeToSeconds(
|
|
484
|
-
trip.lastStoptime = timeToSeconds(
|
|
484
|
+
trip.firstStoptime = timeToSeconds(trip.stoptimes[0].departure_time);
|
|
485
|
+
trip.lastStoptime = timeToSeconds(
|
|
486
|
+
trip.stoptimes[trip.stoptimes.length - 1].departure_time
|
|
487
|
+
);
|
|
485
488
|
}
|
|
486
489
|
sortedTrips = sortBy(
|
|
487
490
|
trips,
|
|
@@ -547,8 +550,8 @@ var getDaysFromCalendars = (calendars) => {
|
|
|
547
550
|
sunday: 0
|
|
548
551
|
};
|
|
549
552
|
for (const calendar of calendars) {
|
|
550
|
-
for (const
|
|
551
|
-
days2[day] =
|
|
553
|
+
for (const day of Object.keys(days2)) {
|
|
554
|
+
days2[day] = days2[day] | calendar[day];
|
|
552
555
|
}
|
|
553
556
|
}
|
|
554
557
|
return days2;
|
|
@@ -823,20 +826,27 @@ var getStopOrder = (timetable, config2) => {
|
|
|
823
826
|
stopGraph.push([stopId, sortedStopIds[index + 1]]);
|
|
824
827
|
}
|
|
825
828
|
}
|
|
826
|
-
const
|
|
829
|
+
const stopIds = toposort(stopGraph);
|
|
827
830
|
return duplicateStopsForDifferentArrivalDeparture(
|
|
828
|
-
|
|
831
|
+
stopIds,
|
|
829
832
|
timetable,
|
|
830
833
|
config2
|
|
831
834
|
);
|
|
832
835
|
} catch {
|
|
836
|
+
const longestTripStoptimes = getLongestTripStoptimes(
|
|
837
|
+
timetable.orderedTrips,
|
|
838
|
+
config2
|
|
839
|
+
);
|
|
840
|
+
const stopIds = longestTripStoptimes.map((stoptime) => stoptime.stop_id);
|
|
841
|
+
config2.logWarning(
|
|
842
|
+
`Timetable ${timetable.timetable_id} stops are unable to be topologically sorted and has no \`timetable_stop_order.txt\`. Falling back to using the using the stop order from trip with most stoptimes, but this can result in timetables with some stops missing. Try manually specifying stops with \`timetable_stop_order.txt\`.`
|
|
843
|
+
);
|
|
844
|
+
return duplicateStopsForDifferentArrivalDeparture(
|
|
845
|
+
stopIds,
|
|
846
|
+
timetable,
|
|
847
|
+
config2
|
|
848
|
+
);
|
|
833
849
|
}
|
|
834
|
-
const longestTripStoptimes = getLongestTripStoptimes(
|
|
835
|
-
timetable.orderedTrips,
|
|
836
|
-
config2
|
|
837
|
-
);
|
|
838
|
-
const stopIds = longestTripStoptimes.map((stoptime) => stoptime.stop_id);
|
|
839
|
-
return duplicateStopsForDifferentArrivalDeparture(stopIds, timetable, config2);
|
|
840
850
|
};
|
|
841
851
|
var getStopsForTimetable = (timetable, config2) => {
|
|
842
852
|
if (timetable.orderedTrips.length === 0) {
|
|
@@ -1328,6 +1338,7 @@ function setDefaultConfig(initialConfig) {
|
|
|
1328
1338
|
noServiceSymbol: "-",
|
|
1329
1339
|
noServiceText: "No service at this stop",
|
|
1330
1340
|
outputFormat: "html",
|
|
1341
|
+
overwriteExistingFiles: true,
|
|
1331
1342
|
requestDropoffSymbol: "\u2020",
|
|
1332
1343
|
requestDropoffText: "Must request drop off",
|
|
1333
1344
|
requestPickupSymbol: "***",
|
|
@@ -1814,7 +1825,6 @@ var argv = yargs(hideBin(process.argv)).option("c", {
|
|
|
1814
1825
|
type: "string"
|
|
1815
1826
|
}).parseSync();
|
|
1816
1827
|
var app = express();
|
|
1817
|
-
var router = express.Router();
|
|
1818
1828
|
var configPath = argv.configPath || join2(process.cwd(), "config.json");
|
|
1819
1829
|
var selectedConfig = JSON.parse(readFileSync(configPath, "utf8"));
|
|
1820
1830
|
var config = setDefaultConfig(selectedConfig);
|
|
@@ -1833,7 +1843,36 @@ try {
|
|
|
1833
1843
|
}
|
|
1834
1844
|
throw error;
|
|
1835
1845
|
}
|
|
1836
|
-
|
|
1846
|
+
app.set("views", getPathToViewsFolder(config));
|
|
1847
|
+
app.set("view engine", "pug");
|
|
1848
|
+
app.use((req, res, next) => {
|
|
1849
|
+
console.log(`${req.method} ${req.url}`);
|
|
1850
|
+
next();
|
|
1851
|
+
});
|
|
1852
|
+
var staticAssetPath = config.templatePath === void 0 ? getPathToViewsFolder(config) : untildify2(config.templatePath);
|
|
1853
|
+
app.use(express.static(staticAssetPath));
|
|
1854
|
+
app.use(
|
|
1855
|
+
"/js",
|
|
1856
|
+
express.static(
|
|
1857
|
+
join2(dirname2(fileURLToPath2(import.meta.resolve("pbf"))), "dist")
|
|
1858
|
+
)
|
|
1859
|
+
);
|
|
1860
|
+
app.use(
|
|
1861
|
+
"/js",
|
|
1862
|
+
express.static(
|
|
1863
|
+
dirname2(fileURLToPath2(import.meta.resolve("gtfs-realtime-pbf-js-module")))
|
|
1864
|
+
)
|
|
1865
|
+
);
|
|
1866
|
+
app.use(
|
|
1867
|
+
"/js",
|
|
1868
|
+
express.static(
|
|
1869
|
+
join2(
|
|
1870
|
+
dirname2(fileURLToPath2(import.meta.resolve("anchorme"))),
|
|
1871
|
+
"../../dist/browser"
|
|
1872
|
+
)
|
|
1873
|
+
)
|
|
1874
|
+
);
|
|
1875
|
+
app.get("/", async (req, res, next) => {
|
|
1837
1876
|
try {
|
|
1838
1877
|
const timetablePages = [];
|
|
1839
1878
|
const timetablePageIds = map(
|
|
@@ -1849,6 +1888,7 @@ router.get("/", async (request, response, next) => {
|
|
|
1849
1888
|
console.error(
|
|
1850
1889
|
`No timetables found for timetable_page_id=${timetablePage.timetable_page_id}`
|
|
1851
1890
|
);
|
|
1891
|
+
continue;
|
|
1852
1892
|
}
|
|
1853
1893
|
timetablePage.relativePath = `/timetables/${timetablePage.timetable_page_id}`;
|
|
1854
1894
|
for (const timetable of timetablePage.consolidatedTimetables) {
|
|
@@ -1857,57 +1897,66 @@ router.get("/", async (request, response, next) => {
|
|
|
1857
1897
|
timetablePages.push(timetablePage);
|
|
1858
1898
|
}
|
|
1859
1899
|
const html = await generateOverviewHTML(timetablePages, config);
|
|
1860
|
-
|
|
1900
|
+
res.send(html);
|
|
1861
1901
|
} catch (error) {
|
|
1862
|
-
console.error(error);
|
|
1863
1902
|
next(error);
|
|
1864
1903
|
}
|
|
1865
1904
|
});
|
|
1866
|
-
|
|
1867
|
-
const { timetablePageId } =
|
|
1905
|
+
app.get("/timetables/:timetablePageId", async (req, res, next) => {
|
|
1906
|
+
const { timetablePageId } = req.params;
|
|
1868
1907
|
if (!timetablePageId) {
|
|
1869
|
-
|
|
1908
|
+
res.status(400).send("No timetablePageId provided");
|
|
1909
|
+
return;
|
|
1870
1910
|
}
|
|
1871
1911
|
try {
|
|
1872
1912
|
const timetablePage = await getFormattedTimetablePage(
|
|
1873
1913
|
timetablePageId,
|
|
1874
1914
|
config
|
|
1875
1915
|
);
|
|
1916
|
+
if (!timetablePage || !timetablePage.consolidatedTimetables || timetablePage.consolidatedTimetables.length === 0) {
|
|
1917
|
+
res.status(404).send("Timetable page not found");
|
|
1918
|
+
return;
|
|
1919
|
+
}
|
|
1876
1920
|
const html = await generateTimetableHTML(timetablePage, config);
|
|
1877
|
-
|
|
1921
|
+
res.send(html);
|
|
1878
1922
|
} catch (error) {
|
|
1923
|
+
if (error?.message.startsWith("No timetable found")) {
|
|
1924
|
+
res.status(404).send("Timetable page not found");
|
|
1925
|
+
return;
|
|
1926
|
+
}
|
|
1879
1927
|
next(error);
|
|
1880
1928
|
}
|
|
1881
1929
|
});
|
|
1882
|
-
app.
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
var staticAssetPath = config.templatePath === void 0 ? getPathToViewsFolder(config) : untildify2(config.templatePath);
|
|
1886
|
-
app.use(express.static(staticAssetPath));
|
|
1887
|
-
app.use(
|
|
1888
|
-
"/js",
|
|
1889
|
-
express.static(
|
|
1890
|
-
join2(dirname2(fileURLToPath2(import.meta.resolve("pbf"))), "dist")
|
|
1891
|
-
)
|
|
1892
|
-
);
|
|
1893
|
-
app.use(
|
|
1894
|
-
"/js",
|
|
1895
|
-
express.static(
|
|
1896
|
-
dirname2(fileURLToPath2(import.meta.resolve("gtfs-realtime-pbf-js-module")))
|
|
1897
|
-
)
|
|
1898
|
-
);
|
|
1930
|
+
app.use((req, res) => {
|
|
1931
|
+
res.status(404).send("Not Found");
|
|
1932
|
+
});
|
|
1899
1933
|
app.use(
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
"../../dist/browser"
|
|
1905
|
-
)
|
|
1906
|
-
)
|
|
1934
|
+
(err, req, res, next) => {
|
|
1935
|
+
console.error(err.stack);
|
|
1936
|
+
res.status(500).send("Something broke!");
|
|
1937
|
+
}
|
|
1907
1938
|
);
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
});
|
|
1939
|
+
var startServer = async (port2) => {
|
|
1940
|
+
try {
|
|
1941
|
+
await new Promise((resolve2, reject) => {
|
|
1942
|
+
const server = app.listen(port2).once("listening", () => {
|
|
1943
|
+
console.log(`Express server listening on port ${port2}`);
|
|
1944
|
+
resolve2();
|
|
1945
|
+
}).once("error", (err) => {
|
|
1946
|
+
if (err.code === "EADDRINUSE") {
|
|
1947
|
+
console.log(`Port ${port2} is in use, trying ${port2 + 1}`);
|
|
1948
|
+
server.close();
|
|
1949
|
+
resolve2(startServer(port2 + 1));
|
|
1950
|
+
} else {
|
|
1951
|
+
reject(err);
|
|
1952
|
+
}
|
|
1953
|
+
});
|
|
1954
|
+
});
|
|
1955
|
+
} catch (err) {
|
|
1956
|
+
console.error("Failed to start server:", err);
|
|
1957
|
+
process.exit(1);
|
|
1958
|
+
}
|
|
1959
|
+
};
|
|
1960
|
+
var port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3e3;
|
|
1961
|
+
startServer(port);
|
|
1913
1962
|
//# sourceMappingURL=index.js.map
|