Loading weather data...

MBTA Routes

Transit routes from Fall River to Boston · Real-time schedules and predictions

On time Delayed Cancelled

To Boston

Inbound routes

Loading routes…

MBTA API

From Boston

Outbound routes

Loading routes…

MBTA API

Loading MBTA data… Data from Massachusetts Bay Transportation Authority
(function() { 'use strict'; // Build-time data embedded so static page shows data without runtime fetch window.FRNA_MBTA_BUILD_DATA = {}; // MBTA API Configuration const MBTA_API_BASE = 'https://api-v3.mbta.com'; const MBTA_API_KEY = '01df058309eb41ad8981a0a6e0be0953'; // Route type mappings const ROUTE_TYPES = { 0: { name: 'Light Rail', class: 'route-light-rail' }, 1: { name: 'Heavy Rail', class: 'route-subway' }, 2: { name: 'Commuter Rail', class: 'route-commuter-rail' }, 3: { name: 'Bus', class: 'route-bus' }, 4: { name: 'Ferry', class: 'route-ferry' } }; // Known Fall River to Boston routes const FALL_RIVER_ROUTES = [ 'CR-Newburyport', 'CR-Kingston', 'CR-Providence', 'CR-Middleborough', 'CR-Greenbush', 'CR-Fairmount', 'CR-Fitchburg', 'CR-Worcester', 'CR-Franklin', 'CR-Haverhill', 'CR-Lowell', 'CR-Needham' ]; function renderMBTABuildData() { var build = window.FRNA_MBTA_BUILD_DATA; if (!build || !build.mbta || !build.mbta.routes || build.mbta.routes.length === 0) return false; var routes = build.mbta.routes; var inboundHtml = []; var outboundHtml = []; var lastUpdated = build.mbta.last_updated || ''; function fmtTime(iso) { if (!iso) return '—'; try { var d = new Date(iso); return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } catch (e) { return iso; } } routes.forEach(function(route) { var name = route.name || route.short_name || route.id || 'Route'; var typeClass = (route.type === 'commuter_rail') ? 'route-commuter-rail' : 'route-bus'; var preds = route.predictions || []; var inboundPreds = preds.filter(function(p) { return p.direction_id === 1; }); var outboundPreds = preds.filter(function(p) { return p.direction_id === 0; }); if (inboundPreds.length > 0) { var nextTime = fmtTime(inboundPreds[0].arrival_time || inboundPreds[0].departure_time); inboundHtml.push('
' + '
' + escapeHtml(name) + '
' + '
' + 'Commuter Rail' + nextTime + '
' + '
On time
'); } if (outboundPreds.length > 0) { var nextTime = fmtTime(outboundPreds[0].arrival_time || outboundPreds[0].departure_time); outboundHtml.push('
' + '
' + escapeHtml(name) + '
' + '
' + 'Commuter Rail' + nextTime + '
' + '
On time
'); } }); var inboundEl = document.getElementById('mbta-inbound-data'); var outboundEl = document.getElementById('mbta-outbound-data'); var statusEl = document.getElementById('mbta-status'); if (inboundEl) inboundEl.innerHTML = inboundHtml.length > 0 ? inboundHtml.join('') : '
🚆

No inbound routes

'; if (outboundEl) outboundEl.innerHTML = outboundHtml.length > 0 ? outboundHtml.join('') : '
🚆

No outbound routes

'; if (statusEl) statusEl.innerHTML = 'Build-time data · ' + (lastUpdated ? 'Updated ' + lastUpdated : '') + 'Data from MBTA'; return true; } function initializeMBTAData() { fetchMBTARoutes(); setInterval(fetchMBTARoutes, 60000); // Update every minute } async function fetchMBTARoutes() { try { const inboundEl = document.getElementById('mbta-inbound-data'); const outboundEl = document.getElementById('mbta-outbound-data'); const statusEl = document.getElementById('mbta-status'); // Update status if (statusEl) { const now = new Date(); statusEl.innerHTML = '' + '' + 'Updated ' + now.toLocaleTimeString() + '' + 'Data from Massachusetts Bay Transportation Authority'; } // Fetch routes that serve Fall River area const routesUrl = `${MBTA_API_BASE}/routes?api_key=${MBTA_API_KEY}&filter[type]=2`; // Commuter Rail only const response = await fetch(routesUrl); if (!response.ok) throw new Error('HTTP ' + response.status); const data = await response.json(); const routes = data.data || []; // Filter routes that might serve Fall River to Boston const relevantRoutes = routes.filter(route => FALL_RIVER_ROUTES.includes(route.id) || route.attributes.long_name?.toLowerCase().includes('providence') || route.attributes.long_name?.toLowerCase().includes('newburyport') ); // For each route, fetch schedules/predictions const routePromises = relevantRoutes.map(route => fetchRouteDetails(route)); const routeDetails = await Promise.allSettled(routePromises); const processedRoutes = routeDetails .filter(result => result.status === 'fulfilled') .map(result => result.value) .filter(route => route !== null); // Separate inbound (to Boston) and outbound (from Boston) const inboundRoutes = processedRoutes.filter(route => route.direction === 'inbound' || route.direction_id === 0 ); const outboundRoutes = processedRoutes.filter(route => route.direction === 'outbound' || route.direction_id === 1 ); // Render routes inboundEl.innerHTML = inboundRoutes.length > 0 ? inboundRoutes.map(route => generateRouteCard(route)).join('') : '
🚆

No inbound routes

Check back soon

'; outboundEl.innerHTML = outboundRoutes.length > 0 ? outboundRoutes.map(route => generateRouteCard(route)).join('') : '
🚆

No outbound routes

Check back soon

'; } catch (error) { ; showMBTAError('Cannot load MBTA data. ' + (error.message || '')); } } async function fetchRouteDetails(route) { try { // Fetch predictions for this route const predictionsUrl = `${MBTA_API_BASE}/predictions?api_key=${MBTA_API_KEY}&filter[route]=${route.id}&include=trip,stop&sort=departure_time`; const response = await fetch(predictionsUrl); if (!response.ok) return null; const data = await response.json(); const predictions = data.data || []; // Get next few departures const now = new Date(); const upcoming = predictions .filter(pred => { const depTime = new Date(pred.attributes.departure_time); return depTime > now; }) .slice(0, 3); return { ...route, predictions: upcoming, nextDeparture: upcoming.length > 0 ? upcoming[0] : null }; } catch (error) { ; return route; // Return route without predictions } } function generateRouteCard(route) { const routeType = ROUTE_TYPES[route.attributes.type] || { name: 'Unknown', class: 'route-bus' }; const routeName = route.attributes.long_name || route.attributes.short_name || route.id; let status = 'on-time'; let statusText = 'On time'; let nextTime = '—'; if (route.nextDeparture) { const depTime = new Date(route.nextDeparture.attributes.departure_time); const now = new Date(); const diffMinutes = Math.floor((depTime - now) / (1000 * 60)); if (diffMinutes < 0) { status = 'delayed'; statusText = 'Departed'; } else if (diffMinutes <= 5) { status = 'delayed'; statusText = 'Soon'; } else { nextTime = depTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } } return `
${escapeHtml(routeName)}
${routeType.name} ${nextTime}
${statusText}
`; } function escapeHtml(s) { if (!s) return ''; var div = document.createElement('div'); div.textContent = s; return div.innerHTML; } function showMBTAError(message) { const statusEl = document.getElementById('mbta-status'); if (statusEl) { statusEl.innerHTML = '' + '' + 'Error loading data'; } const msg = (typeof message === 'string') ? message : 'Error loading data'; const safe = (msg + '').replace(//g, '>'); const inboundEl = document.getElementById('mbta-inbound-data'); const outboundEl = document.getElementById('mbta-outbound-data'); const errHtml = '
' + '
' + '

' + safe + '

' + '

Retrying every 60 sec

'; if (inboundEl) inboundEl.innerHTML = errHtml; if (outboundEl) outboundEl.innerHTML = errHtml; } function runMBTAInit() { // Show build-time data immediately so static page is never blank if (renderMBTABuildData()) { // Still start live refresh; it will overwrite when API succeeds initializeMBTAData(); } else { initializeMBTAData(); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', runMBTAInit); } else { runMBTAInit(); } })();