import * as shapes from "./shapes.js"; import protobuf from "https://cdn.jsdelivr.net/npm/protobufjs@8.0.0/dist/protobuf.js/+esm"; import * as L from "https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet-src.esm.js"; import stations from "./stations.js" let lastUpdated; function displayLastUpdated() { document.getElementById("last-updated").innerText = `${Math.floor((Date.now() - (lastUpdated * 1000)) / 1000)}s ago` } setInterval(displayLastUpdated, 1000) const ICONS = { bus: ``, tram: ``, train: ``, }; var map = L.map("map").setView([40.656734, -111.890818], 12); L.tileLayer( "https://api.mapbox.com/styles/v1/mapbox/light-v11/tiles/256/{z}/{x}/{y}@2x?access_token=pk.eyJ1IjoiY2pkZW5pbyIsImEiOiJjbHdiMG52amcwaGd4MmttbWtlOWt5Mm1iIn0.GhHTt4W_mZpQcLYNkhsG_w", { maxZoom: 19, attribution: '© OpenStreetMap', } ).addTo(map); const busLayer = L.layerGroup().addTo(map); const brtLayer = L.layerGroup().addTo(map); const traxLayer = L.layerGroup().addTo(map); const frontRunnerLayer = L.layerGroup().addTo(map); L.polyline(shapes.blueLine, { color: "#004a97", zIndexOffset: 10 }).addTo(map); L.polyline(shapes.redLine, { color: "#be2036", zIndexOffset: 10 }).addTo(map); L.polyline(shapes.greenLine, { color: "#2eb566", zIndexOffset: 10 }).addTo(map); L.polyline(shapes.sLine, { color: "#77777a", zIndexOffset: 10 }).addTo(map); L.polyline(shapes.frontRunner, { color: "#c227b9", zIndexOffset: 10 }).addTo(map); // render stations for (const station of stations) { let color = ""; switch (station.lines[0]) { case "red": color = "#be2036" break case "blue": color = "#004a97" break case "green": color = "#2eb566" break case "s-line": color = "#77777a" break case "frontrunner": color = "#c227b9" break } L.marker([station.lat, station.lon], { zIndexOffset: 2000, icon: L.divIcon({ className: "", html: `
`, iconSize: [5, 5], }) }).addTo(map).bindPopup(station.name.replace(" Station", "")) } function clearMap() { busLayer.clearLayers(); brtLayer.clearLayers(); traxLayer.clearLayers(); frontRunnerLayer.clearLayers(); } protobuf.load("/schema.proto").then((root) => { const RouteType = root.VehicleFeed.Route.RouteType; function routeDesignator(route) { if (route.id == "92235") { return "OGX"; } else if (route.id == "3686") { return "UVX"; } else if (route.id == "87711") { return "MVX"; } else if (route.type == RouteType.BUS) { return "#" + route.shortName; } else { return route.longName; } } function populateTotals(vehicles) { const totals = { bus: 0, trax: 0, frontrunner: 0, } for (const vehicle of vehicles) { switch(vehicle.route.type) { case RouteType.BUS: totals.bus++; break; case RouteType.TRAM: totals.trax++; break; case RouteType.RAIL: totals.frontrunner++; break; } } document.getElementById("count-bus").innerText = totals.bus.toString() document.getElementById("count-trax").innerText = totals.trax.toString() document.getElementById("count-frontrunner").innerText = totals.frontrunner.toString() } let bannerVisible = false async function reload() { const bin = await fetch("/api").then((r) => r.arrayBuffer()); const { vehicles, info } = root .lookupType("VehicleFeed") .decode(new Uint8Array(bin)); lastUpdated = info.lastUpdate displayLastUpdated() populateTotals(vehicles) if (vehicles.length == 0) { if (!bannerVisible) { document.getElementById("banner").classList.remove("hidden") bannerVisible = true map.invalidateSize() } } else { if (bannerVisible) { document.getElementById("banner").classList.add("hidden") bannerVisible = false map.invalidateSize() } } // render vehicles on map clearMap(); vehicles.forEach((vehicle) => { L.marker([vehicle.lat, vehicle.lon], { zIndexOffset: vehicle.route.type == RouteType.TRAM || vehicle.route.type == RouteType.RAIL ? 4000 : 3000, icon: L.divIcon({ className: "", html: `
${ vehicle.route.type == RouteType.BUS ? ICONS.bus : vehicle.route.type == RouteType.TRAM ? ICONS.tram : vehicle.route.type == RouteType.RAIL ? ICONS.train : "" }
`, }), }) .addTo( ["92235", "3686", "87711"].includes(vehicle.route.id) ? brtLayer : vehicle.route.type == RouteType.TRAM ? traxLayer : vehicle.route.type == RouteType.RAIL ? frontRunnerLayer : busLayer ) .bindPopup( `${routeDesignator(vehicle.route)} to ${vehicle.headsign.replace( /^to /i, "" )}${vehicle.nearestStation ? `
@ ${vehicle.nearestStation?.name}` : ""}

vehicle #: ${vehicle.id}` ); }); } reload(); setInterval(reload, 5000); for (const checkbox of document.querySelectorAll(".layer-toggle")) { checkbox.addEventListener("input", (e) => { let layer; switch (e.target.name) { case "bus": layer = busLayer; break; case "brt": layer = brtLayer; break; case "trax": layer = traxLayer; break; case "frontrunner": layer = frontRunnerLayer; break; } if (e.target.checked) { layer?.addTo(map); } else { layer?.remove(); } }); } });