Interactive Demo
-Exploring Car Jaune GTFS data (La Réunion, France)
-Operated by Transdev Outre-Mer and GIE Activ'
+
+
+
+
+
@@ -31,11 +39,25 @@
Interactive Demo
-
Error loading GTFS data. Please refresh the page.
+Error loading GTFS data. Please refresh the page.
-
+
+
+
+
+
+
+
+
Transit Agencies
+
+
+ Loading agencies...
+Download the processed GTFS data as a SQLite database file
+Select Date
@@ -43,12 +65,6 @@
-
diff --git a/website/demo.ts b/website/demo.ts
index c360e87..41bb08c 100644
--- a/website/demo.ts
+++ b/website/demo.ts
@@ -1,25 +1,46 @@
import initSqlJs from 'sql.js';
import { GtfsSqlJs } from 'gtfs-sqljs';
-import type { Route, Trip, StopTime } from 'gtfs-sqljs';
+import type { Route, Trip, StopTime, Agency } from 'gtfs-sqljs';
let gtfs: GtfsSqlJs;
let selectedDate: string;
+let SQL: any;
-// Initialize the demo
-async function init() {
+// Convert https URL to proxy URL
+function getProxyUrl(httpsUrl: string): string {
+ if (!httpsUrl.startsWith('https://')) {
+ throw new Error('URL must start with https://');
+ }
+ // Remove https:// prefix
+ const withoutProtocol = httpsUrl.substring(8);
+ return `https://gtfs-proxy.sys-dev-run.re/proxy/${withoutProtocol}`;
+}
+
+// Load GTFS from URL or default
+async function loadGTFS(url?: string) {
const loadingEl = document.getElementById('loading')!;
const errorEl = document.getElementById('error')!;
+ const errorMessageEl = document.getElementById('error-message')!;
const contentEl = document.getElementById('demo-content')!;
try {
- // Initialize SQL.js with CDN WASM file
- const SQL = await initSqlJs({
- locateFile: (filename) => `https://sql.js.org/dist/${filename}`
- });
+ // Show loading, hide error and content
+ loadingEl.style.display = 'block';
+ errorEl.style.display = 'none';
+ contentEl.style.display = 'none';
+
+ // Determine source
+ let source: string;
+ if (url) {
+ source = getProxyUrl(url);
+ } else {
+ // Load default car-jaune.zip from public folder
+ source = './car-jaune.zip';
+ }
- // Load GTFS data (Vite will resolve this from public folder)
+ // Load GTFS data
// Skip shapes.txt to reduce memory usage and improve load time
- gtfs = await GtfsSqlJs.fromZip('./car-jaune.zip', {
+ gtfs = await GtfsSqlJs.fromZip(source, {
SQL,
skipFiles: ['shapes.txt']
});
@@ -35,12 +56,44 @@ async function init() {
setupDownloadButton();
// Render initial data
+ renderAgencies();
renderActiveCalendars();
renderRoutes();
} catch (error) {
console.error('Error loading GTFS data:', error);
loadingEl.style.display = 'none';
errorEl.style.display = 'block';
+ errorMessageEl.textContent = `Error loading GTFS data: ${error instanceof Error ? error.message : String(error)}`;
+ }
+}
+
+// Initialize the demo
+async function init() {
+ try {
+ // Initialize SQL.js with CDN WASM file
+ SQL = await initSqlJs({
+ locateFile: (filename) => `https://sql.js.org/dist/${filename}`
+ });
+
+ // Setup URL input handler
+ const loadBtn = document.getElementById('load-gtfs-btn')!;
+ const urlInput = document.getElementById('gtfs-url-input') as HTMLInputElement;
+
+ loadBtn.addEventListener('click', () => {
+ const url = urlInput.value.trim();
+ if (url) {
+ loadGTFS(url);
+ }
+ });
+
+ // Load default GTFS (car-jaune.zip)
+ await loadGTFS();
+ } catch (error) {
+ console.error('Error initializing demo:', error);
+ const loadingEl = document.getElementById('loading')!;
+ const errorEl = document.getElementById('error')!;
+ loadingEl.style.display = 'none';
+ errorEl.style.display = 'block';
}
}
@@ -71,6 +124,38 @@ function initDatePicker() {
});
}
+// Render agencies
+function renderAgencies() {
+ const agenciesListEl = document.getElementById('agencies-list')!;
+
+ try {
+ const agencies = gtfs.getAgencies();
+
+ if (agencies.length === 0) {
+ agenciesListEl.innerHTML = '
Select Date
Loading...
-
-
Download the processed GTFS data as a SQLite database file
-No agency information available
'; + return; + } + + const html = agencies.map((agency): string => { + return ` +
+
+ `;
+ }).join('');
+
+ agenciesListEl.innerHTML = html;
+ } catch (error) {
+ console.error('Error getting agencies:', error);
+ agenciesListEl.innerHTML = '${escapeHtml(agency.agency_name)}
+ ${agency.agency_url ? `${escapeHtml(agency.agency_url)}
` : ''} + ${agency.agency_timezone ? `Timezone: ${escapeHtml(agency.agency_timezone)}
` : ''} + ${agency.agency_lang ? `Language: ${escapeHtml(agency.agency_lang)}
` : ''} + ${agency.agency_phone ? `Phone: ${escapeHtml(agency.agency_phone)}
` : ''} + ${agency.agency_email ? `Email: ${escapeHtml(agency.agency_email)}
` : ''} +Error loading agency information
'; + } +} + // Render active calendars for selected date function renderActiveCalendars() { const activeCalendarsEl = document.getElementById('active-calendars')!; diff --git a/website/style.css b/website/style.css index 4842e2d..cb9893f 100644 --- a/website/style.css +++ b/website/style.css @@ -203,10 +203,75 @@ footer { margin-bottom: 2rem; } -.operator-info { - color: var(--text-secondary); +.url-input-section { + margin-top: 1.5rem; + display: flex; + flex-direction: column; + gap: 0.75rem; + max-width: 700px; + margin-left: auto; + margin-right: auto; +} + +.url-input-section label { + font-weight: 500; + color: var(--text-primary); + text-align: left; +} + +.url-input-section input[type="url"] { + padding: 0.75rem 1rem; + border: 2px solid var(--border); + border-radius: 0.5rem; + font-size: 1rem; + font-family: inherit; + width: 100%; + transition: border-color 0.2s; +} + +.url-input-section input[type="url"]:focus { + outline: none; + border-color: var(--primary-color); +} + +.url-input-section button { + align-self: flex-start; +} + +.agencies-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 1.5rem; +} + +.agency-card { + background: white; + padding: 1.5rem; + border-radius: 0.5rem; + border: 1px solid var(--border); + box-shadow: var(--shadow); +} + +.agency-card h3 { + font-size: 1.25rem; + margin: 0 0 1rem 0; + color: var(--primary-color); +} + +.agency-card p { + margin: 0.5rem 0; font-size: 0.95rem; - margin-top: 0.5rem; + color: var(--text-primary); +} + +.agency-card a { + color: var(--primary-color); + text-decoration: none; + word-break: break-all; +} + +.agency-card a:hover { + text-decoration: underline; } .loading { @@ -248,6 +313,15 @@ footer { border: 1px solid var(--border); } +.date-section { + padding: 1.25rem 2rem; +} + +.date-section h2 { + font-size: 1.25rem; + margin-bottom: 1rem; +} + .help-text { color: var(--text-secondary); font-style: italic; @@ -255,17 +329,17 @@ footer { } .date-selector { - margin-bottom: 1.5rem; + margin-bottom: 1rem; } .date-selector input[type="date"] { - padding: 0.75rem 1rem; + padding: 0.6rem 0.875rem; border: 2px solid var(--border); border-radius: 0.5rem; - font-size: 1rem; + font-size: 0.95rem; font-family: inherit; width: 100%; - max-width: 300px; + max-width: 250px; transition: border-color 0.2s; } @@ -275,7 +349,7 @@ footer { } .active-calendars { - margin-top: 1rem; + margin-top: 0.75rem; } .info-text { @@ -285,8 +359,8 @@ footer { } .download-section { - margin-top: 2rem; text-align: center; + padding: 1.5rem 2rem; } .download-section .button { @@ -295,6 +369,7 @@ footer { .download-section .help-text { margin-top: 0.5rem; + margin-bottom: 0; font-size: 0.875rem; }