diff --git a/apps/route_patterns/lib/repo.ex b/apps/route_patterns/lib/repo.ex index a1339d8b49..d8f7238983 100644 --- a/apps/route_patterns/lib/repo.ex +++ b/apps/route_patterns/lib/repo.ex @@ -38,14 +38,13 @@ defmodule RoutePatterns.Repo do opts |> Keyword.put(:route, route_id) |> Keyword.put(:sort, "typicality,sort_order") - |> Keyword.put(:include, "representative_trip.shape") |> cache(&api_all/1) |> Enum.sort(&reorder_mrts(&1, &2, route_id)) end def by_stop_id(stop_id) do [stop: stop_id] - |> Keyword.put(:include, "representative_trip.shape") + |> Keyword.put(:include, "representative_trip.shape,representative_trip.stops") |> cache(&api_all/1) end diff --git a/apps/route_patterns/lib/route_pattern.ex b/apps/route_patterns/lib/route_pattern.ex index 76ce66d790..ff0fa215f3 100644 --- a/apps/route_patterns/lib/route_pattern.ex +++ b/apps/route_patterns/lib/route_pattern.ex @@ -36,7 +36,8 @@ defmodule RoutePatterns.RoutePattern do :stop_ids, :route_id, :time_desc, - :typicality + :typicality, + sort_order: 0 ] @type id_t :: String.t() @@ -53,7 +54,8 @@ defmodule RoutePatterns.RoutePattern do stop_ids: [Stop.id_t()], route_id: Route.id_t(), time_desc: String.t(), - typicality: typicality_t() + typicality: typicality_t(), + sort_order: integer() } def new(%Item{ @@ -62,7 +64,8 @@ defmodule RoutePatterns.RoutePattern do "direction_id" => direction_id, "name" => name, "time_desc" => time_desc, - "typicality" => typicality + "typicality" => typicality, + "sort_order" => sort_order }, relationships: %{ "representative_trip" => [ @@ -71,20 +74,7 @@ defmodule RoutePatterns.RoutePattern do "headsign" => headsign }, id: representative_trip_id, - relationships: %{ - "shape" => [ - %Item{ - attributes: %{ - "polyline" => representative_trip_polyline, - "priority" => shape_priority - }, - id: shape_id, - relationships: %{ - "stops" => stops - } - } - ] - } + relationships: trip_relationships } ], "route" => [%Item{id: route_id}] @@ -95,14 +85,14 @@ defmodule RoutePatterns.RoutePattern do id: id, name: name, representative_trip_id: representative_trip_id, - representative_trip_polyline: representative_trip_polyline, - shape_id: shape_id, - shape_priority: shape_priority, + representative_trip_polyline: polyline(trip_relationships), + shape_id: shape_id(trip_relationships), headsign: headsign, - stop_ids: Enum.map(stops, fn %JsonApi.Item{id: id} -> id end), + stop_ids: stop_ids(trip_relationships), route_id: route_id, time_desc: time_desc, - typicality: typicality + typicality: typicality, + sort_order: sort_order } end @@ -112,7 +102,8 @@ defmodule RoutePatterns.RoutePattern do "direction_id" => direction_id, "name" => name, "time_desc" => time_desc, - "typicality" => typicality + "typicality" => typicality, + "sort_order" => sort_order }, relationships: %{ "representative_trip" => [%Item{id: representative_trip_id}], @@ -126,7 +117,39 @@ defmodule RoutePatterns.RoutePattern do representative_trip_id: representative_trip_id, route_id: route_id, time_desc: time_desc, - typicality: typicality + typicality: typicality, + sort_order: sort_order } end + + defp polyline(%{ + "shape" => [ + %Item{ + attributes: %{ + "polyline" => polyline + } + } + ] + }), + do: polyline + + defp polyline(_), do: nil + + defp shape_id(%{ + "shape" => [ + %Item{ + id: shape_id + } + ] + }), + do: shape_id + + defp shape_id(_), do: nil + + # Note: these are child stop IDs + defp stop_ids(%{"stops" => stops}) when is_list(stops) do + Enum.map(stops, & &1.id) + end + + defp stop_ids(_), do: nil end diff --git a/apps/route_patterns/test/repo_test.exs b/apps/route_patterns/test/repo_test.exs index 95bf3f1607..f57dfe9aac 100644 --- a/apps/route_patterns/test/repo_test.exs +++ b/apps/route_patterns/test/repo_test.exs @@ -37,8 +37,12 @@ defmodule RoutePatterns.RepoTest do end describe "by_stop_id/1" do - test "returns route patterns for a stop" do - assert [%RoutePattern{} | _] = Repo.by_stop_id("place-sstat") + test "returns route patterns for a stop, with shape and stops" do + assert [%RoutePattern{representative_trip_polyline: polyline, stop_ids: stop_ids} | _] = + Repo.by_stop_id("place-sstat") + + assert stop_ids + assert polyline end end end diff --git a/apps/routes/test/parser_test.exs b/apps/routes/test/parser_test.exs index 7a7e04b3c7..746710c0e4 100644 --- a/apps/routes/test/parser_test.exs +++ b/apps/routes/test/parser_test.exs @@ -140,7 +140,8 @@ defmodule Routes.ParserTest do "direction_id" => 1, "name" => "rp", "time_desc" => "td", - "typicality" => 1 + "typicality" => 1, + "sort_order" => 12_132_123 }, relationships: %{ "representative_trip" => [%Item{id: "id"}], diff --git a/apps/site/assets/ts/__v3api.d.ts b/apps/site/assets/ts/__v3api.d.ts index 1d21d2b95c..7b1d9a9d50 100644 --- a/apps/site/assets/ts/__v3api.d.ts +++ b/apps/site/assets/ts/__v3api.d.ts @@ -356,9 +356,11 @@ export interface RoutePattern { shape_id: string; shape_priority: number; stop_ids: string[]; + headsign: string; name: string; id: string; direction_id: DirectionId; + sort_order: number; } export interface StopHours { diff --git a/apps/site/assets/ts/schedule/components/__tests__/ScheduleFinderTest.tsx b/apps/site/assets/ts/schedule/components/__tests__/ScheduleFinderTest.tsx index d436fcc419..283184edbe 100644 --- a/apps/site/assets/ts/schedule/components/__tests__/ScheduleFinderTest.tsx +++ b/apps/site/assets/ts/schedule/components/__tests__/ScheduleFinderTest.tsx @@ -147,7 +147,8 @@ const routePatternsByDirection = { name: "North Station - Wachusett", headsign: "Wachusett", id: "CR-Fitchburg-0-0", - direction_id: 0 + direction_id: 0, + sort_order: 3 } ], "1": [ @@ -163,7 +164,8 @@ const routePatternsByDirection = { name: "Wachusett - North Station", headsign: "North Station", id: "CR-Fitchburg-0-1", - direction_id: 1 + direction_id: 1, + sort_order: 4 } ] } as RoutePatternsByDirection; diff --git a/apps/site/assets/ts/schedule/components/__tests__/test-data/routePatternsByDirectionData.json b/apps/site/assets/ts/schedule/components/__tests__/test-data/routePatternsByDirectionData.json index ec8d25d333..843277381a 100644 --- a/apps/site/assets/ts/schedule/components/__tests__/test-data/routePatternsByDirectionData.json +++ b/apps/site/assets/ts/schedule/components/__tests__/test-data/routePatternsByDirectionData.json @@ -8,11 +8,17 @@ "route_id": "route-1", "representative_trip_id": "trip-1", "representative_trip_polyline": "trip-1-polyline", - "stop_ids": ["1", "stop-place-alfcl", "2", "3"], + "stop_ids": [ + "1", + "stop-place-alfcl", + "2", + "3" + ], "name": "Pattern 1 - Dest", "headsign": "Pattern 1", "id": "pattern-1", - "direction_id": 0 + "direction_id": 0, + "sort_order": 123 }, { "typicality": 1, @@ -22,11 +28,17 @@ "route_id": "route-1", "representative_trip_id": "trip-3", "representative_trip_polyline": "trip-3-polyline", - "stop_ids": ["1", "stop-place-alfcl", "2", "4"], + "stop_ids": [ + "1", + "stop-place-alfcl", + "2", + "4" + ], "name": "Pattern 3 - Dest", "headsign": "Pattern 3", "id": "pattern-3", - "direction_id": 0 + "direction_id": 0, + "sort_order": 123 }, { "typicality": 3, @@ -36,11 +48,17 @@ "route_id": "route-1", "representative_trip_id": "trip-4", "representative_trip_polyline": "trip-4-polyline", - "stop_ids": ["1", "stop-place-alfcl", "3", "4"], + "stop_ids": [ + "1", + "stop-place-alfcl", + "3", + "4" + ], "name": "Pattern 4 - Dest", "headsign": "Pattern 3", "id": "pattern-4", - "direction_id": 0 + "direction_id": 0, + "sort_order": 123 } ], "1": [ @@ -52,11 +70,17 @@ "route_id": "route-1", "representative_trip_id": "trip-1", "representative_trip_polyline": "trip-2-polyline", - "stop_ids": ["2", "stop-place-alfcl", "3", "4"], + "stop_ids": [ + "2", + "stop-place-alfcl", + "3", + "4" + ], "name": "Pattern 2 - Dest", "headsign": "Pattern 2", "id": "pattern-2", - "direction_id": 1 + "direction_id": 1, + "sort_order": 123 } ] } diff --git a/apps/site/assets/ts/schedule/components/direction/__tests__/BusMenuTest.tsx b/apps/site/assets/ts/schedule/components/direction/__tests__/BusMenuTest.tsx index 0aaebf918a..e8cdb578f6 100644 --- a/apps/site/assets/ts/schedule/components/direction/__tests__/BusMenuTest.tsx +++ b/apps/site/assets/ts/schedule/components/direction/__tests__/BusMenuTest.tsx @@ -23,7 +23,8 @@ const routePatterns: EnhancedRoutePattern[] = [ shape_id: "660140", shape_priority: 1, time_desc: null, - typicality: 1 + typicality: 1, + sort_order: 5 }, { typicality: 3, @@ -37,7 +38,8 @@ const routePatterns: EnhancedRoutePattern[] = [ name: "Dudley Station - Union Square, Boston", id: "66-B-0", headsign: "Watertown Yard via Union Square Allston", - direction_id: 0 + direction_id: 0, + sort_order: 7 } ]; const singleRoutePattern = routePatterns.slice(0, 1); diff --git a/apps/site/assets/ts/schedule/components/direction/__tests__/reducer-test.tsx b/apps/site/assets/ts/schedule/components/direction/__tests__/reducer-test.tsx index fdf063655a..927f0fbe49 100644 --- a/apps/site/assets/ts/schedule/components/direction/__tests__/reducer-test.tsx +++ b/apps/site/assets/ts/schedule/components/direction/__tests__/reducer-test.tsx @@ -15,7 +15,8 @@ const routePatternsForInbound: EnhancedRoutePattern[] = [ shape_id: "1110177", shape_priority: 1, time_desc: null, - typicality: 1 + typicality: 1, + sort_order: 1 }, { direction_id: 1, @@ -29,7 +30,8 @@ const routePatternsForInbound: EnhancedRoutePattern[] = [ shape_id: "1110157", shape_priority: 1, time_desc: "Weekdays only", - typicality: 2 + typicality: 2, + sort_order: 2 } ]; @@ -55,7 +57,8 @@ const initialState: State = { shape_priority: 1, shape_id: "1110180", time_desc: null, - typicality: 1 + typicality: 1, + sort_order: 3 }, directionId: 0, routePatternsByDirection: { @@ -86,7 +89,8 @@ it("menuReducer handles 'toggleDirection'", () => { shape_id: "1110177", shape_priority: 1, time_desc: null, - typicality: 1 + typicality: 1, + sort_order: 1 } }; diff --git a/apps/site/lib/site_web/controllers/route_controller.ex b/apps/site/lib/site_web/controllers/route_controller.ex index 6228680333..a8feb4923d 100644 --- a/apps/site/lib/site_web/controllers/route_controller.ex +++ b/apps/site/lib/site_web/controllers/route_controller.ex @@ -22,7 +22,7 @@ defmodule SiteWeb.RouteController do defp route_polylines(route, stop_id) do route.id - |> RoutePatterns.Repo.by_route_id(stop: stop_id) + |> RoutePatterns.Repo.by_route_id(stop: stop_id, include: "representative_trip.shape") |> Enum.filter(&(!is_nil(&1.representative_trip_polyline))) |> Enum.map(&Polyline.new(&1, color: "#" <> route.color, weight: 4)) end diff --git a/apps/site/lib/site_web/controllers/schedule/line/helpers.ex b/apps/site/lib/site_web/controllers/schedule/line/helpers.ex index f7d1dc4a9e..7f2b9a17d8 100644 --- a/apps/site/lib/site_web/controllers/schedule/line/helpers.ex +++ b/apps/site/lib/site_web/controllers/schedule/line/helpers.ex @@ -7,8 +7,6 @@ defmodule SiteWeb.ScheduleController.Line.Helpers do alias RoutePatterns.RoutePattern alias Routes.Repo, as: RoutesRepo alias Routes.{Route, Shape} - alias Schedules.Repo, as: SchedulesRepo - alias Schedules.Trip alias Stops.Repo, as: StopsRepo alias Stops.{RouteStop, RouteStops, Stop} @@ -72,93 +70,17 @@ defmodule SiteWeb.ScheduleController.Line.Helpers do route |> do_get_branch_route_stops(direction_id, route_pattern_id) |> Enum.map(&RouteStop.list_from_route_pattern(&1, route)) - |> make_trunks_consistent(route) |> RouteStops.from_route_stop_groups() end - @routes_with_trunk_discrepancies ~w(CR-Franklin CR-Providence) - @spec do_get_branch_route_stops(Route.t(), direction_id(), RoutePattern.id_t() | nil) :: [ {RoutePattern.t(), [Stop.t()]} ] defp do_get_branch_route_stops(route, direction_id, route_pattern_id) do - route_patterns = get_line_route_patterns(route.id, direction_id, route_pattern_id) - - # code addressing routes_with_trunk_discrepancies depends on this function - # returning at least two route patterns, which is not guaranteed when using - # filtered_by_typicality() - filtered_route_patterns = - case route.id do - id when id in @routes_with_trunk_discrepancies -> - route_patterns - |> Enum.sort_by(& &1.typicality) - |> Enum.take(2) - - _ -> - route_patterns - |> filtered_by_typicality() - end - - filtered_route_patterns + get_line_route_patterns(route, direction_id, route_pattern_id) |> Enum.map(&stops_for_route_pattern/1) end - @spec make_trunks_consistent([[RouteStop.t()]], Route.t()) :: [[RouteStop.t()]] - defp make_trunks_consistent(route_stop_lists, %Route{id: route_id}) - when route_id in @routes_with_trunk_discrepancies do - shared_ids = shared_ids(route_stop_lists) - - route_stop_lists_with_trunk_ranges = - Enum.map(route_stop_lists, fn route_stop_list -> - trunk_range = - range(route_stop_list, fn route_stop -> MapSet.member?(shared_ids, route_stop.id) end) - - {route_stop_list, trunk_range} - end) - - largest_trunk = largest_trunk(route_stop_lists_with_trunk_ranges) - - Enum.map(route_stop_lists_with_trunk_ranges, fn {route_stops, trunk_range} -> - old_trunk = Enum.slice(route_stops, trunk_range) - - if old_trunk == largest_trunk do - # No need to replace anything - route_stops - else - route_stops - |> Enum.reject(&Enum.member?(old_trunk, &1)) - |> List.insert_at(trunk_range.first, largest_trunk) - |> List.flatten() - end - end) - end - - defp make_trunks_consistent(route_stop_lists, _route), do: route_stop_lists - - @spec range(Enum.t(), (Enum.element() -> as_boolean(term()))) :: Range.t() - defp range(items, fun) do - {min, max} = - items - |> Enum.with_index() - |> Enum.reduce([], fn {item, index}, acc -> if fun.(item), do: [index | acc], else: acc end) - |> Enum.min_max(fn -> {0, 0} end) - - min..max - end - - @spec largest_trunk([{[RouteStop.t()], Range.t()}]) :: [RouteStop.t()] - defp largest_trunk(route_stop_lists_with_trunk_ranges) do - largest_trunk_range = - route_stop_lists_with_trunk_ranges - |> Enum.map(&elem(&1, 1)) - |> Enum.max() - - route_stop_lists_with_trunk_ranges - |> Enum.find(fn {_, trunk_range} -> trunk_range == largest_trunk_range end) - |> elem(0) - |> Enum.slice(largest_trunk_range) - end - @spec get_map_route_patterns(Route.id_t(), Route.type_int()) :: [RoutePattern.t()] def get_map_route_patterns("Green", type) do GreenLine.branch_ids() |> Enum.join(",") |> get_map_route_patterns(type) @@ -166,7 +88,9 @@ defmodule SiteWeb.ScheduleController.Line.Helpers do def get_map_route_patterns(route_id, type) do route_id - |> RoutePatternsRepo.by_route_id() + |> RoutePatternsRepo.by_route_id( + include: "representative_trip.shape,representative_trip.stops" + ) |> filter_map_route_patterns(type) end @@ -330,43 +254,34 @@ defmodule SiteWeb.ScheduleController.Line.Helpers do defp do_update_green_branch_stop(false, stop, branch_id), do: %{stop | branch: branch_id} @spec stops_for_route_pattern(RoutePattern.t()) :: {RoutePattern.t(), [Stop.t()]} - defp stops_for_route_pattern(route_pattern) do - stops = - route_pattern - |> trip_for_route_pattern() - |> shape_for_trip() - |> stops_for_shape() - + defp stops_for_route_pattern(%RoutePattern{stop_ids: stop_ids} = route_pattern) do + stops = Enum.map(stop_ids, &StopsRepo.get_parent/1) {route_pattern, stops} end - @spec trip_for_route_pattern(RoutePattern.t()) :: Trip.t() | nil - defp trip_for_route_pattern(%RoutePattern{representative_trip_id: representative_trip_id}), - do: SchedulesRepo.trip(representative_trip_id) - - @spec shape_for_trip(Trip.t() | nil) :: Shape.t() | nil - defp shape_for_trip(nil), do: nil + @spec get_line_route_patterns(Route.t(), direction_id(), RoutePattern.id_t() | nil) :: [ + RoutePattern.t() + ] + defp get_line_route_patterns(%Route{id: route_id, type: route_type}, direction_id, nil) do + base_opts = [direction_id: direction_id, include: "representative_trip.stops"] - defp shape_for_trip(%Trip{shape_id: shape_id}) do - shape_id - |> RoutesRepo.get_shape() - |> List.first() - end + opts = + case route_type do + type when type in [0, 1, 2] -> + Keyword.put(base_opts, :canonical, true) - @spec stops_for_shape(Shape.t() | nil) :: [Stop.t()] - defp stops_for_shape(nil), do: [] - defp stops_for_shape(%Shape{stop_ids: stop_ids}), do: Enum.map(stop_ids, &StopsRepo.get!/1) + _ -> + base_opts + end - @spec get_line_route_patterns(Route.id_t(), direction_id(), RoutePattern.id_t() | nil) :: [ - RoutePattern.t() - ] - defp get_line_route_patterns(route_id, direction_id, nil), - do: - RoutePatternsRepo.by_route_id(route_id, direction_id: direction_id) - |> Enum.filter(&(&1.route_id == route_id)) + RoutePatternsRepo.by_route_id(route_id, opts) + |> Enum.filter(&(&1.route_id == route_id)) + end - defp get_line_route_patterns(_route_id, _direction_id, route_pattern_id) do - case RoutePatternsRepo.get(route_pattern_id) do + defp get_line_route_patterns(_route, _direction_id, route_pattern_id) do + case RoutePatternsRepo.get(route_pattern_id, + include: "representative_trip.stops" + ) do %RoutePattern{} = route_pattern -> [route_pattern] diff --git a/apps/site/lib/site_web/controllers/schedule/timetable_controller.ex b/apps/site/lib/site_web/controllers/schedule/timetable_controller.ex index f21b9764aa..7d89c967b8 100644 --- a/apps/site/lib/site_web/controllers/schedule/timetable_controller.ex +++ b/apps/site/lib/site_web/controllers/schedule/timetable_controller.ex @@ -59,14 +59,15 @@ defmodule SiteWeb.ScheduleController.TimetableController do } = build_timetable(conn.assigns.all_stops, timetable_schedules) canonical_rps = - RoutePatterns.Repo.by_route_id(route.id, direction_id: direction_id, canonical: true) + RoutePatterns.Repo.by_route_id(route.id, + direction_id: direction_id, + canonical: true, + include: "representative_trip.stops" + ) - # Don't use the stop ids set on the route pattern - those are mapped to the parent stops. - # Get the stops directly for the canonical trips canonical_stop_ids = canonical_rps - |> Enum.flat_map(&Stops.Repo.by_trip(&1.representative_trip_id)) - |> Enum.map(& &1.id) + |> Enum.flat_map(& &1.stop_ids) |> MapSet.new() track_changes = track_changes(trip_schedules, canonical_stop_ids) diff --git a/apps/site/test/site_web/controllers/schedule/line/helpers_test.exs b/apps/site/test/site_web/controllers/schedule/line/helpers_test.exs index ba13afdec0..f537cf89e9 100644 --- a/apps/site/test/site_web/controllers/schedule/line/helpers_test.exs +++ b/apps/site/test/site_web/controllers/schedule/line/helpers_test.exs @@ -643,7 +643,7 @@ defmodule SiteWeb.ScheduleController.Line.HelpersTest do end test "handles the Hingham-Hull ferry" do - route_stops = Helpers.get_branch_route_stops(%Route{id: "Boat-F1"}, 0) + route_stops = Helpers.get_branch_route_stops(%Route{id: "Boat-F1", type: 4}, 0) assert [ %RouteStops{ @@ -654,11 +654,7 @@ defmodule SiteWeb.ScheduleController.Line.HelpersTest do branch: "Long Wharf - Hingham via Hull", stops: long_hull_route_stops }, - %RouteStops{branch: "Rowes Wharf - Hingham", stops: rowe_route_stops}, - %RouteStops{ - branch: "Long Wharf - Hingham via Georges Island & Hull", - stops: _georges_route_stops - } + %RouteStops{branch: "Rowes Wharf - Hingham", stops: rowe_route_stops} | _others ] = route_stops assert Enum.all?( diff --git a/apps/stops/lib/route_stop.ex b/apps/stops/lib/route_stop.ex index 3234748b23..ea5d8990c8 100644 --- a/apps/stops/lib/route_stop.ex +++ b/apps/stops/lib/route_stop.ex @@ -303,6 +303,8 @@ defmodule Stops.RouteStop do ) do connections = route_stop.id + |> Stops.Repo.get_parent() + |> Map.get(:id) |> Routes.Repo.by_stop(include: "stop.connecting_stops") |> Enum.reject(&(&1.id == route_stop.route.id))