Skip to content

Commit

Permalink
Merge branch 'dev' into allow-more-styling
Browse files Browse the repository at this point in the history
  • Loading branch information
miles-grant-ibigroup committed Jun 19, 2024
2 parents cc5a8e7 + 3eea5c0 commit f60a3a7
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 93 deletions.
68 changes: 68 additions & 0 deletions __tests__/util/pattern-viewer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import '../test-utils/mock-window-url'
import { extractMainHeadsigns } from '../../lib/util/pattern-viewer'

function createStops(ids) {
return ids.map((id) => ({
id,
name: id
}))
}

function editHeadsign(pattern) {
pattern.headsign = `${pattern.headsign} (${pattern.lastStop})`
}

describe('util > pattern-viewer', () => {
describe('extractMainHeadsigns', () => {
it('should retain the essential patterns', () => {
// Consider the following patterns P1, P2, P3 of the same route with the same headsigns:
// Stops S1 S2 S3 S4 S5 S6 S7 --> direction of travel
// P1: o--o--o--o--o
// P2: o--o-----o--o
// P3: o--o--o
//
// P3 should be removed because it is a subset of P1.
// P1 and P2 should be kept.
// Patterns are assumed in descending length order because
// pre-sorting happened before extractMainHeadsigns is invoked (key order matters).
const headsign = 'Everett via Lynnwood'
const route = '512'
const patterns = {
P1: {
headsign,
id: 'P1',
name: 'P1 Pattern name',
patternGeometry: {
length: 1404,
points: 'p1-points'
},
stops: createStops(['S1', 'S2', 'S3', 'S4', 'S5'])
},
P2: {
headsign,
id: 'P2',
name: 'P2 Pattern name',
patternGeometry: {
length: 1072,
points: 'p2-points'
},
stops: createStops(['S3', 'S4', 'S6', 'S7'])
},
P3: {
headsign,
id: 'P3',
name: 'P3 Pattern name',
patternGeometry: {
length: 987,
points: 'p3-points'
},
stops: createStops(['S3', 'S4', 'S5'])
}
}
const headsignData = extractMainHeadsigns(patterns, route, editHeadsign)
expect(headsignData.length).toBe(2)
expect(headsignData[0].headsign).toBe(headsign)
expect(headsignData[1].headsign).toBe(`${headsign} (S7)`)
})
})
})
4 changes: 3 additions & 1 deletion lib/actions/apiV2.js
Original file line number Diff line number Diff line change
Expand Up @@ -939,7 +939,8 @@ export function routingQuery(searchId = null, updateSearchInReducer) {
modes,
numItineraries,
routingType,
time
time,
unpreferred
} = currentQuery
const arriveBy = departArrive === 'ARRIVE'

Expand Down Expand Up @@ -997,6 +998,7 @@ export function routingQuery(searchId = null, updateSearchInReducer) {
numItineraries: numItineraries || config?.modes?.numItineraries || 7,
time,
to: currentQuery.to,
unpreferred,
// TODO: Does this break everything?
...currentQuery
}
Expand Down
13 changes: 13 additions & 0 deletions lib/actions/field-trip.js
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,9 @@ export function saveRequestTripItineraries(request, outbound, intl) {
function prepareQueryParams(request, outbound) {
return async function (dispatch, getState) {
const { config } = getState().otp
const { modules, transitOperators } = config
const fieldTripOptions = modules?.find((m) => m.id === 'ft')?.options

const queryParams = {
date: format(parseDate(request.travelDate), OTP_API_DATE_FORMAT)
}
Expand All @@ -520,6 +523,16 @@ function prepareQueryParams(request, outbound) {
OTP_API_TIME_FORMAT
)
}
// Generate banned list from clear list
if (fieldTripOptions?.allowedAgencies) {
const bannedAgencies = transitOperators
.map((to) => to.agencyId)
.filter((to) => !fieldTripOptions.allowedAgencies.includes(to))

queryParams.unpreferred = {
agencies: bannedAgencies
}
}
const locations = await planParamsToQueryAsync(locationsToGeocode, config)
return dispatch(setQueryParam({ ...locations, ...queryParams }))
}
Expand Down
109 changes: 29 additions & 80 deletions lib/components/viewers/route-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ import React, { Component } from 'react'
import styled from 'styled-components'

import * as uiActions from '../../actions/ui'
import {
extractHeadsignFromPattern,
getRouteColorBasedOnSettings
} from '../../util/viewer'
import { DEFAULT_ROUTE_COLOR } from '../util/colors'
import { extractMainHeadsigns, PatternSummary } from '../../util/pattern-viewer'
import { getOperatorName } from '../../util/state'
import { getRouteColorBasedOnSettings } from '../../util/viewer'
import { LinkOpensNewWindow } from '../util/externalLink'
import {
SetViewedRouteHandler,
Expand All @@ -30,7 +29,6 @@ import {
StopLink,
Stop as StyledStop
} from './styled'
import { DEFAULT_ROUTE_COLOR } from '../util/colors'

const PatternSelectButton = styled(UnstyledButton)`
span {
Expand All @@ -40,13 +38,6 @@ const PatternSelectButton = styled(UnstyledButton)`
}
`

interface PatternSummary {
geometryLength: number
headsign: string
id: string
lastStop?: string
}

interface Props {
intl: IntlShape
operator: TransitOperator
Expand Down Expand Up @@ -77,6 +68,13 @@ class RouteDetails extends Component<Props> {
setViewedStop(stop)
}

_editHeadsign = (pattern: PatternSummary) => {
pattern.headsign = this.props.intl.formatMessage(
{ id: 'components.RouteDetails.headsignTo' },
{ ...pattern }
) as string
}

render() {
const { intl, operator, patternId, route, setHoveredStop } = this.props
const { agency, patterns = {}, shortName, url } = route
Expand All @@ -86,74 +84,25 @@ class RouteDetails extends Component<Props> {

const routeColor = getRouteColorBasedOnSettings(operator, route)

const headsigns = Object.entries(patterns)
.map(
([id, pat]): PatternSummary => ({
geometryLength: pat.patternGeometry?.length || 0,
headsign: extractHeadsignFromPattern(pat, shortName),
id,
lastStop: pat.stops?.[pat.stops?.length - 1]?.name
})
)
// Address duplicate headsigns.
.reduce((prev: PatternSummary[], cur) => {
const amended = prev
const alreadyExistingIndex = prev.findIndex(
(h) => h.headsign === cur.headsign
)
// If the headsign is a duplicate, and the last stop of the pattern is not the headsign,
// amend the headsign with the last stop name in parenthesis.
// e.g. "Headsign (Last Stop)"
if (
alreadyExistingIndex >= 0 &&
cur.lastStop &&
cur.headsign !== cur.lastStop
) {
cur.headsign = intl.formatMessage(
{ id: 'components.RouteDetails.headsignTo' },
{ ...cur }
)

// If there are only two total patterns, then we should rename
// both of them
if (amended.length === 1 && Object.entries(patterns).length === 2) {
amended[0].headsign = intl.formatMessage(
{ id: 'components.RouteDetails.headsignTo' },
{ ...amended[0] }
)
amended.push(cur)
return amended
}
}

// With all remaining duplicate headsigns, only keep the pattern with the
// longest geometry.
if (alreadyExistingIndex >= 0) {
if (
amended[alreadyExistingIndex].geometryLength < cur.geometryLength
) {
amended[alreadyExistingIndex] = cur
}
} else {
amended.push(cur)
}
return amended
}, [])
.sort((a, b) => {
// sort by number of vehicles on that pattern
const aVehicleCount =
route.vehicles?.filter((vehicle) => vehicle.patternId === a.id)
.length || 0
const bVehicleCount =
route.vehicles?.filter((vehicle) => vehicle.patternId === b.id)
.length || 0

// if both have the same count, sort by pattern geometry length
if (aVehicleCount === bVehicleCount) {
return b.geometryLength - a.geometryLength
}
return bVehicleCount - aVehicleCount
})
const headsigns = extractMainHeadsigns(
patterns,
shortName,
this._editHeadsign
).sort((a, b) => {
// sort by number of vehicles on that pattern
const aVehicleCount =
route.vehicles?.filter((vehicle) => vehicle.patternId === a.id)
.length || 0
const bVehicleCount =
route.vehicles?.filter((vehicle) => vehicle.patternId === b.id)
.length || 0

// if both have the same count, sort by pattern geometry length
if (aVehicleCount === bVehicleCount) {
return b.geometryLength - a.geometryLength
}
return bVehicleCount - aVehicleCount
})

const patternSelectLabel = intl.formatMessage({
id: 'components.RouteDetails.selectADirection'
Expand Down
65 changes: 65 additions & 0 deletions lib/util/pattern-viewer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Pattern } from '../components/util/types'

import { extractHeadsignFromPattern } from './viewer'

export interface PatternSummary {
geometryLength: number
headsign: string
id: string
lastStop?: string
}

export function extractMainHeadsigns(
patterns: Record<string, Pattern>,
shortName: string,
editHeadsign: (pattern: PatternSummary) => void
): PatternSummary[] {
const mapped = Object.entries(patterns).map(
([id, pat]): PatternSummary => ({
geometryLength: pat.patternGeometry?.length || 0,
headsign: extractHeadsignFromPattern(pat, shortName),
id,
lastStop: pat.stops?.[pat.stops?.length - 1]?.name
})
)

// Address duplicate headsigns.
return mapped.reduce((prev: PatternSummary[], cur) => {
const amended = prev
const alreadyExistingIndex = prev.findIndex(
(h) => h.headsign === cur.headsign
)
// If the headsign is a duplicate, and the last stop of the pattern is not the headsign,
// amend the headsign with the last stop name in parenthesis.
// e.g. "Headsign (Last Stop)"
if (
alreadyExistingIndex >= 0 &&
cur.lastStop &&
cur.headsign !== cur.lastStop
) {
editHeadsign(cur)

// If there are only two total patterns, then we should rename
// both of them
if (amended.length === 1 && Object.entries(patterns).length === 2) {
editHeadsign(amended[0])
amended.push(cur)
return amended
}
}

// With all remaining duplicate headsigns with the same last stops, only keep the pattern with the
// longest geometry.
if (
alreadyExistingIndex >= 0 &&
amended[alreadyExistingIndex].lastStop === cur.lastStop
) {
if (amended[alreadyExistingIndex].geometryLength < cur.geometryLength) {
amended[alreadyExistingIndex] = cur
}
} else {
amended.push(cur)
}
return amended
}, [])
}
3 changes: 2 additions & 1 deletion lib/util/viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import { getMostReadableTextColor } from '@opentripplanner/core-utils/lib/route'
import tinycolor from 'tinycolor2'

import { checkForRouteModeOverride } from './config'
import { DARK_TEXT_GREY } from '../components/util/colors'

import { checkForRouteModeOverride } from './config'
import { getOperatorAndRoute } from './state'
import { isBlank } from './ui'

Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@
"@floating-ui/react": "^0.19.2",
"@opentripplanner/base-map": "^3.1.0",
"@opentripplanner/building-blocks": "^1.0.3",
"@opentripplanner/core-utils": "^11.4.0",
"@opentripplanner/core-utils": "^11.4.1",
"@opentripplanner/endpoints-overlay": "^2.0.12",
"@opentripplanner/from-to-location-picker": "^2.1.12",
"@opentripplanner/geocoder": "^2.2.2",
"@opentripplanner/geocoder": "^3.0.1",
"@opentripplanner/humanize-distance": "^1.2.0",
"@opentripplanner/icons": "^2.0.10",
"@opentripplanner/itinerary-body": "^5.3.0",
"@opentripplanner/location-field": "^2.0.17",
"@opentripplanner/location-field": "^2.0.18",
"@opentripplanner/location-icon": "^1.4.1",
"@opentripplanner/map-popup": "^3.1.0",
"@opentripplanner/otp2-tile-overlay": "^1.0.12",
Expand Down
Loading

0 comments on commit f60a3a7

Please sign in to comment.