diff --git a/gtfs/src/subway_gleam/gtfs/st.gleam b/gtfs/src/subway_gleam/gtfs/st.gleam index f642988..7e300b0 100644 --- a/gtfs/src/subway_gleam/gtfs/st.gleam +++ b/gtfs/src/subway_gleam/gtfs/st.gleam @@ -16,10 +16,12 @@ import gleam/set import gleam/string import gleam/time/duration import gsv +import subway_gleam/gtfs/st_extra import subway_gleam/gtfs/internal/decode_parse_str_field.{decode_parse_str_field} import subway_gleam/gtfs/internal/ffi import subway_gleam/gtfs/st/route.{type Route} +import subway_gleam/gtfs/st_extra_data pub type Feed { /// This file represents the "normal" subway schedule and does not include @@ -101,7 +103,8 @@ pub fn parse(bits: BitArray) -> Result(Schedule, FetchError) { use trips <- result.try(parse_file("trips.txt", trip_decoder())) let trips = trips_from_rows(trips) - use stops <- result.try(parse_file("stops.txt", stop_decoder())) + let st_extra_data = st_extra_data.data() + use stops <- result.try(parse_file("stops.txt", stop_decoder(st_extra_data))) let stops = list.fold(over: stops, from: dict.new(), with: fn(acc, stop) { acc |> dict.insert(for: #(stop.id, stop.direction), insert: stop) @@ -269,10 +272,17 @@ pub type Stop(direction) { lon: Float, location_type: option.Option(Int), parent_station: option.Option(StopId), + // === Extra fields === // + borough: st_extra.Borough, + /// The routes that "normally" (ie, daytime weekdays) stop at the stop. + /// Does not account for rush hour express. + daytime_routes: set.Set(Route), ) } -fn stop_decoder() -> decode.Decoder(Stop(option.Option(Direction))) { +fn stop_decoder( + st_extra_data: dict.Dict(String, st_extra.Stop), +) -> decode.Decoder(Stop(option.Option(Direction))) { use #(id, direction) <- decode.field("stop_id", stop_id_decoder()) use name <- decode.field("stop_name", decode.string) use lat <- decode.field( @@ -304,6 +314,11 @@ fn stop_decoder() -> decode.Decoder(Stop(option.Option(Direction))) { |> decode.map(option.Some), ) + let assert Ok(st_extra.Stop(borough:, daytime_routes:)) = + dict.get(st_extra_data, case id { + StopId(id) -> id + }) + decode.success(Stop( id:, direction:, @@ -312,6 +327,8 @@ fn stop_decoder() -> decode.Decoder(Stop(option.Option(Direction))) { lon:, location_type:, parent_station:, + borough:, + daytime_routes:, )) } diff --git a/server/src/subway_gleam/server/route/line.gleam b/server/src/subway_gleam/server/route/line.gleam index 214366f..8e140c6 100644 --- a/server/src/subway_gleam/server/route/line.gleam +++ b/server/src/subway_gleam/server/route/line.gleam @@ -54,6 +54,8 @@ fn stop_li(stop: st.Stop(direction)) -> element.Element(msg) { lon: _, location_type: _, parent_station: _, + borough: _, + daytime_routes: _, ) = stop let url = "/stop/" <> st.stop_id_to_string(id, direction: option.None) <> "/" diff --git a/shared/src/subway_gleam/shared/route/stops.gleam b/shared/src/subway_gleam/shared/route/stops.gleam index 1ee0360..c814f6e 100644 --- a/shared/src/subway_gleam/shared/route/stops.gleam +++ b/shared/src/subway_gleam/shared/route/stops.gleam @@ -6,12 +6,15 @@ import gleam/json import gleam/list import gleam/option import gleam/result +import gleam/set import lustre/attribute import lustre/element import lustre/element/html import lustre/element/keyed import subway_gleam/gtfs/st +import subway_gleam/gtfs/st/route.{type Route} +import subway_gleam/gtfs/st_extra import subway_gleam/shared/component/navbar.{navbar} import subway_gleam/shared/component/route_bullet import subway_gleam/shared/ffi/geolocation @@ -161,6 +164,11 @@ fn stop_decoder() -> decode.Decoder(st.Stop(Nil)) { use name <- decode.field("name", decode.string) use lat <- decode.field("lat", float_or_int_decoder) use lon <- decode.field("lon", float_or_int_decoder) + use borough <- decode.field("borough", borough_decoder()) + use daytime_routes <- decode.field( + "daytime_routes", + decode.list(of: route_decoder()) |> decode.map(set.from_list), + ) st.Stop( id:, @@ -170,6 +178,8 @@ fn stop_decoder() -> decode.Decoder(st.Stop(Nil)) { lon:, location_type: option.None, parent_station: option.None, + borough:, + daytime_routes:, ) |> decode.success } @@ -183,6 +193,8 @@ fn stop_to_json(stop: st.Stop(Nil)) -> json.Json { lon:, location_type: _, parent_station: _, + borough:, + daytime_routes:, ) = stop let st.StopId(id) = id @@ -191,5 +203,47 @@ fn stop_to_json(stop: st.Stop(Nil)) -> json.Json { #("name", json.string(name)), #("lat", json.float(lat)), #("lon", json.float(lon)), + #("borough", borough_to_json(borough)), + #( + "daytime_routes", + json.preprocessed_array( + daytime_routes |> set.to_list |> list.map(route_to_json), + ), + ), ]) } + +fn borough_decoder() -> decode.Decoder(st_extra.Borough) { + use borough <- decode.then(decode.string) + case borough { + "Manhattan" -> st_extra.Manhattan |> decode.success + "Brooklyn" -> st_extra.Brooklyn |> decode.success + "Queens" -> st_extra.Queens |> decode.success + "Bronx" -> st_extra.Bronx |> decode.success + "StatenIsland" -> st_extra.StatenIsland |> decode.success + _ -> decode.failure(st_extra.Manhattan, "Borough") + } +} + +fn borough_to_json(borough: st_extra.Borough) -> json.Json { + json.string(case borough { + st_extra.Manhattan -> "Manhattan" + st_extra.Brooklyn -> "Brooklyn" + st_extra.Queens -> "Queens" + st_extra.Bronx -> "Bronx" + st_extra.StatenIsland -> "StatenIsland" + }) +} + +fn route_decoder() -> decode.Decoder(Route) { + use long_id <- decode.then(decode.string) + case route.from_long_id(long_id) { + Ok(route) -> decode.success(route) + Error(Nil) -> decode.failure(route.A, "Route") + } +} + +fn route_to_json(route: Route) -> json.Json { + let long_id = route.to_long_id(route) + json.string(long_id) +}