gtfs-to-html 2.9.14 → 2.10.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/README.md +3 -1
- package/config-sample.json +0 -1
- package/dist/app/index.js +104 -55
- package/dist/app/index.js.map +1 -1
- package/dist/bin/gtfs-to-html.js +29 -17
- package/dist/bin/gtfs-to-html.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +29 -17
- package/dist/index.js.map +1 -1
- package/package.json +10 -11
- package/views/default/css/overview_styles.css +11 -9
- package/views/default/css/timetable_styles.css +17 -15
- package/views/default/formatting_functions.pug +2 -1
- package/views/default/js/system-map.js +492 -400
- package/views/default/js/timetable-map.js +390 -286
- package/views/default/overview.pug +3 -4
- package/views/default/overview_full.pug +4 -4
- package/views/default/timetablepage.pug +1 -1
- package/views/default/timetablepage_full.pug +2 -4
package/README.md
CHANGED
|
@@ -59,7 +59,8 @@ 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)
|
|
64
65
|
- [BusWay – CIRA (Aveiro, Portugal)](https://busway-cira.pt)
|
|
65
66
|
- [Capital Transit (Helena, Montana)](http://www.ridethecapitalt.org)
|
|
@@ -70,6 +71,7 @@ Many transit agencies use `gtfs-to-html` to generate the schedule pages used on
|
|
|
70
71
|
- [Greater Attleboro-Taunton Regional Transit Authority](https://www.gatra.org)
|
|
71
72
|
- [Humboldt Transit Authority](http://hta.org)
|
|
72
73
|
- [Kings Area Rural Transit (KART)](https://www.kartbus.org)
|
|
74
|
+
- [Lowell Regional Transit Authority](https://lrta.com)
|
|
73
75
|
- [Madera County Connection](http://mcctransit.com)
|
|
74
76
|
- [Marin Transit](https://marintransit.org)
|
|
75
77
|
- [Morongo Basin Transit Authority](https://mbtabus.com)
|
package/config-sample.json
CHANGED
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
|
|
@@ -376,7 +375,7 @@ function getAgencyGeoJSON(config2) {
|
|
|
376
375
|
}
|
|
377
376
|
|
|
378
377
|
// package.json
|
|
379
|
-
var version = "2.
|
|
378
|
+
var version = "2.10.0";
|
|
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) {
|
|
@@ -1319,6 +1329,7 @@ function setDefaultConfig(initialConfig) {
|
|
|
1319
1329
|
interpolatedStopText: "Estimated time of arrival",
|
|
1320
1330
|
gtfsToHtmlVersion: version,
|
|
1321
1331
|
linkStopUrls: false,
|
|
1332
|
+
mapStyleUrl: "https://tiles.openfreemap.org/styles/liberty",
|
|
1322
1333
|
menuType: "jump",
|
|
1323
1334
|
noDropoffSymbol: "\u2021",
|
|
1324
1335
|
noDropoffText: "No drop off available",
|
|
@@ -1815,7 +1826,6 @@ var argv = yargs(hideBin(process.argv)).option("c", {
|
|
|
1815
1826
|
type: "string"
|
|
1816
1827
|
}).parseSync();
|
|
1817
1828
|
var app = express();
|
|
1818
|
-
var router = express.Router();
|
|
1819
1829
|
var configPath = argv.configPath || join2(process.cwd(), "config.json");
|
|
1820
1830
|
var selectedConfig = JSON.parse(readFileSync(configPath, "utf8"));
|
|
1821
1831
|
var config = setDefaultConfig(selectedConfig);
|
|
@@ -1834,7 +1844,36 @@ try {
|
|
|
1834
1844
|
}
|
|
1835
1845
|
throw error;
|
|
1836
1846
|
}
|
|
1837
|
-
|
|
1847
|
+
app.set("views", getPathToViewsFolder(config));
|
|
1848
|
+
app.set("view engine", "pug");
|
|
1849
|
+
app.use((req, res, next) => {
|
|
1850
|
+
console.log(`${req.method} ${req.url}`);
|
|
1851
|
+
next();
|
|
1852
|
+
});
|
|
1853
|
+
var staticAssetPath = config.templatePath === void 0 ? getPathToViewsFolder(config) : untildify2(config.templatePath);
|
|
1854
|
+
app.use(express.static(staticAssetPath));
|
|
1855
|
+
app.use(
|
|
1856
|
+
"/js",
|
|
1857
|
+
express.static(
|
|
1858
|
+
join2(dirname2(fileURLToPath2(import.meta.resolve("pbf"))), "dist")
|
|
1859
|
+
)
|
|
1860
|
+
);
|
|
1861
|
+
app.use(
|
|
1862
|
+
"/js",
|
|
1863
|
+
express.static(
|
|
1864
|
+
dirname2(fileURLToPath2(import.meta.resolve("gtfs-realtime-pbf-js-module")))
|
|
1865
|
+
)
|
|
1866
|
+
);
|
|
1867
|
+
app.use(
|
|
1868
|
+
"/js",
|
|
1869
|
+
express.static(
|
|
1870
|
+
join2(
|
|
1871
|
+
dirname2(fileURLToPath2(import.meta.resolve("anchorme"))),
|
|
1872
|
+
"../../dist/browser"
|
|
1873
|
+
)
|
|
1874
|
+
)
|
|
1875
|
+
);
|
|
1876
|
+
app.get("/", async (req, res, next) => {
|
|
1838
1877
|
try {
|
|
1839
1878
|
const timetablePages = [];
|
|
1840
1879
|
const timetablePageIds = map(
|
|
@@ -1850,6 +1889,7 @@ router.get("/", async (request, response, next) => {
|
|
|
1850
1889
|
console.error(
|
|
1851
1890
|
`No timetables found for timetable_page_id=${timetablePage.timetable_page_id}`
|
|
1852
1891
|
);
|
|
1892
|
+
continue;
|
|
1853
1893
|
}
|
|
1854
1894
|
timetablePage.relativePath = `/timetables/${timetablePage.timetable_page_id}`;
|
|
1855
1895
|
for (const timetable of timetablePage.consolidatedTimetables) {
|
|
@@ -1858,57 +1898,66 @@ router.get("/", async (request, response, next) => {
|
|
|
1858
1898
|
timetablePages.push(timetablePage);
|
|
1859
1899
|
}
|
|
1860
1900
|
const html = await generateOverviewHTML(timetablePages, config);
|
|
1861
|
-
|
|
1901
|
+
res.send(html);
|
|
1862
1902
|
} catch (error) {
|
|
1863
|
-
console.error(error);
|
|
1864
1903
|
next(error);
|
|
1865
1904
|
}
|
|
1866
1905
|
});
|
|
1867
|
-
|
|
1868
|
-
const { timetablePageId } =
|
|
1906
|
+
app.get("/timetables/:timetablePageId", async (req, res, next) => {
|
|
1907
|
+
const { timetablePageId } = req.params;
|
|
1869
1908
|
if (!timetablePageId) {
|
|
1870
|
-
|
|
1909
|
+
res.status(400).send("No timetablePageId provided");
|
|
1910
|
+
return;
|
|
1871
1911
|
}
|
|
1872
1912
|
try {
|
|
1873
1913
|
const timetablePage = await getFormattedTimetablePage(
|
|
1874
1914
|
timetablePageId,
|
|
1875
1915
|
config
|
|
1876
1916
|
);
|
|
1917
|
+
if (!timetablePage || !timetablePage.consolidatedTimetables || timetablePage.consolidatedTimetables.length === 0) {
|
|
1918
|
+
res.status(404).send("Timetable page not found");
|
|
1919
|
+
return;
|
|
1920
|
+
}
|
|
1877
1921
|
const html = await generateTimetableHTML(timetablePage, config);
|
|
1878
|
-
|
|
1922
|
+
res.send(html);
|
|
1879
1923
|
} catch (error) {
|
|
1924
|
+
if (error?.message.startsWith("No timetable found")) {
|
|
1925
|
+
res.status(404).send("Timetable page not found");
|
|
1926
|
+
return;
|
|
1927
|
+
}
|
|
1880
1928
|
next(error);
|
|
1881
1929
|
}
|
|
1882
1930
|
});
|
|
1883
|
-
app.
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
var staticAssetPath = config.templatePath === void 0 ? getPathToViewsFolder(config) : untildify2(config.templatePath);
|
|
1887
|
-
app.use(express.static(staticAssetPath));
|
|
1888
|
-
app.use(
|
|
1889
|
-
"/js",
|
|
1890
|
-
express.static(
|
|
1891
|
-
join2(dirname2(fileURLToPath2(import.meta.resolve("pbf"))), "dist")
|
|
1892
|
-
)
|
|
1893
|
-
);
|
|
1894
|
-
app.use(
|
|
1895
|
-
"/js",
|
|
1896
|
-
express.static(
|
|
1897
|
-
dirname2(fileURLToPath2(import.meta.resolve("gtfs-realtime-pbf-js-module")))
|
|
1898
|
-
)
|
|
1899
|
-
);
|
|
1931
|
+
app.use((req, res) => {
|
|
1932
|
+
res.status(404).send("Not Found");
|
|
1933
|
+
});
|
|
1900
1934
|
app.use(
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
"../../dist/browser"
|
|
1906
|
-
)
|
|
1907
|
-
)
|
|
1935
|
+
(err, req, res, next) => {
|
|
1936
|
+
console.error(err.stack);
|
|
1937
|
+
res.status(500).send("Something broke!");
|
|
1938
|
+
}
|
|
1908
1939
|
);
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
});
|
|
1940
|
+
var startServer = async (port2) => {
|
|
1941
|
+
try {
|
|
1942
|
+
await new Promise((resolve2, reject) => {
|
|
1943
|
+
const server = app.listen(port2).once("listening", () => {
|
|
1944
|
+
console.log(`Express server listening on port ${port2}`);
|
|
1945
|
+
resolve2();
|
|
1946
|
+
}).once("error", (err) => {
|
|
1947
|
+
if (err.code === "EADDRINUSE") {
|
|
1948
|
+
console.log(`Port ${port2} is in use, trying ${port2 + 1}`);
|
|
1949
|
+
server.close();
|
|
1950
|
+
resolve2(startServer(port2 + 1));
|
|
1951
|
+
} else {
|
|
1952
|
+
reject(err);
|
|
1953
|
+
}
|
|
1954
|
+
});
|
|
1955
|
+
});
|
|
1956
|
+
} catch (err) {
|
|
1957
|
+
console.error("Failed to start server:", err);
|
|
1958
|
+
process.exit(1);
|
|
1959
|
+
}
|
|
1960
|
+
};
|
|
1961
|
+
var port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3e3;
|
|
1962
|
+
startServer(port);
|
|
1914
1963
|
//# sourceMappingURL=index.js.map
|