Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ShuttleHeadsigns): Merge the replacement shuttle bus cards with … #1769

Merged
merged 5 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 5 additions & 1 deletion apps/route_patterns/lib/repo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ defmodule RoutePatterns.Repo do

alias RoutePatterns.RoutePattern
alias V3Api.RoutePatterns, as: RoutePatternsApi
alias Routes.Repo, as: RoutesRepo

@doc """
Returns a single route pattern by ID
Expand Down Expand Up @@ -43,7 +44,10 @@ defmodule RoutePatterns.Repo do
end

def by_stop_id(stop_id) do
[stop: stop_id]
routes = RoutesRepo.by_stop(stop_id)
route_ids = Enum.join(Enum.map(routes, fn r -> r.id end), ",")

[route: route_ids]
kotva006 marked this conversation as resolved.
Show resolved Hide resolved
|> Keyword.put(:include, "representative_trip.shape,representative_trip.stops")
|> cache(&api_all/1)
end
Expand Down
1 change: 1 addition & 0 deletions apps/route_patterns/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ defmodule RoutePatterns.MixProject do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
{:routes, in_umbrella: true},
{:repo_cache, in_umbrella: true},
{:v3_api, in_umbrella: true}
]
Expand Down
11 changes: 10 additions & 1 deletion apps/routes/lib/parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,19 @@ defmodule Routes.Parser do
direction_destinations:
direction_attrs(attributes["direction_destinations"], parse_route_patterns(relationships)),
description: parse_gtfs_desc(attributes["description"]),
fare_class: parse_gtfs_fare_class(attributes["fare_class"])
fare_class: parse_gtfs_fare_class(attributes["fare_class"]),
line: parse_line(relationships)
}
end

defp parse_line(%{"line" => [head | _] = _lines}) do
# TODO figure out how to make this not an array on parsing
# Testing shows this as always a single object from the API that gets parsed into an array for some reason
kotva006 marked this conversation as resolved.
Show resolved Hide resolved
head
end

defp parse_line(_), do: nil

def parse_route_with_route_pattern(%Item{relationships: relationships} = item) do
{parse_route(item), parse_route_patterns(relationships)}
end
Expand Down
12 changes: 8 additions & 4 deletions apps/routes/lib/route.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ defmodule Routes.Route do
direction_destinations: :unknown,
description: :unknown,
fare_class: :unknown_fare,
custom_route?: false
custom_route?: false,
line: []

@type id_t :: String.t()
@type t :: %__MODULE__{
Expand All @@ -29,7 +30,8 @@ defmodule Routes.Route do
direction_destinations: %{0 => String.t(), 1 => String.t()} | :unknown,
description: gtfs_route_desc,
fare_class: gtfs_fare_class,
custom_route?: boolean
custom_route?: boolean,
line: []
kotva006 marked this conversation as resolved.
Show resolved Hide resolved
}
@type gtfs_route_type ::
:subway | :commuter_rail | :bus | :ferry | :logan_express | :massport_shuttle
Expand Down Expand Up @@ -263,7 +265,8 @@ defmodule Routes.Route do
direction_destinations: direction_destinations,
description: description,
fare_class: fare_class,
custom_route?: custom_route?
custom_route?: custom_route?,
line: line
}) do
direction_destinations_value =
if direction_destinations == :unknown,
Expand All @@ -287,7 +290,8 @@ defmodule Routes.Route do
direction_destinations: direction_destinations_value,
description: description,
fare_class: fare_class,
custom_route?: custom_route?
custom_route?: custom_route?,
line: line
}
end
end
Expand Down
2 changes: 1 addition & 1 deletion apps/routes/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ defmodule Routes.Mixfile do
#
# Type "mix help compile.app" for more information
def application do
[extra_applications: [:logger, :route_patterns], mod: {Routes, []}]
[extra_applications: [:logger], mod: {Routes, []}]
end

# Dependencies can be Hex packages:
Expand Down
6 changes: 6 additions & 0 deletions apps/site/assets/ts/__v3api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ export type FareClassType =
| "special_fare"
| "unknown_fare";

export interface Line {
id: string;
type: string;
}

export interface Route {
color?: string;
description: string;
Expand All @@ -131,6 +136,7 @@ export interface Route {
name: string;
sort_order?: number;
type: RouteType;
line: Line;
}

export interface EnhancedRoute extends Route {
Expand Down
3 changes: 3 additions & 0 deletions apps/site/assets/ts/models/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@ export const isASilverLineRoute = (routeOrRouteId: Route | string): boolean => {
};

export const RAPID_TRANSIT = "rapid_transit";
export const RAIL_REPLACEMENT_BUS = "rail_replacement_bus";

export const isRapidTransit = ({ description }: Route): boolean =>
description === RAPID_TRANSIT;
export const isGreenLine = ({ id }: Route): boolean => id === "Green";
export const isRailReplacementBus = ({ description }: Route): boolean =>
description === RAIL_REPLACEMENT_BUS;

const routeTypesToMode: {
[key in RouteType]: Mode;
Expand Down
1 change: 1 addition & 0 deletions apps/site/assets/ts/stop/components/DepartureCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const DepartureCard = ({
}),
[routePatternsByHeadsign]
);
// console.log(sortedRoutePatternsByHeadsign)
return (
<li className="departure-card">
<a
Expand Down
96 changes: 88 additions & 8 deletions apps/site/assets/ts/stop/components/DeparturesAndMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import renderFa from "../../helpers/render-fa";
import {
isASilverLineRoute,
isSubwayRoute,
isACommuterRailRoute
isACommuterRailRoute,
isRailReplacementBus,
isRapidTransit
} from "../../models/route";
import { useSMDown } from "../../helpers/media-breakpoints-react";
import usePredictionsChannel from "../../hooks/usePredictionsChannel";
Expand All @@ -30,6 +32,7 @@ import {
} from "../../models/alert";
import useDepartureRow from "../../hooks/useDepartureRow";
import { GroupedRoutePatterns } from "../stop-redesign-loader";
import { DepartureInfo } from "../../models/departureInfo";

interface DeparturesAndMapProps {
routes: Route[];
Expand All @@ -38,6 +41,69 @@ interface DeparturesAndMapProps {
setPredictionError: React.Dispatch<React.SetStateAction<boolean>>;
}

interface RouteIdReplacementMap {
[key: string]: Route;
}

// map replacement routes to the routes they replace
const mapRouteIds = (routes: Route[]): RouteIdReplacementMap => {
const railReplacementRoutes = filter(routes, r => isRailReplacementBus(r));
// We don't want to display the shuttle information on subway cards so don't map the subway routes
const regularRoutesSansSubway = filter(
routes,
r => !isRailReplacementBus(r) && !isRapidTransit(r)
);

const routeIdMap: RouteIdReplacementMap = {};
railReplacementRoutes.forEach(r => {
regularRoutesSansSubway.forEach(rr => {
if (r.line.id === rr.line.id) {
routeIdMap[r.id] = rr;
}
});
});

return routeIdMap;
};

const updateDepartureInfos = (
departureInfos: DepartureInfo[],
routeIdMap: RouteIdReplacementMap
): DepartureInfo[] => {
return departureInfos.map(di => {
let updatedRoute = di.route;
if (routeIdMap[di.route.id]) {
updatedRoute = routeIdMap[di.route.id];
}
return {
...di,
route: updatedRoute
};
});
};

// Maps a route pattern from one route to a different route based off the routeIdMap
// Used to map shuttles to train routes, as to display the same info on the card
const updateRoutePatterns = (
groupedRoutePatterns: GroupedRoutePatterns,
routeIdMap: { [key: string]: Route }
) => {
let updatedPatterns = groupedRoutePatterns;
Object.keys(groupedRoutePatterns).forEach(key => {
if (routeIdMap[key]) {
const routeIdToUpdate = routeIdMap[key].id;

updatedPatterns[routeIdToUpdate] = {
...updatedPatterns[routeIdToUpdate],
...updatedPatterns[key]
};
// remove the route from the list (we don't want it displayed)
delete updatedPatterns[key];
}
});
return updatedPatterns;
};

const DeparturesAndMap = ({
routes,
stop,
Expand All @@ -55,6 +121,13 @@ const DeparturesAndMap = ({
}, [setPredictionError, predictions]);
const [realtimeAlerts, setRealtimeAlerts] = useState<Alert[]>([]);

const mappedRouteIds = mapRouteIds(routes);
console.log(routes);
const updatedDepartureInfos = updateDepartureInfos(
departureInfos,
mappedRouteIds
);

const { activeRow, resetRow } = useDepartureRow(routes);
useEffect(() => {
if (activeRow && activeRow.route) {
Expand Down Expand Up @@ -84,15 +157,15 @@ const DeparturesAndMap = ({

// filter by chosen route and direction
const filteredDepartures = activeRow
? departureInfos.filter(departure => {
? updatedDepartureInfos.filter(departure => {
const { route, trip } = departure;
return (
route.id === activeRow.route.id &&
trip.direction_id === activeRow.directionId &&
trip.headsign === activeRow.headsign
);
})
: departureInfos;
: updatedDepartureInfos;

const isSmallBreakpoint = useSMDown();
const refEl = useRef<HTMLDivElement>(null);
Expand All @@ -116,10 +189,16 @@ const DeparturesAndMap = ({
isACommuterRailRoute(route)
);
const groupedRoutePatterns = useLoaderData() as GroupedRoutePatterns;

const updatedGroupedRoutePatterns = updateRoutePatterns(
groupedRoutePatterns,
mappedRouteIds
);

const defaultPolylines = orderBy(routesForMap, "sort_order", "desc").flatMap(
route => {
const routePatterns = Object.values(
groupedRoutePatterns[route.id]
updatedGroupedRoutePatterns[route.id]
).flat();
return routePatterns.map(rp => rp.representative_trip_polyline);
}
Expand All @@ -128,9 +207,9 @@ const DeparturesAndMap = ({
/** TODO: Filter by selected trip. Blocked by being unable to match
* schedule/prediction shape IDs with route canonical shape IDs */
const routePatternsForSelection = activeRow
? groupedRoutePatterns[activeRow.route.id][activeRow.headsign].filter(
rp => rp.direction_id === activeRow.directionId
)
? updatedGroupedRoutePatterns[activeRow.route.id][
activeRow.headsign
].filter(rp => rp.direction_id === activeRow.directionId)
: [];
const shapeForSelection = routePatternsForSelection.map(
rp => rp.representative_trip_polyline
Expand Down Expand Up @@ -170,8 +249,9 @@ const DeparturesAndMap = ({
) : (
<StopPageDepartures
routes={routes}
departureInfos={departureInfos}
departureInfos={updatedDepartureInfos}
alerts={alerts}
groupedRoutePatterns={updatedGroupedRoutePatterns}
/>
)}
</div>
Expand Down
13 changes: 8 additions & 5 deletions apps/site/assets/ts/stop/components/StopPageDepartures.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, { ReactElement, useState } from "react";
import { useLoaderData } from "react-router-dom";
import { Alert, Route } from "../../__v3api";
import DeparturesFilters, { ModeChoice } from "./DeparturesFilters";
import { modeForRoute } from "../../models/route";
import { isRailReplacementBus, modeForRoute } from "../../models/route";
import DepartureCard from "./DepartureCard";
import { alertsByRoute, isInNextXDays } from "../../models/alert";
import { DepartureInfo } from "../../models/departureInfo";
Expand All @@ -16,6 +16,7 @@ interface StopPageDeparturesProps {
routes: Route[];
departureInfos: DepartureInfo[];
alerts: Alert[];
groupedRoutePatterns: GroupedRoutePatterns;
}

// Commuter Rail, then Subway, then Bus
Expand All @@ -32,17 +33,19 @@ const modeSortFn = ({ type }: Route): number => {
const StopPageDepartures = ({
routes,
departureInfos,
alerts
alerts,
groupedRoutePatterns
}: StopPageDeparturesProps): ReactElement<HTMLElement> => {
// default to show all modes.
const [selectedMode, setSelectedMode] = useState<ModeChoice>("all");
const groupedRoutes = groupBy(routes, modeForRoute);
// Filters our all replacement buses that haven't been remapped in the parent component
const nonShuttleRoutes = filter(routes, r => !isRailReplacementBus(r));
const groupedRoutes = groupBy(nonShuttleRoutes, modeForRoute);
const modesList = Object.keys(groupedRoutes) as ModeChoice[];
const filteredRoutes =
selectedMode === "all" ? routes : groupedRoutes[selectedMode];
selectedMode === "all" ? nonShuttleRoutes : groupedRoutes[selectedMode];
const currentAlerts = filter(alerts, a => isInNextXDays(a, 0));
const groupedAlerts = alertsByRoute(currentAlerts);
const groupedRoutePatterns = useLoaderData() as GroupedRoutePatterns;

return (
<div className="routes">
Expand Down
3 changes: 3 additions & 0 deletions apps/site/assets/ts/stop/components/StopPageRedesign.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,15 @@ const StopPageRedesign = ({
const [hasPredictionError, setPredictionError] = useState(false);
const stopResult = useStop(stopId);
const groupedRoutePatterns = useLoaderData() as GroupedRoutePatterns;
// console.log(groupedRoutePatterns)
const routesResult = useRoutes(Object.keys(groupedRoutePatterns || []));
const alertsForStopResult = useAlertsByStop(stopId);
const facilities = useFacilitiesByStop(stopId);
const routes = routesResult.data || [];
const alertsForRoutesResult = useAlertsByRoute(routes.map(r => r.id));

// console.log(routesResult.data)

if (
[stopResult.status, routesResult.status, facilities.status].includes(
FetchStatus.Error
Expand Down