Skip to main content
Glama
stationService.ts5.07 kB
import { GTFSManager } from "./gtfsManager.js"; let stopsData: any[] = []; let transfersData: any[] = []; let routesData: any[] = []; let isDataLoaded = false; let currentGTFSSource: 'local' | 'supplemented' | 'regular' = 'local'; export class StationMatcher { static normalizeStationName(name: string): string { return name.toLowerCase() .trim() .replace(/\bav\b/g, 'avenue') .replace(/\bst\b/g, 'street') .replace(/\bpkwy\b/g, 'parkway') .replace(/\bblvd\b/g, 'boulevard') .replace(/\bsq\b/g, 'square') .replace(/[\-\/]/g, ' ') .replace(/[\(\)]/g, '') .replace(/\s+/g, ' ') .trim(); } static findBestMatches(query: string, stops: any[]): any[] { const normalizedQuery = this.normalizeStationName(query); const originalQuery = query.toLowerCase().trim(); let matches = stops.filter(stop => stop.stop_name?.toLowerCase().trim() === originalQuery && stop.location_type === '1' ); if (matches.length === 0) { matches = stops.filter(stop => this.normalizeStationName(stop.stop_name) === normalizedQuery && stop.location_type === '1' ); } if (matches.length > 1) { const majorHubs = [ 'times sq-42 st', 'grand central-42 st', '42 st-port authority bus terminal', '42 st-bryant pk', 'union sq-14 st', '14 st-union sq', 'herald sq-34 st', 'penn station-34 st', 'columbus circle-59 st', 'lexington av-59 st' ]; const hubMatches = matches.filter(stop => majorHubs.includes(stop.stop_name.toLowerCase()) ); if (hubMatches.length > 0) { matches = hubMatches; } } if (matches.length === 0 && normalizedQuery.length > 3) { const partialMatches = stops.filter(stop => { const normalizedStopName = this.normalizeStationName(stop.stop_name); return normalizedStopName.includes(normalizedQuery) && stop.location_type === '1'; }); // Prioritize exact word matches over partial matches const exactWordMatches = partialMatches.filter(stop => { const normalizedStopName = this.normalizeStationName(stop.stop_name); const words = normalizedStopName.split(' '); return words.some(word => word === normalizedQuery); }); if (exactWordMatches.length > 0) { matches = exactWordMatches; } else { matches = partialMatches.sort((a, b) => { const aNorm = this.normalizeStationName(a.stop_name); const bNorm = this.normalizeStationName(b.stop_name); const aStarts = aNorm.startsWith(normalizedQuery); const bStarts = bNorm.startsWith(normalizedQuery); if (aStarts && !bStarts) return -1; if (!aStarts && bStarts) return 1; return a.stop_name.length - b.stop_name.length; }); } } return matches; } } export async function ensureDataLoaded() { if (isDataLoaded) { return; } try { try { const supplementedData = await GTFSManager.getGTFSData('supplemented'); stopsData = supplementedData.stops; transfersData = supplementedData.transfers; routesData = supplementedData.routes; currentGTFSSource = 'supplemented'; isDataLoaded = true; return; } catch (error) { console.warn('Supplemented GTFS unavailable, trying regular GTFS:', error); } try { const regularData = await GTFSManager.getGTFSData('regular'); stopsData = regularData.stops; transfersData = regularData.transfers; routesData = regularData.routes; currentGTFSSource = 'regular'; isDataLoaded = true; return; } catch (error) { console.error('Regular GTFS unavailable'); throw new Error('Failed to load GTFS data from both supplemented and regular sources'); } } catch (error) { console.error('Error loading GTFS data occurred'); throw new Error('GTFS data loading failed'); } } // Keep the old function for backward compatibility export async function loadStaticGTFS() { return ensureDataLoaded(); } export function getStopsData(): any[] { return stopsData; } export function getTransfersData(): any[] { return transfersData; } export function getRoutesData(): any[] { return routesData; } export async function getGTFSSourceInfo(): Promise<{ source: string; status: any }> { return { source: currentGTFSSource, status: await GTFSManager.getCacheStatus() }; } export async function refreshGTFS(type?: 'regular' | 'supplemented'): Promise<void> { if (type) { await GTFSManager.forceRefresh(type); } else { await GTFSManager.forceRefresh('supplemented'); await GTFSManager.forceRefresh('regular'); } isDataLoaded = false; // Force reload await ensureDataLoaded(); } export function clearGTFSData() { stopsData = []; transfersData = []; routesData = []; isDataLoaded = false; currentGTFSSource = 'local'; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sasabasara/where_is_my_train_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server