Skip to content

Commit

Permalink
Include trip shape on the VPP map (#415)
Browse files Browse the repository at this point in the history
* cleaner shape loading

* load GTFS shapes for non shuttle routes

* /api/shapes/trip/:trip_id endpoint

change /api/shapes/:route_id to /api/shapes/route/:route_id
add Gtfs.shape_for_trip
rename useRouteShapes.ts to useShapes.ts
add useTripShape to useShapes.ts

* useTripShape in VehiclePropertiesPanel

* useRouteShapes returns Shape[] instead of ByRouteId<Shape[] | null>

* useTripShape returns Shape[] instead of Shape | null
  • Loading branch information
skyqrose authored Jan 22, 2020
1 parent 4f8ba9c commit 0533a16
Show file tree
Hide file tree
Showing 17 changed files with 427 additions and 220 deletions.
26 changes: 22 additions & 4 deletions assets/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import "whatwg-fetch"
import { DirectionName, Route, RouteId, Shape, TimepointId } from "./schedule.d"
import {
DirectionName,
Route,
RouteId,
Shape,
TimepointId,
TripId,
} from "./schedule.d"

interface RoutesResponse {
data: RouteData[]
Expand All @@ -14,7 +21,7 @@ interface RouteData {
name: string
}

interface ShapesFroRouteResponse {
interface ShapesForRouteResponse {
data: Shape[]
}

Expand Down Expand Up @@ -59,10 +66,21 @@ export const fetchRoutes = (): Promise<Route[]> =>
})

export const fetchShapeForRoute = (routeId: RouteId): Promise<Shape[]> =>
fetch(`/api/shapes/${routeId}`)
fetch(`/api/shapes/route/${routeId}`)
.then(checkResponseStatus)
.then(parseJson)
.then(({ data: shapes }: ShapesFroRouteResponse) => shapes)
.then(({ data: shapes }: ShapesForRouteResponse) => shapes)
.catch(error => {
// tslint:disable-next-line: no-console
console.error(error)
throw error
})

export const fetchShapeForTrip = (tripId: TripId): Promise<Shape | null> =>
fetch(`/api/shapes/trip/${tripId}`)
.then(checkResponseStatus)
.then(parseJson)
.then(({ data: shape }: { data: Shape | null }) => shape)
.catch(error => {
// tslint:disable-next-line: no-console
console.error(error)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useState } from "react"
import useInterval from "../../hooks/useInterval"
import { useTripShape } from "../../hooks/useShapes"
import { isShuttle, shouldShowHeadwayDiagram } from "../../models/vehicle"
import { DataDiscrepancy, Vehicle } from "../../realtime"
import { Route } from "../../schedule"
import { Route, Shape } from "../../schedule"
import Map from "../map"
import PropertiesList from "../propertiesList"
import Header from "./header"
Expand Down Expand Up @@ -30,6 +31,9 @@ const directionsUrl = (

const Location = ({ vehicle }: { vehicle: Vehicle }) => {
const [epocNowInSeconds, setEpocNowInSeconds] = useState(nowInSeconds())

const shapes: Shape[] = useTripShape(vehicle.tripId)

useInterval(() => setEpocNowInSeconds(nowInSeconds()), 1000)
const secondsAgo = (epocTime: number): string =>
`${epocNowInSeconds - epocTime}s ago`
Expand Down Expand Up @@ -58,7 +62,7 @@ const Location = ({ vehicle }: { vehicle: Vehicle }) => {
Directions
</a>
<div className="m-vehicle-properties-panel__map">
<Map vehicles={[vehicle]} />
<Map vehicles={[vehicle]} shapes={shapes} />
</div>
</div>
)
Expand Down
9 changes: 2 additions & 7 deletions assets/src/components/shuttleMapPage.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React, { ReactElement, useContext } from "react"
import { ShuttleVehiclesContext } from "../contexts/shuttleVehiclesContext"
import { StateDispatchContext } from "../contexts/stateDispatchContext"
import useRouteShapes from "../hooks/useRouteShapes"
import { loadedShapes } from "../models/shape"
import { useRouteShapes } from "../hooks/useShapes"
import { RunId, Vehicle, VehicleId } from "../realtime"
import { Shape } from "../schedule"
import Map from "./map"
Expand Down Expand Up @@ -36,11 +35,7 @@ const ShuttleMapPage = ({}): ReactElement<HTMLDivElement> => {
selectedVehicleId,
} = state
const shuttles: Vehicle[] | null = useContext(ShuttleVehiclesContext)
const shuttleRouteShapesByRouteId = useRouteShapes(selectedShuttleRouteIds)
const shapes: Shape[] = loadedShapes(
shuttleRouteShapesByRouteId,
selectedShuttleRouteIds
)
const shapes: Shape[] = useRouteShapes(selectedShuttleRouteIds)
const selectedShuttles: Vehicle[] = filterShuttles(
shuttles || [],
selectedShuttleRunIds
Expand Down
46 changes: 0 additions & 46 deletions assets/src/hooks/useRouteShapes.ts

This file was deleted.

77 changes: 77 additions & 0 deletions assets/src/hooks/useShapes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useEffect, useState } from "react"
import { fetchShapeForRoute, fetchShapeForTrip } from "../api"
import { flatten } from "../helpers/array"
import { isASubwayRoute, subwayRouteShapes } from "../models/subwayRoute"
import { ByRouteId, RouteId, Shape, TripId } from "../schedule"

// An undefined value indicates that the shapes need to be loaded
// A null value indicates that we are currently loading the shapes
type LoadableShapes = Shape[] | null | undefined

export const useRouteShapes = (selectedRouteIds: RouteId[]): Shape[] => {
const [shapesByRouteId, setShapesByRouteId] = useState<
ByRouteId<LoadableShapes>
>({})

const setLoadingShapesForRoute = (routeId: RouteId) => {
setShapesByRouteId(previousShapesByRouteId => ({
...previousShapesByRouteId,
[routeId]: null,
}))
}

const setShapesForRoute = (routeId: RouteId, shapes: Shape[]) => {
setShapesByRouteId(previousShapesByRouteId => ({
...previousShapesByRouteId,
[routeId]: shapes,
}))
}

useEffect(() => {
selectedRouteIds.forEach((routeId: RouteId) => {
if (!(routeId in shapesByRouteId)) {
setLoadingShapesForRoute(routeId)

if (isASubwayRoute(routeId)) {
setShapesForRoute(routeId, subwayRouteShapes(routeId))
} else {
fetchShapeForRoute(routeId).then((shapes: Shape[]) =>
setShapesForRoute(routeId, shapes)
)
}
}
})
}, [selectedRouteIds, shapesByRouteId])

return loadedShapes(shapesByRouteId, selectedRouteIds)
}

export const useTripShape = (tripId: TripId | null): Shape[] => {
// null means loading
const [shape, setShape] = useState<Shape | null>(null)

useEffect(() => {
if (tripId !== null) {
fetchShapeForTrip(tripId).then((shapeResult: Shape | null) =>
setShape(shapeResult)
)
}
}, [])

return shape === null ? [] : [shape]
}

const loadedShapes = (
shapesByRouteId: ByRouteId<LoadableShapes>,
routeIds: RouteId[]
): Shape[] =>
flatten(
routeIds.map((routeId: RouteId): Shape[] => {
const loadableShapes: LoadableShapes = shapesByRouteId[routeId]
if (loadableShapes === undefined || loadableShapes === null) {
return []
} else {
return loadableShapes
}
})
)
18 changes: 0 additions & 18 deletions assets/src/models/shape.ts

This file was deleted.

8 changes: 0 additions & 8 deletions assets/src/schedule.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,6 @@ export interface ShapePoint {
lon: number
}

// An undefined value indicates that the shapes need to be loaded
// A null value indicates that we are currently loading the shapes
export type LoadableShapes = Shape[] | null | undefined

export type LoadableShapesByRouteId = ByRouteId<LoadableShapes>

export type ShapesByRouteId = ByRouteId<Shape[]>

export type TimepointId = string

// An undefined value indicates that the timepoints need to be loaded
Expand Down
24 changes: 24 additions & 0 deletions assets/tests/api.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
fetchRoutes,
fetchShapeForRoute,
fetchShapeForTrip,
fetchShuttleRoutes,
fetchTimepointsForRoute,
} from "../src/api"
Expand Down Expand Up @@ -209,6 +210,29 @@ describe("fetchShapeForRoute", () => {
})
})

describe("fetchShapeForTrip", () => {
test("fetches a shape for the trip", done => {
const shape = {
id: "shape",
points: [
{
shape_id: "shape1",
lat: 42.41356,
lon: -70.99211,
sequence: 0,
},
],
}

mockFetch(200, { data: shape })

fetchShapeForTrip("trip").then(response => {
expect(response).toEqual(shape)
done()
})
})
})

describe("fetchShuttleRoutes", () => {
test("fetches a list of shuttle routes", done => {
mockFetch(200, {
Expand Down
Loading

0 comments on commit 0533a16

Please sign in to comment.