diff --git a/example-config.yml b/example-config.yml
index 408d955ad..37acdcc6a 100644
--- a/example-config.yml
+++ b/example-config.yml
@@ -294,6 +294,15 @@ itinerary:
# defaultRouteTextColor: '000000'
# longNameSplitter: ' - '
# order: 2
+# Use this config to overwrite the accessibility score gradation
+# map that ships with otp-ui
+#accessibilityScore:
+# gradationMap:
+# 0.0:
+# color: "#ffb5b9"
+# # The text can be overridden in the language section
+# text: 'Not Accessible'
+# icon: thumbs-down
### Use this config for the standard mode selector
# modeGroups:
@@ -319,9 +328,13 @@ itinerary:
# common:
# accessModes:
# bikeshare: Blue Bike
-# . config:
-# menuItems:
-# demo-item: Demo Item
+# config:
+# acessibilityScore:
+# gradationMap:
+# 0.0: 'Not Accessible'
+# 0.9: 'Mostly Accessible'
+# menuItems:
+# demo-item: Demo Item
### Localization section to provide language/locale settings
#localization:
diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js
index c106c1ff3..128e408dc 100644
--- a/lib/components/narrative/default/default-itinerary.js
+++ b/lib/components/narrative/default/default-itinerary.js
@@ -1,8 +1,9 @@
import coreUtils from '@opentripplanner/core-utils'
import React from 'react'
-import { FormattedMessage, FormattedNumber, FormattedTime } from 'react-intl'
+import { FormattedMessage, FormattedNumber, FormattedTime, injectIntl } from 'react-intl'
import { connect } from 'react-redux'
import styled from 'styled-components'
+import { AccessibilityRating } from '@opentripplanner/itinerary-body'
import FieldTripGroupSize from '../../admin/field-trip-itinerary-group-size'
import NarrativeItinerary from '../narrative-itinerary'
@@ -10,6 +11,11 @@ import ItineraryBody from '../line-itin/connected-itinerary-body'
import SimpleRealtimeAnnotation from '../simple-realtime-annotation'
import FormattedDuration from '../../util/formatted-duration'
import { getTotalFare } from '../../../util/state'
+import {
+ getAccessibilityScoreForItinerary,
+ itineraryHasAccessibilityScores
+} from '../../util/accessibility-routing'
+import Icon from '../../util/icon'
import ItinerarySummary from './itinerary-summary'
@@ -164,6 +170,7 @@ class DefaultItinerary extends NarrativeItinerary {
render () {
const {
+ accessibilityScoreGradationMap,
active,
configCosts,
currency,
@@ -181,7 +188,9 @@ class DefaultItinerary extends NarrativeItinerary {
return (
- {ITINERARY_ATTRIBUTES
- .sort((a, b) => {
- const aSelected = this._isSortingOnAttribute(a)
- const bSelected = this._isSortingOnAttribute(b)
- if (aSelected) return -1
- if (bSelected) return 1
- else return a.order - b.order
- })
- .map(attribute => {
- const isSelected = this._isSortingOnAttribute(attribute)
- const options = attribute.id === 'arrivalTime' ? timeOptions : {}
- if (isSelected) {
- options.isSelected = true
- options.selection = this.props.sort.type
- }
- options.LegIcon = LegIcon
- options.configCosts = configCosts
- options.currency = currency
- return (
- -
- {attribute.render(itinerary, options)}
-
- )
- })
- }
+ {ITINERARY_ATTRIBUTES.sort((a, b) => {
+ const aSelected = this._isSortingOnAttribute(a)
+ const bSelected = this._isSortingOnAttribute(b)
+ if (aSelected) return -1
+ if (bSelected) return 1
+
+ return a.order - b.order
+ }).map((attribute) => {
+ const isSelected = this._isSortingOnAttribute(attribute)
+ const options = attribute.id === 'arrivalTime' ? timeOptions : {}
+ if (isSelected) {
+ options.isSelected = true
+ options.selection = this.props.sort.type
+ }
+ options.LegIcon = LegIcon
+ options.configCosts = configCosts
+ options.currency = currency
+ return (
+ -
+ {attribute.render(itinerary, options)}
+
+ )
+ })}
+ {itineraryHasAccessibilityScores(itinerary) && (
+
+ )}
- {(active && !expanded) &&
+ {active && !expanded && (
- }
+ )}
- {(active && expanded) &&
+ {active && expanded && (
<>
{showRealtimeAnnotation && }
-
+
>
- }
+ )}
)
}
}
const mapStateToProps = (state, ownProps) => {
+ const {intl} = ownProps
+ const gradationMap = state.otp.config.accessibilityScore?.gradationMap
+
+ // Generate icons based on fa icon keys in config
+ // Override text fields if translation set
+ gradationMap && Object.keys(gradationMap).forEach(key => {
+ const {icon} = gradationMap[key]
+ if (icon && typeof icon === 'string') {
+ gradationMap[key].icon =
+ }
+
+ // As these localization keys are in the config, rather than
+ // standard language files, the message ids must be dynamically generated
+ const localizationId = `config.acessibilityScore.gradationMap.${key}`
+ const localizedText = intl.formatMessage({id: localizationId})
+ // Override the config label if a localized label exists
+ if (localizationId !== localizedText) {
+ gradationMap[key].text = localizedText
+ }
+ })
+
return {
+ accessibilityScoreGradationMap: gradationMap,
configCosts: state.otp.config.itinerary?.costs,
// The configured (ambient) currency is needed for rendering the cost
// of itineraries whether they include a fare or not, in which case
@@ -250,4 +295,4 @@ const mapStateToProps = (state, ownProps) => {
}
}
-export default connect(mapStateToProps)(DefaultItinerary)
+export default injectIntl(connect(mapStateToProps)(DefaultItinerary))
diff --git a/lib/components/narrative/line-itin/connected-itinerary-body.js b/lib/components/narrative/line-itin/connected-itinerary-body.js
index 4e41498f4..79b8f167b 100644
--- a/lib/components/narrative/line-itin/connected-itinerary-body.js
+++ b/lib/components/narrative/line-itin/connected-itinerary-body.js
@@ -43,6 +43,7 @@ class ConnectedItineraryBody extends Component {
render () {
const {
+ accessibilityScoreGradationMap,
config,
diagramVisible,
itinerary,
@@ -56,6 +57,7 @@ class ConnectedItineraryBody extends Component {
return (
{
+ return !!itinerary.legs.find(leg => !!leg.accessibilityScore)
+}
+
+/**
+ * Calculates the total itinerary score based on leg score by weighting
+ * each leg equally
+ */
+export const getAccessibilityScoreForItinerary = (itinerary) => {
+ const scores = itinerary.legs
+ .map((leg) => leg.accessibilityScore || null)
+ .filter((score) => score !== null)
+
+ return scores.reduce((prev, cur) => prev + (cur * (1 / scores.length)), 0)
+}
diff --git a/package.json b/package.json
index 0126801ce..c62a565c0 100644
--- a/package.json
+++ b/package.json
@@ -40,18 +40,18 @@
"@opentripplanner/geocoder": "^1.1.1",
"@opentripplanner/humanize-distance": "^1.1.0",
"@opentripplanner/icons": "^1.2.0",
- "@opentripplanner/itinerary-body": "^2.4.0",
- "@opentripplanner/location-field": "^1.7.0",
+ "@opentripplanner/itinerary-body": "^2.5.0",
+ "@opentripplanner/location-field": "^1.7.1",
"@opentripplanner/location-icon": "^1.4.0",
"@opentripplanner/park-and-ride-overlay": "^1.2.2",
- "@opentripplanner/printable-itinerary": "^1.3.0",
+ "@opentripplanner/printable-itinerary": "^1.3.1",
"@opentripplanner/route-viewer-overlay": "^1.1.1",
"@opentripplanner/stop-viewer-overlay": "^1.1.1",
"@opentripplanner/stops-overlay": "^3.3.1",
"@opentripplanner/transit-vehicle-overlay": "^2.3.1",
"@opentripplanner/transitive-overlay": "^1.1.2",
"@opentripplanner/trip-details": "^1.4.0",
- "@opentripplanner/trip-form": "^1.4.0",
+ "@opentripplanner/trip-form": "^1.6.0",
"@opentripplanner/trip-viewer-overlay": "^1.1.1",
"@opentripplanner/vehicle-rental-overlay": "^1.2.1",
"blob-stream": "^0.1.3",
diff --git a/yarn.lock b/yarn.lock
index b340ec254..298767336 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1071,7 +1071,7 @@
dependencies:
regenerator-runtime "^0.13.4"
-"@babel/runtime@^7.14.0":
+"@babel/runtime@^7.14.0", "@babel/runtime@^7.15.3":
version "7.15.4"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a"
integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==
@@ -1127,6 +1127,15 @@
"@conveyal/lonlat" "^1.3.0"
geocoder-arcgis "^2.0.4"
+"@conveyal/geocoder-arcgis-geojson@^0.0.3":
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/@conveyal/geocoder-arcgis-geojson/-/geocoder-arcgis-geojson-0.0.3.tgz#0d1381c1d4f0e18bcf0ab1958fc63d148e2d80da"
+ integrity sha512-eb6EgqzbaUloxYyB44TF6YIefz302azcwbdUgVYUBJY4CzPLqKxBk6c6V+cx7iqxgtug3DqLuABDCSp1wGWuag==
+ dependencies:
+ "@conveyal/lonlat" "^1.4.1"
+ eslint-config-standard-jsx "^10.0.0"
+ geocoder-arcgis "^2.0.5"
+
"@conveyal/lonlat@^1.1.2", "@conveyal/lonlat@^1.3.0", "@conveyal/lonlat@^1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@conveyal/lonlat/-/lonlat-1.4.0.tgz#18a5c1349078a779e710d24af11bc02b24127ba0"
@@ -1691,33 +1700,34 @@
"@opentripplanner/core-utils" "^4.1.0"
prop-types "^15.7.2"
-"@opentripplanner/itinerary-body@^2.4.0":
- version "2.4.0"
- resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-2.4.0.tgz#46010f813ff341d29b1a8fe96963584b1fbf8312"
- integrity sha512-tzGVs7Yg7O7CeSCe2FFshshQz7Xt80bGf3Rjj/ABAXt+DArq6KkUHRdHVcMACn3SwC2DdMPMRVvW6otsm22Azg==
+"@opentripplanner/itinerary-body@^2.5.0":
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/@opentripplanner/itinerary-body/-/itinerary-body-2.5.0.tgz#c533712b7a7f1da2e9625739d0b36a26a5587c99"
+ integrity sha512-r1ZRUpK0GuvCiRV/3hakQKtt4cQe9xYqUAMghh/V/EhBDa3MAW1mX5bGC/4QUBRa4rmU2HGQKALn5j8qrcDzrA==
dependencies:
"@opentripplanner/core-utils" "^4.1.0"
"@opentripplanner/humanize-distance" "^1.1.0"
"@opentripplanner/icons" "^1.1.0"
"@opentripplanner/location-icon" "^1.3.0"
"@styled-icons/fa-solid" "^10.34.0"
+ "@styled-icons/foundation" "^10.34.0"
currency-formatter "^1.5.5"
moment "^2.24.0"
prop-types "^15.7.2"
react-resize-detector "^4.2.1"
velocity-react "^1.4.3"
-"@opentripplanner/location-field@^1.7.0":
- version "1.7.0"
- resolved "https://registry.yarnpkg.com/@opentripplanner/location-field/-/location-field-1.7.0.tgz#57e808dfdd90c78bb2b8941bb9f8422d02244142"
- integrity sha512-xN/cyMueF5cJiGwzjPhST69aqYEZNmWoMgt9lRE/QjhoGRi/20w9/fsuVeqBZe3rb/wOnIro1Uakt3c+utHxWQ==
+"@opentripplanner/location-field@^1.7.1":
+ version "1.7.1"
+ resolved "https://registry.yarnpkg.com/@opentripplanner/location-field/-/location-field-1.7.1.tgz#90aa4c06eb13a91a5c95076d33c34ff043fd50b2"
+ integrity sha512-I98YSf0JXdqax2B9f8zlf7f2f3e3g5nw62zv5lGeC2DdmPb8rwDXskUxqq4c9xzsXW8aMN4+ihkuyMpbhhJiNA==
dependencies:
+ "@conveyal/geocoder-arcgis-geojson" "^0.0.3"
"@opentripplanner/core-utils" "^4.2.0"
"@opentripplanner/geocoder" "^1.1.1"
"@opentripplanner/humanize-distance" "^1.1.0"
- "@opentripplanner/location-icon" "^1.3.0"
+ "@opentripplanner/location-icon" "^1.4.0"
"@styled-icons/fa-solid" "^10.34.0"
- prop-types "^15.7.2"
throttle-debounce "^2.1.0"
"@opentripplanner/location-icon@^1.0.1", "@opentripplanner/location-icon@^1.3.0":
@@ -1744,10 +1754,10 @@
"@opentripplanner/from-to-location-picker" "^1.2.1"
prop-types "^15.7.2"
-"@opentripplanner/printable-itinerary@^1.3.0":
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/@opentripplanner/printable-itinerary/-/printable-itinerary-1.3.0.tgz#266c1eb945b28939739f4823bfe9a2b78b3ec2c8"
- integrity sha512-J/UPhaXtEgpnRcejCLDMPkHngNXSukgRBsG9cFpMQVwIrLLxmG7gkbYxVaLBcyPBKVlqeSnVW+EmmwFVC2bt4w==
+"@opentripplanner/printable-itinerary@^1.3.1":
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/@opentripplanner/printable-itinerary/-/printable-itinerary-1.3.1.tgz#e82ecfc91ecd5fb8f4ec4d7d3cc1548f33b3da7e"
+ integrity sha512-3T+r/U+0Pfe6CvUSWbqBBFS5uUyq3T3v19xV3jb7KplsQpYLy4nKjQcqO2/doxf/fVpWZ3XPXAFMntFHqx7pTA==
dependencies:
"@opentripplanner/core-utils" "^4.1.0"
"@opentripplanner/humanize-distance" "^1.1.0"
@@ -1815,14 +1825,19 @@
prop-types "^15.7.2"
velocity-react "^1.4.3"
-"@opentripplanner/trip-form@^1.4.0":
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/@opentripplanner/trip-form/-/trip-form-1.4.0.tgz#aa73c6bc8fc900db4d1e9a621713b89e82cabe19"
- integrity sha512-K9a7nsOp8qXc/Gj5AKY/l8gPfanIita29cK0FzfHIQLyC4ap83NlIiGEnSA3pXfEFx0iW5jhmlcuXwTCGXT+rA==
+"@opentripplanner/trip-form@^1.6.0":
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/@opentripplanner/trip-form/-/trip-form-1.6.0.tgz#d681b6dc34a9a682bd20dcc5f8a68a621d62cfb5"
+ integrity sha512-y7jR9HigmS3khepPwSKvJmYdStvBhR9q0QioGY0ErVH+PfW1aIo4V7ClVHo0TGH4vOFSJakRVyrVnOC+ENRQJw==
dependencies:
"@opentripplanner/core-utils" "^4.1.0"
- "@opentripplanner/icons" "^1.1.0"
+ "@opentripplanner/icons" "^1.2.0"
+ "@styled-icons/bootstrap" "^10.34.0"
+ "@styled-icons/boxicons-regular" "^10.38.0"
+ "@styled-icons/fa-regular" "^10.34.0"
+ "@styled-icons/fa-solid" "^10.34.0"
moment "^2.17.1"
+ react-indiana-drag-scroll "^2.0.1"
"@opentripplanner/trip-viewer-overlay@^1.1.1":
version "1.1.1"
@@ -1980,6 +1995,14 @@
lodash "^4.17.4"
read-pkg-up "^7.0.0"
+"@styled-icons/bootstrap@^10.34.0":
+ version "10.34.0"
+ resolved "https://registry.yarnpkg.com/@styled-icons/bootstrap/-/bootstrap-10.34.0.tgz#d9142e9eb70dc437f7ef62ffc40168e1ae13ab12"
+ integrity sha512-UpzdVUR7r9BNqEfPrMchJdgMZEg9eXQxLQJUXM0ouvbI5o9j21/y1dGameO4PZtYbutT/dWv5O6y24z5JWzd5w==
+ dependencies:
+ "@babel/runtime" "^7.14.0"
+ "@styled-icons/styled-icon" "^10.6.3"
+
"@styled-icons/boxicons-logos@^9.4.1":
version "9.4.1"
resolved "https://registry.yarnpkg.com/@styled-icons/boxicons-logos/-/boxicons-logos-9.4.1.tgz#774e7f73839e834445dcc0ae4c0c356221767664"
@@ -1988,6 +2011,14 @@
"@styled-icons/styled-icon" "^9.4.1"
tslib "^1.9.3"
+"@styled-icons/boxicons-regular@^10.38.0":
+ version "10.38.0"
+ resolved "https://registry.yarnpkg.com/@styled-icons/boxicons-regular/-/boxicons-regular-10.38.0.tgz#1eb80b4f94a18a9b77b11dee5204aa23378d37ec"
+ integrity sha512-xjhafoa0/EeYtQOyTw+ohuSlBx599t8l8++JH1tQlM0l73dP4icTpF4znYL+HhZeaUe3D+UhrkfWuBBua2w2Qw==
+ dependencies:
+ "@babel/runtime" "^7.15.3"
+ "@styled-icons/styled-icon" "^10.6.3"
+
"@styled-icons/boxicons-regular@^9.4.1":
version "9.4.1"
resolved "https://registry.yarnpkg.com/@styled-icons/boxicons-regular/-/boxicons-regular-9.4.1.tgz#742a1d0b5798c54f189b9278810d8110df8f9754"
@@ -5008,6 +5039,11 @@ classnames@^2.2.5:
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
+classnames@^2.2.6:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
+ integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
+
clean-stack@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.1.0.tgz#9e7fec7f3f8340a2ab4f127c80273085e8fbbdd0"
@@ -6217,6 +6253,11 @@ debounce@^1.0.0:
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131"
integrity sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==
+debounce@^1.2.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5"
+ integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==
+
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.5.1, debug@^2.6.8, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -6721,6 +6762,11 @@ duplexify@^3.4.2, duplexify@^3.6.0:
readable-stream "^2.0.0"
stream-shift "^1.0.0"
+easy-bem@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/easy-bem/-/easy-bem-1.1.1.tgz#1bfcc10425498090bcfddc0f9c000aba91399e03"
+ integrity sha512-GJRqdiy2h+EXy6a8E6R+ubmqUM08BK0FWNq41k24fup6045biQ8NXxoXimiwegMQvFFV3t1emADdGNL1TlS61A==
+
ecc-jsbn@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
@@ -7079,6 +7125,11 @@ escodegen@~1.2.0:
optionalDependencies:
source-map "~0.1.30"
+eslint-config-standard-jsx@^10.0.0:
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-config-standard-jsx/-/eslint-config-standard-jsx-10.0.0.tgz#dc24992661325a2e480e2c3091d669f19034e18d"
+ integrity sha512-hLeA2f5e06W1xyr/93/QJulN/rLbUVUmqTlexv9PRKHFwEC9ffJcH2LvJhMoEqYQBEYafedgGZXH2W8NUpt5lA==
+
eslint-config-standard-jsx@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/eslint-config-standard-jsx/-/eslint-config-standard-jsx-6.0.2.tgz#90c9aa16ac2c4f8970c13fc7efc608bacd02da70"
@@ -8251,7 +8302,7 @@ gentle-fs@^2.3.0, gentle-fs@^2.3.1:
read-cmd-shim "^1.0.1"
slide "^1.1.6"
-geocoder-arcgis@^2.0.4:
+geocoder-arcgis@^2.0.4, geocoder-arcgis@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/geocoder-arcgis/-/geocoder-arcgis-2.0.5.tgz#61cd1ff2b5b325e6d36b6e7da8138611936b2f26"
integrity sha512-KebkgRbjJ5+682DVUswqRrNHGhcCz8CG3kqP21l6xeGWDbaoSkuFzgE+fMHrKs0Ij2NJHe2/R4Z779EaXdR/3w==
@@ -14813,6 +14864,15 @@ react-fontawesome@^1.5.0:
dependencies:
prop-types "^15.5.6"
+react-indiana-drag-scroll@^2.0.1:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/react-indiana-drag-scroll/-/react-indiana-drag-scroll-2.1.0.tgz#37654eae8caced01cdecc8bce55f0382871a021d"
+ integrity sha512-Tj94Dv9PkmoKqc9nxK/dzwhtE8pP7NTxmeHqkD8KN0zad1NNE+/JsvRcK4EdqE6CdA2nMESzqPMvv1AzRXdBew==
+ dependencies:
+ classnames "^2.2.6"
+ debounce "^1.2.0"
+ easy-bem "^1.1.1"
+
react-input-autosize@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.2.tgz#fcaa7020568ec206bc04be36f4eb68e647c4d8c2"