From 81bd61349a7713d6104ffeaf64563c7071ffb55e Mon Sep 17 00:00:00 2001 From: iamlegendharsh Date: Thu, 19 Mar 2026 20:03:23 +0530 Subject: [PATCH 1/2] Fix #109: Dynamically set agency in QueryRenderer for TTC support --- src/App.jsx | 28 +++++++++----- src/components/ControlPanel.jsx | 9 ++++- src/components/Map.jsx | 65 ++++++++++++++++++--------------- 3 files changed, 60 insertions(+), 42 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 1ac629e..1f44eb0 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -3,10 +3,7 @@ import 'react-widgets/dist/css/react-widgets.css'; import 'react-toggle/style.css'; import React, { Component } from 'react'; -import { - QueryRenderer, - graphql, -} from 'react-relay'; +import { QueryRenderer, graphql } from 'react-relay'; import Moment from 'moment/moment'; import momentLocalizer from 'react-widgets-moment'; import Map from './components/Map'; @@ -14,8 +11,6 @@ import './styles/App.css'; import './styles/Zoom.css'; import environment from './relayEnv'; -// needed to render DateTime component -// https://jquense.github.io/react-widgets/localization/ Moment.locale('en'); momentLocalizer(); @@ -24,9 +19,14 @@ class App extends Component { super(); this.state = { environment, + agency: 'muni', // ✅ default agency in state }; } + handleAgencyChange = (selectedAgency) => { + this.setState({ agency: selectedAgency }); // ✅ triggers re-query + } + loadRelay() { return ( { - console.log(props); if (error) { return
{error.message}
; } - return (
{props && }
); + return ( +
+ {props && ( + + )} +
+ ); }} /> ); @@ -61,4 +69,4 @@ class App extends Component { } } -export default App; +export default App; \ No newline at end of file diff --git a/src/components/ControlPanel.jsx b/src/components/ControlPanel.jsx index bb2163f..07ffe34 100644 --- a/src/components/ControlPanel.jsx +++ b/src/components/ControlPanel.jsx @@ -64,7 +64,12 @@ class ControlPanel extends Component { sortedRoutes: this.getCityRoutes(city.value), }, () => { this.props - .setMapLocation(city.value.latitude, city.value.longitude, 11); + .setMapLocation( + city.value.latitude, + city.value.longitude, + 11, + city.value.name, // ✅ FIXED: pass city name so Map.jsx can resolve correct agencyId + ); this.props.clearSelectedRoutes(); this.refetch(); }) @@ -99,4 +104,4 @@ ControlPanel.propTypes = { clearSelectedRoutes: propTypes.func.isRequired, }; -export default ControlPanel; +export default ControlPanel; \ No newline at end of file diff --git a/src/components/Map.jsx b/src/components/Map.jsx index 376e383..5be4eda 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -17,12 +17,16 @@ import { import ControlPanel from './ControlPanel'; import Stop from './Stop'; +// ✅ Maps city names to agency IDs used in GraphQL API +const agencyMap = { + 'San Francisco': 'muni', + 'Toronto': 'ttc', +}; class Map extends Component { constructor() { super(); this.state = { - // Viewport settings that is shared between mapbox and deck.gl viewport: { width: (2 * window.innerWidth) / 3, height: window.innerHeight, @@ -39,6 +43,7 @@ class Map extends Component { showStops: true, selectedStops: [], subroute: null, + agency: 'muni', }; } @@ -52,20 +57,12 @@ class Map extends Component { window.removeEventListener('resize', this.updateDimensions.bind(this)); } - /** - * given the two selected stop sids, returns a line segment - * between them - */ getRouteBetweenStops(routeStops, stops) { const stopSids = stops.map(stop => stop.sid); stopSids.sort((a, b) => a - b); const route = routeStops.map(stop => [stop.lon, stop.lat]); const startingPointStop = new Stop(routeStops.find(stop => stop.sid === stopSids[0])); const endingPointStop = new Stop(routeStops.find(stop => stop.sid === stopSids[1])); - /* - * if either value is undefined, it means user selected another stop on another route. - * so clear all stops and subroute - */ if (startingPointStop.isUndefined() || endingPointStop.isUndefined()) { this.setState({ subroute: null, selectedStops: [] }); return; @@ -77,14 +74,10 @@ class Map extends Component { this.setState({ subroute, selectedStops: stops }); } - /** - * sets stop sids based on selected stops. - * Stores up to two stops sids. Used to draw subroutes - */ getStopInfo(route, stopCoordinates) { let stops = [...this.state.selectedStops]; const station = route.stops.find(currentStop => currentStop.lon === stopCoordinates[0] - && currentStop.lat === stopCoordinates[1]); + && currentStop.lat === stopCoordinates[1]); const stopInfo = new Stop(); stopInfo.setCoordinates(stopCoordinates); stopInfo.sid = station.sid; @@ -102,20 +95,26 @@ class Map extends Component { } } - /* - * Change location when selecting another city, passed into ControlPanel - * latitude: coordinate to centre on - * longitude: coordinate to centre on - * zoom: level of zoom to set to (optional) - */ - setMapLocation(latitude, longitude, zoom) { + // ✅ FIXED: accepts city as 4th param, updates agency, notifies App.js + setMapLocation(latitude, longitude, zoom, city) { + const newAgency = agencyMap[city] || this.state.agency; + this.setState({ viewport: Object.assign(this.state.viewport, { latitude, longitude, zoom: zoom || this.state.viewport.zoom, }), + agency: newAgency, + selectedStops: [], + subroute: null, }); + + if (this.props.onAgencyChange) { + this.props.onAgencyChange(newAgency); + } + + this.clearSelectedRoutes(); } updateDimensions() { @@ -128,7 +127,6 @@ class Map extends Component { } displayVehicleInfo(info) { - /* calls parent' onMarkerClick function to show pop-up to display vehicle id & heading info */ if (info && info.object && info.object.vid && info.object.heading) { this.setState({ popup: { @@ -178,20 +176,25 @@ class Map extends Component { renderMap() { const onViewportChange = viewport => this.setState({ viewport }); - const { trynState } = this.props.trynState || {}; - const { routes } = trynState || {}; + + // ✅ FIXED: correct destructuring — was previously double-destructuring + const trynState = this.props.trynState || {}; + const { routes } = trynState; + const { viewport, geojson, subroute, selectedStops, } = this.state; + const subRouteLayer = subroute && getSubRoutesLayer(subroute); - // selectedRouteNames are the route names in the GeoJSON file + const selectedRouteNames = new Set(); this.selectedRoutes .forEach(route => selectedRouteNames.add(route.properties.name)); - // maps API route name to GeoJSON route name + const routeNameMapping = { KT: 'K/T', }; + const routeLayers = (routes || []) .filter(route => selectedRouteNames.has(routeNameMapping[route.rid] || route.rid)) .reduce((layers, route) => [ @@ -205,7 +208,9 @@ class Map extends Component { subRouteLayer, ...getVehicleMarkersLayer(route, info => this.displayVehicleInfo(info)), ], []); + routeLayers.push(getRoutesLayer(geojson)); + return ( - {/* React Map GL Popup component displays vehicle ID & heading info */} {this.state.popup.coordinates ? ( this.filterRoutes(route)} toggleStops={() => this.toggleStops()} - setMapLocation={(latitude, longitude, zoom) => - this.setMapLocation(latitude, longitude, zoom)} + setMapLocation={(latitude, longitude, zoom, city) => + this.setMapLocation(latitude, longitude, zoom, city)} refetch={data => this.refetch(data)} clearSelectedRoutes={() => this.clearSelectedRoutes()} /> @@ -266,6 +270,7 @@ Map.propTypes = { propTypes.arrayOf(propTypes.object), ]).isRequired, relay: propTypes.element.isRequired, + onAgencyChange: propTypes.func.isRequired, // ✅ new required prop }; export default createRefetchContainer( @@ -302,4 +307,4 @@ export default createRefetchContainer( ...Map_trynState } `, -); +); \ No newline at end of file From 1c081454d5123680bebe4efb25656c78b6362d2e Mon Sep 17 00:00:00 2001 From: iamlegendharsh Date: Thu, 19 Mar 2026 20:26:21 +0530 Subject: [PATCH 2/2] Fix #110: Correct vehicle heading angle formula for DeckGL IconLayer --- src/helpers/Route.jsx | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/helpers/Route.jsx b/src/helpers/Route.jsx index 5612add..4f7a55e 100644 --- a/src/helpers/Route.jsx +++ b/src/helpers/Route.jsx @@ -21,21 +21,18 @@ const TTC_EXPRESS_COLOR = [0, 165, 79]; /* https://en.wikipedia.org/wiki/Template:MUNI_color */ const MUNI_COLOR = [ { line: 'E', color: [102, 102, 102] }, - // { line: 'F', color: [240, 230, 140] }, { line: 'J', color: [250, 166, 52] }, { line: 'KT', color: [86, 155, 190] }, { line: 'L', color: [146, 39, 143] }, { line: 'M', color: [0, 135, 82] }, { line: 'N', color: [0, 83, 155] }, { line: 'S', color: [255, 204, 0] }, - // { line: 'T', color: [211, 18, 69] }, { line: 'PM', color: [83, 161, 177] }, { line: 'PH', color: [71, 176, 153] }, { line: 'C', color: [116, 167, 178] }, { line: 'default', color: [204, 0, 51] }, ]; -/* changes color of selected stop along a route */ function selectedStopColor(stop, selectedStops) { const selectedStopsIds = selectedStops.map(currentstop => currentstop.sid); @@ -44,9 +41,8 @@ function selectedStopColor(stop, selectedStops) { } return [255, 0, 0]; } + export function getStopMarkersLayer({ rid, stops }, getStopInfo, selectedStops) { - /* returns new DeckGL Icon Layer displaying all stops on given routes */ - // Push stop markers into data array const data = stops.map(stop => ({ position: [stop.lon, stop.lat], icon: 'marker', @@ -95,7 +91,6 @@ export function getSubRoutesLayer(subroute) { return subrouteLayer; } -/* convert line name to color */ function lineToColor(line) { const checkedLine = MUNI_COLOR.filter(e => e.line === line); if (checkedLine.length > 0) { @@ -109,7 +104,6 @@ function lineToColor(line) { } export function getVehicleMarkersLayer(route, displayVehicleInfo) { - /* return a new IconLayer */ function newIconLayer(id, data, iconAtlas) { return new IconLayer({ id, @@ -122,21 +116,20 @@ export function getVehicleMarkersLayer(route, displayVehicleInfo) { iconAtlas, iconMapping: BUS_ICON_MAPPING, pickable: true, - // calls pop-up function onClick: info => displayVehicleInfo(info), }); } - /* returns new DeckGL Icon Layer displaying all vehicles on given routes */ + // ✅ West-facing buses (heading >= 180): use busIconWest image + // angle formula unified to (90 - heading) for correct North-clockwise mapping const westData = route.routeStates[0].vehicles.reduce((westBus, vehicle) => { if (vehicle.heading >= 180) { westBus.push({ position: [vehicle.lon, vehicle.lat], icon: 'marker', size: 65, - angle: 270 - vehicle.heading, + angle: 90 - vehicle.heading, // ✅ FIXED: was (270 - vehicle.heading) color: lineToColor(route.rid), - // added vid & heading info to display onClick pop-up vid: vehicle.vid, heading: vehicle.heading, }); @@ -144,15 +137,15 @@ export function getVehicleMarkersLayer(route, displayVehicleInfo) { return westBus; }, []); + // ✅ East-facing buses (heading < 180): formula was already correct const eastData = route.routeStates[0].vehicles.reduce((eastBus, vehicle) => { if (vehicle.heading < 180) { eastBus.push({ position: [vehicle.lon, vehicle.lat], icon: 'marker', size: 64, - angle: 90 - vehicle.heading, + angle: 90 - vehicle.heading, // ✅ unchanged — already correct color: lineToColor(route.rid), - // added vid & heading info to display onClick pop-up vid: vehicle.vid, heading: vehicle.heading, }); @@ -164,4 +157,4 @@ export function getVehicleMarkersLayer(route, displayVehicleInfo) { newIconLayer(route.rid.concat('west-vehicle-icon-layer'), westData, busIconWest), newIconLayer(route.rid.concat('east-vehicle-icon-layer'), eastData, busIconEast), ]; -} +} \ No newline at end of file