diff --git a/web/src/transit.ts b/web/src/transit.ts index 86432a7..40ed73e 100644 --- a/web/src/transit.ts +++ b/web/src/transit.ts @@ -1,367 +1,385 @@ -import './transit.css'; -import maplibregl from "maplibre-gl"; +import './transit.css' +import maplibregl from 'maplibre-gl' -const STOP_ZOOM_LEVEL = 15; -const VEHICLE_ZOOM_LEVEL = 12; +const STOP_ZOOM_LEVEL = 15 +const VEHICLE_ZOOM_LEVEL = 12 -const GTFS_FEED = "https://tracking.tfemf.uk/media/gtfs.json"; -const GTFS_RT_FEED = "https://tracking.tfemf.uk/media/gtfs-rt.json"; -const DEPARTURE_BOARD = "https://tracking.tfemf.uk/hafas/departureBoard"; +const GTFS_FEED = 'https://tracking.tfemf.uk/media/gtfs.json' +const GTFS_RT_FEED = 'https://tracking.tfemf.uk/media/gtfs-rt.json' +const DEPARTURE_BOARD = 'https://tracking.tfemf.uk/hafas/departureBoard' type Stop = { - name: string; - marker: maplibregl.Marker; - popup: maplibregl.Popup; + name: string + marker: maplibregl.Marker + popup: maplibregl.Popup } type Stops = { - [stop_id: string]: Stop; -}; + [stop_id: string]: Stop +} type Vehicle = { - marker: maplibregl.Marker; - popup: maplibregl.Popup; + marker: maplibregl.Marker + popup: maplibregl.Popup } type Vehicles = { - [vehicle_id: string]: Vehicle; -}; + [vehicle_id: string]: Vehicle +} type GTFSFeed = { stops: { [stop_id: string]: { - stop_id: string; - stop_code: string; - stop_name: string; - tts_stop_name?: string; - stop_desc?: string; - stop_lat: number; - stop_lon: number; - stop_url?: string; - }; - }; + stop_id: string + stop_code: string + stop_name: string + tts_stop_name?: string + stop_desc?: string + stop_lat: number + stop_lon: number + stop_url?: string + } + } routes: { [route_id: string]: { - route_id: string; - agency_id: string; - route_short_name: string; - route_long_name: string; - route_desc: string; - route_type: number; - route_url: string; - route_color: string; - route_text_color: string; - route_sort_order: number; + route_id: string + agency_id: string + route_short_name: string + route_long_name: string + route_desc: string + route_type: number + route_url: string + route_color: string + route_text_color: string + route_sort_order: number } - }; + } trips: { [trip_id: string]: { - route_id: string; - service_id: string; - trip_id: string; - trip_headsign: string; - trip_short_name: string; - direction_id: number; - block_id: string; - shape_id: string; + route_id: string + service_id: string + trip_id: string + trip_headsign: string + trip_short_name: string + direction_id: number + block_id: string + shape_id: string } } -}; +} type GTFSRTFeed = { - vehiclePositions: [{ - id: string; - vehicle: { - id: string; - label: string; - licensePlate?: string; - }; - trip?: { - trip_id: string; - routeId?: string; - scheduleRelationship: string; - }; - position: { - latitude: number; - longitude: number; - }; - stopId?: string; - currentStopSequence?: string; - currentStatus?: string; - timestamp: number; - }] -}; + vehiclePositions: [ + { + id: string + vehicle: { + id: string + label: string + licensePlate?: string + } + trip?: { + trip_id: string + routeId?: string + scheduleRelationship: string + } + position: { + latitude: number + longitude: number + } + stopId?: string + currentStopSequence?: string + currentStatus?: string + timestamp: number + }, + ] +} type HAFASDepartureBoard = { - requestId: string; - Departure: [{ - Product: [{ - operatorInfo: { - name: string; - id: string; - }, - catOut: string; - }], - name: string; - direction: string; - time: string; - date: string; - rtTime: string; - rtDate: string; - rtPlatform?: { - text: string; - hidden: boolean; - }; - Notes: { - Note: [{ - value: string; - }] - }; - cancelled: boolean; - }] -}; + requestId: string + Departure: [ + { + Product: [ + { + operatorInfo: { + name: string + id: string + } + catOut: string + }, + ] + name: string + direction: string + time: string + date: string + rtTime: string + rtDate: string + rtPlatform?: { + text: string + hidden: boolean + } + Notes: { + Note: [ + { + value: string + }, + ] + } + cancelled: boolean + }, + ] +} export default class TransitInfo { - _map: maplibregl.Map; + _map: maplibregl.Map - stops: Stops; - vehicles: Vehicles; - routes: GTFSFeed['routes']; - trips: GTFSFeed['trips']; + stops: Stops + vehicles: Vehicles + routes: GTFSFeed['routes'] + trips: GTFSFeed['trips'] constructor(map: maplibregl.Map) { - this._map = map; + this._map = map - this.stops = {}; - this.vehicles = {}; - this.routes = {}; - this.trips = {}; + this.stops = {} + this.vehicles = {} + this.routes = {} + this.trips = {} - this.loadGTFSFeed(); - this.updateGTFSRTFeed(); + this.loadGTFSFeed() + this.updateGTFSRTFeed() - this._map.on('zoom', this.updateStopVisibility.bind(this)); + this._map.on('zoom', this.updateStopVisibility.bind(this)) } loadGTFSFeed() { fetch(GTFS_FEED) - .then(response => response.json()) + .then((response) => response.json()) .then((data: GTFSFeed) => { - this.routes = data.routes; - this.trips = data.trips; - this.loadStops(data.stops); - this.updateStopVisibility(); + this.routes = data.routes + this.trips = data.trips + this.loadStops(data.stops) + this.updateStopVisibility() + }) + .catch((error) => { + console.error('Failed to load GTFS feed', error) }) - .catch(error => { - console.error("Failed to load GTFS feed", error); - }); } updateGTFSRTFeed() { fetch(GTFS_RT_FEED) - .then(response => response.json()) + .then((response) => response.json()) .then((data: GTFSRTFeed) => { - this.updateVehicles(data.vehiclePositions); - this.updateStopVisibility(); + this.updateVehicles(data.vehiclePositions) + this.updateStopVisibility() + }) + .catch((error) => { + console.error('Failed to load GTFS-RT feed', error) }) - .catch(error => { - console.error("Failed to load GTFS-RT feed", error); - }); - setTimeout(this.updateGTFSRTFeed.bind(this), 2500); + setTimeout(this.updateGTFSRTFeed.bind(this), 2500) } loadStops(stops: GTFSFeed['stops']) { - this.clearStops(); + this.clearStops() - this.stops = {}; + this.stops = {} Object.values(stops).forEach((stop) => { - const el = document.createElement('div'); - el.className = 'marker-stop'; - el.innerText = stop.stop_name; + const el = document.createElement('div') + el.className = 'marker-stop' + el.innerText = stop.stop_name const popup = new maplibregl.Popup({ className: 'popup-stop', - offset: 25 + offset: 25, }) - .setMaxWidth("80vw") + .setMaxWidth('80vw') .on('open', () => { - this.stopPopupOpen(stop.stop_id); - }); + this.stopPopupOpen(stop.stop_id) + }) const marker = new maplibregl.Marker({ - element: el + element: el, }) .setLngLat([stop.stop_lon, stop.stop_lat]) .setPopup(popup) - .addTo(this._map); + .addTo(this._map) this.stops[stop.stop_id] = { name: stop.stop_name, marker: marker, - popup: popup - }; - }); + popup: popup, + } + }) } updateVehicles(vehicles: GTFSRTFeed['vehiclePositions']) { - const seenVehicles: string[] = []; + const seenVehicles: string[] = [] vehicles.forEach((vehicle) => { - seenVehicles.push(vehicle.id); + if (vehicle.vehicle.id == '044ae755-742f-40d8-bdf1-55f1984e2ff0') + // Ignore Q + return + + seenVehicles.push(vehicle.id) if (vehicle.id in this.vehicles) { - this.vehicles[vehicle.id].marker - .setLngLat([vehicle.position.longitude, vehicle.position.latitude]); - this.vehicles[vehicle.id].popup - .setHTML(this.makeVehiclePopup(vehicle)); + this.vehicles[vehicle.id].marker.setLngLat([ + vehicle.position.longitude, + vehicle.position.latitude, + ]) + this.vehicles[vehicle.id].popup.setHTML(this.makeVehiclePopup(vehicle)) } else { - const el = document.createElement('div'); - el.className = 'marker-vehicle'; + const el = document.createElement('div') + el.className = 'marker-vehicle' const popup = new maplibregl.Popup({ className: 'popup-vehicle', - offset: 25 - }) - .setHTML(this.makeVehiclePopup(vehicle)); + offset: 25, + }).setHTML(this.makeVehiclePopup(vehicle)) const marker = new maplibregl.Marker({ - element: el + element: el, }) .setLngLat([vehicle.position.longitude, vehicle.position.latitude]) .setPopup(popup) - .addTo(this._map); + .addTo(this._map) this.vehicles[vehicle.id] = { marker: marker, popup: popup, } } - }); + }) Object.entries(this.vehicles).forEach(([vehicle_id, vehicle]) => { if (!seenVehicles.includes(vehicle_id)) { - vehicle.marker.remove(); - delete this.vehicles[vehicle_id]; + vehicle.marker.remove() + delete this.vehicles[vehicle_id] } }) } makeVehiclePopup(vehicle: GTFSRTFeed['vehiclePositions'][0]) { - const updated = new Date(vehicle.timestamp * 1000); + const updated = new Date(vehicle.timestamp * 1000) let html = `