Skip to content

Commit

Permalink
Fix Franklin line diagram issues around Foxboro
Browse files Browse the repository at this point in the history
* Fixes glitchy/disconnected line diagram in the outbound direction
* Fixes Ruggles and Back Bay not showing up on the inbound direction

The Foxboro pilot creates a split-then-merge-then-split situation the
current line diagram is unable to properly represent. This change does
not address that larger issue; it only adjusts the subset of shapes and
stops we discard to arrive at something the line diagram can handle.
  • Loading branch information
digitalcora committed Jan 8, 2020
1 parent a0eedc5 commit d76a775
Show file tree
Hide file tree
Showing 2 changed files with 212 additions and 15 deletions.
48 changes: 33 additions & 15 deletions apps/stops/lib/route_stop.ex
Original file line number Diff line number Diff line change
Expand Up @@ -125,16 +125,30 @@ defmodule Stops.RouteStop do
|> merge_branch_list(1)
end

def list_from_shapes(
shapes,
stops,
%Route{id: "CR-Franklin"} = route,
0
) do
shapes
|> Enum.reject(&(&1.name == "South Station - Foxboro via Fairmount"))
|> Enum.map(&do_list_from_shapes(&1.name, &1.stop_ids, stops, route))
|> merge_branch_list(0)
end

def list_from_shapes(
shapes,
stops,
%Route{id: "CR-Franklin"} = route,
1
) do
# We need to prefer the shorter "trunk" here when merging the branch list so the trunk stops
# don't end up being the ones from the Fairmount line
shapes
|> Enum.reject(&(&1.name == "Forge Park/495 - South Station via Back Bay"))
|> Enum.reject(&(&1.name == "Forge Park/495 - South Station via Fairmount"))
|> Enum.map(&do_list_from_shapes(&1.name, &1.stop_ids, stops, route))
|> merge_branch_list(1)
|> merge_branch_list(1, true)
end

def list_from_shapes(shapes, stops, route, direction_id) do
Expand Down Expand Up @@ -224,20 +238,22 @@ defmodule Stops.RouteStop do
[]
end

@spec merge_branch_list([[RouteStop.t()]], direction_id_t) :: [RouteStop.t()]
defp merge_branch_list(branches, direction_id) do
# If we know a route has branches, then we need to figure out which stops are on a branch vs. which stops
# are shared. At this point, we have two lists of branches, and at the back end the stops are all the same,
# but starting at some point in the middle the stops branch.
@spec merge_branch_list([[RouteStop.t()]], direction_id_t, boolean) :: [RouteStop.t()]
defp merge_branch_list(branches, direction_id, prefer_shorter_trunk \\ false) do
# Attempt to flatten a collection of RouteStop lists into a single list consisting of a shared
# "trunk" (where `branch` is set to nil) and two or more "branches" (where it is left alone).
# If the routes split, then merge, then split again, only the final split will be represented
# as "branches" and all but one of the multiple "trunks" will be discarded. Normally the kept
# trunk will be the one with the most stops; `prefer_shorter_trunk` instead keeps the one with
# the fewest stops.
branches
|> Enum.map(&flip_branches_to_front(&1, direction_id))
|> flatten_branches
# unflips the branches
|> flatten_branches(prefer_shorter_trunk)
|> flip_branches_to_front(direction_id)
end

@spec flatten_branches([[RouteStop.t()]]) :: [RouteStop.t()]
defp flatten_branches(branches) do
@spec flatten_branches([[RouteStop.t()]], boolean) :: [RouteStop.t()]
defp flatten_branches(branches, prefer_shorter_trunk) do
# We build a list of the shared stops between the branches, then unassign
# the branch for each stop that's in the list of shared stops.
shared_stop_ids =
Expand All @@ -249,7 +265,7 @@ defmodule Stops.RouteStop do

branches
|> Enum.map(&unassign_branch_if_shared(&1, shared_stop_ids))
|> Enum.reduce(&merge_two_branches/2)
|> Enum.reduce(&merge_two_branches(&1, &2, prefer_shorter_trunk))
end

@spec unassign_branch_if_shared([RouteStop.t()], MapSet.t()) :: [RouteStop.t()]
Expand All @@ -263,14 +279,16 @@ defmodule Stops.RouteStop do
end
end

@spec merge_two_branches([RouteStop.t()], [RouteStop.t()]) :: [RouteStop.t()]
defp merge_two_branches(first, second) do
@spec merge_two_branches([RouteStop.t()], [RouteStop.t()], boolean) :: [RouteStop.t()]
defp merge_two_branches(first, second, prefer_shorter_trunk) do
{first_branch, first_core} = Enum.split_while(first, & &1.branch)
{second_branch, second_core} = Enum.split_while(second, & &1.branch)

max_or_min_by = if(prefer_shorter_trunk, do: &Enum.min_by/2, else: &Enum.max_by/2)

core =
[first_core, second_core]
|> Enum.max_by(&length/1)
|> max_or_min_by.(&length/1)
|> Enum.map(&%{&1 | branch: nil})

second_branch ++ first_branch ++ core
Expand Down
179 changes: 179 additions & 0 deletions apps/stops/test/route_stop_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,60 @@ defmodule Stops.RouteStopTest do
assert_branch_names(actual, ["Kingston", "Plymouth", nil, nil, nil])
end

test "handles the Foxboro pilot on the Franklin line (outbound)" do
route = %Route{id: "CR-Franklin"}
shapes = franklin_shapes(0)
stops = make_stops(shapes |> Enum.flat_map(& &1.stop_ids) |> Enum.uniq())

actual = list_from_shapes(shapes, stops, route, 0)

assert_stop_ids(actual, [
"place-sstat",
"place-bbsta",
"place-rugg",
"place-NEC-2203",
"place-DB-0095",
"place-FB-0109",
"place-FB-0118",
"place-FS-0049",
"place-FB-0125",
"place-FB-0143",
"place-FB-0148",
"place-FB-0166",
"place-FB-0191",
"place-FB-0230",
"place-FB-0275",
"place-FB-0303"
])
end

test "handles the Foxboro pilot on the Franklin line (inbound)" do
route = %Route{id: "CR-Franklin"}
shapes = franklin_shapes(1)
stops = make_stops(shapes |> Enum.flat_map(& &1.stop_ids) |> Enum.uniq())

actual = list_from_shapes(shapes, stops, route, 1)

assert_stop_ids(actual, [
"place-FB-0303",
"place-FB-0275",
"place-FB-0230",
"place-FB-0191",
"place-FB-0177",
"place-FS-0049",
"place-FB-0166",
"place-FB-0148",
"place-FB-0143",
"place-FB-0125",
"place-FB-0118",
"place-FB-0109",
"place-DB-0095",
"place-rugg",
"place-bbsta",
"place-sstat"
])
end

test "handles ferry routes with multiple shapes by returning the stops as-is" do
primary = %Shape{id: "primary"}
other = %Shape{id: "secondary"}
Expand Down Expand Up @@ -170,4 +224,129 @@ defmodule Stops.RouteStopTest do
def assert_branch_names(actual, branch_names) do
assert Enum.map(actual, & &1.branch) == branch_names
end

defp franklin_shapes(0) do
[
%Shape{
id: "9880006",
name: "South Station - Forge Park/495 via Back Bay",
stop_ids: [
"place-sstat",
"place-bbsta",
"place-rugg",
"place-NEC-2203",
"place-DB-0095",
"place-FB-0109",
"place-FB-0118",
"place-FB-0125",
"place-FB-0143",
"place-FB-0148",
"place-FB-0166",
"place-FB-0191",
"place-FB-0230",
"place-FB-0275",
"place-FB-0303"
]
},
%Shape{
id: "SouthStationToFoxboroViaBackBay",
name: "South Station - Foxboro via Back Bay & Dedham",
stop_ids: ["place-sstat", "place-bbsta", "place-FB-0118", "place-FS-0049"]
},
%Shape{
id: "SouthStationToFoxboroViaFairmount",
name: "South Station - Foxboro via Fairmount",
stop_ids: [
"place-sstat",
"place-DB-2265",
"place-DB-2258",
"place-DB-2249",
"place-DB-2240",
"place-DB-2230",
"place-DB-2222",
"place-DB-2205",
"place-DB-0095",
"place-FB-0109",
"place-FB-0118",
"place-FB-0125",
"place-FB-0143",
"place-FB-0148",
"place-FB-0166",
"place-FS-0049"
]
}
]
end

defp franklin_shapes(1) do
[
%Shape{
id: "9880005",
name: "Forge Park/495 - South Station via Back Bay",
stop_ids: [
"place-FB-0303",
"place-FB-0275",
"place-FB-0230",
"place-FB-0191",
"place-FB-0177",
"place-FB-0166",
"place-FB-0148",
"place-FB-0143",
"place-FB-0125",
"place-FB-0118",
"place-FB-0109",
"place-DB-0095",
"place-rugg",
"place-bbsta",
"place-sstat"
]
},
%Shape{
id: "9880002",
name: "Forge Park/495 - South Station via Fairmount",
stop_ids: [
"place-FB-0303",
"place-FB-0275",
"place-FB-0230",
"place-FB-0191",
"place-FB-0148",
"place-FB-0143",
"place-FB-0125",
"place-FB-0118",
"place-FB-0109",
"place-DB-0095",
"place-DB-2205",
"place-DB-2222",
"place-DB-2230",
"place-DB-2240",
"place-DB-2249",
"place-DB-2258",
"place-DB-2265",
"place-sstat"
]
},
%Shape{
id: "FoxboroToSouthStationViaFairmount",
name: "Foxboro - South Station via Fairmount",
stop_ids: [
"place-FS-0049",
"place-FB-0166",
"place-FB-0148",
"place-FB-0143",
"place-FB-0125",
"place-FB-0118",
"place-FB-0109",
"place-DB-0095",
"place-DB-2205",
"place-DB-2222",
"place-DB-2230",
"place-DB-2240",
"place-DB-2249",
"place-DB-2258",
"place-DB-2265",
"place-sstat"
]
}
]
end
end

0 comments on commit d76a775

Please sign in to comment.