Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions website/demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ <h1 class="logo">gtfs-sqljs</h1>
<main class="container">
<section class="demo-header">
<h1>Interactive Demo</h1>
<p>Exploring Car Jaune GTFS data (Martinique, France)</p>
<p>Exploring Car Jaune GTFS data (La Réunion, France)</p>
<p class="operator-info">Operated by Transdev Outre-Mer and GIE Activ'</p>
</section>

<div id="loading" class="loading">
Expand All @@ -35,15 +36,18 @@ <h1>Interactive Demo</h1>

<div id="demo-content" style="display: none;">
<section class="demo-section">
<h2>Calendar Information</h2>
<div id="calendar-info" class="calendar-info">
<h2>Select Date</h2>
<div class="date-selector">
<input type="date" id="date-input" />
</div>
<div id="active-calendars" class="active-calendars">
<p>Loading...</p>
</div>
</section>

<section class="demo-section">
<h2>Routes</h2>
<p class="help-text">Click on a route to see its trips</p>
<p class="help-text">Click on a route to see its trips for the selected date</p>
<div id="routes-list" class="routes-list">
<p>Loading routes...</p>
</div>
Expand Down
191 changes: 108 additions & 83 deletions website/demo.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import initSqlJs from 'sql.js';
import { GtfsSqlJs } from 'gtfs-sqljs';
import type { Route, Trip, StopTime, Calendar } from 'gtfs-sqljs';
import type { Route, Trip, StopTime } from 'gtfs-sqljs';

let gtfs: GtfsSqlJs;
let selectedDate: string;

// Initialize the demo
async function init() {
Expand All @@ -23,8 +24,11 @@ async function init() {
loadingEl.style.display = 'none';
contentEl.style.display = 'block';

// Initialize date picker with today's date
initDatePicker();

// Render initial data
renderCalendarInfo();
renderActiveCalendars();
renderRoutes();
} catch (error) {
console.error('Error loading GTFS data:', error);
Expand All @@ -33,64 +37,55 @@ async function init() {
}
}

// Render calendar information
function renderCalendarInfo() {
const calendarInfoEl = document.getElementById('calendar-info')!;

const db = gtfs.getDatabase();
const stmt = db.prepare('SELECT * FROM calendar');

const calendars: Calendar[] = [];
while (stmt.step()) {
const row = stmt.getAsObject();
calendars.push({
service_id: String(row.service_id),
monday: Number(row.monday),
tuesday: Number(row.tuesday),
wednesday: Number(row.wednesday),
thursday: Number(row.thursday),
friday: Number(row.friday),
saturday: Number(row.saturday),
sunday: Number(row.sunday),
start_date: String(row.start_date),
end_date: String(row.end_date),
});
}
stmt.free();
// Initialize date picker
function initDatePicker() {
const dateInput = document.getElementById('date-input') as HTMLInputElement;

// Set to today's date
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
const todayStr = `${year}-${month}-${day}`;

dateInput.value = todayStr;
selectedDate = `${year}${month}${day}`; // YYYYMMDD format for GTFS

// Listen for date changes
dateInput.addEventListener('change', (e) => {
const target = e.target as HTMLInputElement;
const [y, m, d] = target.value.split('-');
selectedDate = `${y}${m}${d}`;
renderActiveCalendars();

// Reset trips section
document.getElementById('trips-section')!.style.display = 'none';
document.getElementById('stop-times-section')!.style.display = 'none';
});
}

if (calendars.length === 0) {
calendarInfoEl.innerHTML = '<p>No calendar data available</p>';
return;
}
// Render active calendars for selected date
function renderActiveCalendars() {
const activeCalendarsEl = document.getElementById('active-calendars')!;

const html = calendars.map(cal => {
const days = ['M', 'T', 'W', 'T', 'F', 'S', 'S'];
const values = [
cal.monday,
cal.tuesday,
cal.wednesday,
cal.thursday,
cal.friday,
cal.saturday,
cal.sunday
];

const daysHtml = days.map((day, i) =>
`<div class="day ${values[i] ? 'active' : 'inactive'}">${day}</div>`
).join('');
try {
const serviceIds = gtfs.getActiveServiceIds(selectedDate);

return `
<div class="calendar-card">
<h3>${cal.service_id}</h3>
<div class="calendar-days">${daysHtml}</div>
<p style="font-size: 0.875rem; color: var(--text-secondary); margin-top: 0.5rem;">
${formatDate(cal.start_date)} - ${formatDate(cal.end_date)}
</p>
</div>
`;
}).join('');
if (serviceIds.length === 0) {
activeCalendarsEl.innerHTML = '<p class="info-text">No active service calendars for this date</p>';
return;
}

calendarInfoEl.innerHTML = html;
const html = `
<p class="info-text">
<strong>Active calendars:</strong> ${serviceIds.join(', ')}
</p>
`;
activeCalendarsEl.innerHTML = html;
} catch (error) {
console.error('Error getting active calendars:', error);
activeCalendarsEl.innerHTML = '<p class="info-text">Error loading calendar information</p>';
}
}

// Render routes list
Expand Down Expand Up @@ -131,33 +126,74 @@ function renderRoutes() {
// Hide stop times section
stopTimesSectionEl.style.display = 'none';

// Get trips for this route
const trips = gtfs.getTripsByRoute(routeId);
// Get trips for this route on the selected date
const trips = gtfs.getTrips({
routeId: routeId,
date: selectedDate
});

if (trips.length === 0) {
tripsListEl.innerHTML = '<p>No trips found for this route</p>';
tripsListEl.innerHTML = '<p>No trips found for this route on the selected date</p>';
tripsSectionEl.style.display = 'block';
selectedRouteNameEl.textContent = routeName;
return;
}

// Render trips
const html = trips.map(trip => {
// Group trips by headsign and direction
interface TripGroup {
headsign: string;
directionId: number;
trips: Trip[];
}

const groupMap = new Map<string, TripGroup>();

trips.forEach(trip => {
const headsign = trip.trip_headsign || 'No headsign';
const direction = trip.direction_id !== undefined ? `Direction ${trip.direction_id}` : '';
const service = trip.service_id;
const directionId = trip.direction_id ?? 0;
const key = `${headsign}|${directionId}`;

if (!groupMap.has(key)) {
groupMap.set(key, {
headsign,
directionId,
trips: []
});
}

groupMap.get(key)!.trips.push(trip);
});

// Sort groups by direction, then by headsign
const groups = Array.from(groupMap.values()).sort((a, b) => {
if (a.directionId !== b.directionId) {
return a.directionId - b.directionId;
}
return a.headsign.localeCompare(b.headsign);
});

// Render grouped trips
const html = groups.map(group => {
const directionLabel = `Direction ${group.directionId}`;
const tripCount = group.trips.length;

return `
<div class="trip-card" onclick="showStopTimes('${trip.trip_id}', '${escapeHtml(headsign)}')">
<div class="trip-info">
<div class="trip-headsign">${escapeHtml(headsign)}</div>
<div class="trip-meta">
Trip ID: ${escapeHtml(trip.trip_id)}
${direction ? `• ${direction}` : ''}
• Service: ${escapeHtml(service)}
</div>
<div class="trip-group">
<div class="trip-group-header">
<div class="trip-headsign">${escapeHtml(group.headsign)}</div>
<div class="trip-meta">${directionLabel} • ${tripCount} trip${tripCount > 1 ? 's' : ''}</div>
</div>
<div class="trip-items">
${group.trips.map(trip => `
<div class="trip-card" onclick="showStopTimes('${trip.trip_id}', '${escapeHtml(group.headsign)}')">
<div class="trip-info">
<div class="trip-id">Trip: ${escapeHtml(trip.trip_id)}</div>
<div class="trip-service">Service: ${escapeHtml(trip.service_id)}</div>
</div>
<div>→</div>
</div>
`).join('')}
</div>
<div>→</div>
</div>
`;
}).join('');
Expand Down Expand Up @@ -210,17 +246,6 @@ function renderRoutes() {

// Utility functions

function formatDate(dateStr: string): string {
// YYYYMMDD -> DD/MM/YYYY
if (dateStr.length === 8) {
const year = dateStr.substring(0, 4);
const month = dateStr.substring(4, 6);
const day = dateStr.substring(6, 8);
return `${day}/${month}/${year}`;
}
return dateStr;
}

function escapeHtml(text: string): string {
const div = document.createElement('div');
div.textContent = text;
Expand Down
Loading