From ddca1ced18c3131f93001d879ff55fd79912aaea Mon Sep 17 00:00:00 2001 From: sharhio Date: Wed, 5 Jul 2023 15:19:01 +0300 Subject: [PATCH 01/37] feat: dt-5325 co2 emissions --- .../ItinerarySummaryListContainer.js | 9 + app/component/ItineraryTab.js | 83 ++++++++- app/component/SummaryPage.js | 6 + app/component/SummaryPlanContainer.js | 2 + app/component/SummaryRow.js | 17 ++ app/component/itinerary-profile.scss | 1 + app/component/itinerary.scss | 166 ++++++++++++++++++ app/component/summary-row.scss | 43 +++++ app/configurations/config.default.js | 6 + app/configurations/config.hsl.js | 5 + app/configurations/config.jyvaskyla.js | 3 + app/prop-types/ItineraryShape.js | 1 + app/translations.js | 4 + app/util/planParamUtil.js | 12 +- app/util/queryUtils.js | 1 + static/assets/svg-sprite.default.svg | 3 + static/assets/svg-sprite.hsl.svg | 3 + .../mock-data/SummaryPageQueryResponse.json | 5 + 18 files changed, 359 insertions(+), 11 deletions(-) diff --git a/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js b/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js index 9037416f7a..80088f4390 100644 --- a/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js +++ b/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js @@ -55,6 +55,13 @@ function ItinerarySummaryListContainer( itineraries.length > 0 && !itineraries.includes(undefined) ) { + const lowestCo2value = Math.round( + itineraries + .filter(itinerary => itinerary.emissions > 0) + .reduce((a, b) => { + return a.emissions < b.emissions ? a : b; + }, 0).emissions, + ); const summaries = itineraries.map((itinerary, i) => ( )); if ( @@ -334,6 +342,7 @@ const containerComponent = createFragmentContainer( walkDistance startTime endTime + emissions legs { realTime realtimeState diff --git a/app/component/ItineraryTab.js b/app/component/ItineraryTab.js index 25df444366..0a2b022f07 100644 --- a/app/component/ItineraryTab.js +++ b/app/component/ItineraryTab.js @@ -7,6 +7,7 @@ import { matchShape, routerShape } from 'found'; import { FormattedMessage, intlShape } from 'react-intl'; import connectToStores from 'fluxible-addons-react/connectToStores'; +import Link from 'found/Link'; import Icon from './Icon'; import TicketInformation from './TicketInformation'; import RouteInformation from './RouteInformation'; @@ -43,6 +44,7 @@ import { import CityBikeDurationInfo from './CityBikeDurationInfo'; import { getCityBikeNetworkId } from '../util/citybikes'; import { FareShape } from '../util/shapes'; +import Loading from './Loading'; const AlertShape = PropTypes.shape({ alertSeverityLevel: PropTypes.string }); @@ -66,6 +68,7 @@ const ItineraryShape = PropTypes.shape({ }), ), fares: PropTypes.arrayOf(FareShape), + emissions: PropTypes.number, }); /* eslint-disable prettier/prettier */ @@ -80,10 +83,12 @@ class ItineraryTab extends React.Component { isMobile: PropTypes.bool.isRequired, currentTime: PropTypes.number.isRequired, hideTitle: PropTypes.bool, + carItinerary: PropTypes.array, }; static defaultProps = { hideTitle: false, + carItinerary: [], }; static contextTypes = { @@ -213,6 +218,9 @@ class ItineraryTab extends React.Component { const suggestionIndex = this.context.match.params.secondHash ? Number(this.context.match.params.secondHash) + 1 : Number(this.context.match.params.hash) + 1; + const co2value = itinerary.emissions ? Math.round(itinerary.emissions) : 0; + const carCo2Value = this.props.carItinerary && this.props.carItinerary.length > 0 ? Math.round(this.props.carItinerary[0].emissions) : 0; + const co2percentage = co2value > 0 && carCo2Value > 0 ? Math.round((co2value / carCo2Value) * 100) : 0; return (

@@ -277,15 +285,36 @@ class ItineraryTab extends React.Component { ), shouldShowFareInfo(config) && ( shouldShowFarePurchaseInfo(config,breakpoint,fares) ? ( - ) : ( ) + fares={fares} + zones={getZones(itinerary.legs)} + legs={itinerary.legs} + />) + ), + config.showCO2InItinerarySummary && co2value > 0 && ( +
+
+
+
+ + + + +
+
+
{co2value} g
+
+
+
+
+
),
{config.showRouteInformation && }
+ {config.showCO2InItinerarySummary && co2value > 0 && co2percentage > 0 ? ( +
+
+
+
+
+ + + + {config.URL.EMISSIONSINFO && ( +
+ + + +
+ )} +
+
+
+
+
+
+ ) : config.forceCarRouting && + (
+ +
)} {this.shouldShowDisclaimer(config) && (
); @@ -2930,6 +2935,7 @@ const containerComponent = createRefetchContainer( endTime ...ItineraryTab_itinerary ...SummaryPlanContainer_itineraries + emissions legs { mode ...ItineraryLine_legs diff --git a/app/component/SummaryPlanContainer.js b/app/component/SummaryPlanContainer.js index 31b4c1ef5a..13f2d79c37 100644 --- a/app/component/SummaryPlanContainer.js +++ b/app/component/SummaryPlanContainer.js @@ -344,6 +344,7 @@ const connectedContainer = createFragmentContainer( itineraries { startTime endTime + emissions legs { mode ...ItineraryLine_legs @@ -398,6 +399,7 @@ const connectedContainer = createFragmentContainer( ...ItinerarySummaryListContainer_itineraries endTime startTime + emissions legs { mode to { diff --git a/app/component/SummaryRow.js b/app/component/SummaryRow.js index 7efecffa42..4262afaccb 100644 --- a/app/component/SummaryRow.js +++ b/app/component/SummaryRow.js @@ -221,6 +221,7 @@ const SummaryRow = ( intermediatePlaces, zones, onlyHasWalkingItineraries, + lowestCo2value, ...props }, { intl, intl: { formatMessage }, config }, @@ -236,6 +237,7 @@ const SummaryRow = ( const startTime = moment(data.startTime); const endTime = moment(data.endTime); const duration = endTime.diff(startTime); + const co2value = Math.round(data.emissions); const mobile = bp => !(bp === 'large'); const legs = []; @@ -780,6 +782,14 @@ const SummaryRow = ( {(getTotalDistance(data) / 1000).toFixed(1)} km
)} + {config.showCO2InItinerarySummary && co2value > 0 && ( +
+ {lowestCo2value === co2value && ( + + )} +
{co2value} g
+
+ )}
@@ -874,10 +884,17 @@ SummaryRow.propTypes = { showCancelled: PropTypes.bool, zones: PropTypes.arrayOf(PropTypes.string), onlyHasWalkingItineraries: PropTypes.bool, + lowestCo2value: PropTypes.number, }; SummaryRow.defaultProps = { zones: [], + passive: false, + intermediatePlaces: [], + isCancelled: false, + showCancelled: false, + onlyHasWalkingItineraries: false, + lowestCo2value: 0, }; SummaryRow.contextTypes = { diff --git a/app/component/itinerary-profile.scss b/app/component/itinerary-profile.scss index a86f737619..ce2d8a4980 100644 --- a/app/component/itinerary-profile.scss +++ b/app/component/itinerary-profile.scss @@ -6,6 +6,7 @@ font-size: 15px; line-height: 18px; margin-top: 7px; + margin-bottom: 7px; &.small { font-size: 10pt; diff --git a/app/component/itinerary.scss b/app/component/itinerary.scss index 76cc36c4a2..9708aac172 100644 --- a/app/component/itinerary.scss +++ b/app/component/itinerary.scss @@ -188,6 +188,172 @@ $itinerary-tab-switch-height: 48px; } } +.itinerary-co2-information { + padding: 0 65px; +} + +.itinerary-co2-comparison { + padding: 0 0; + + .itinerary-co2-line { + position: relative; + + .divider-top, + .divider-bottom { + position: relative; + width: 400px; + height: 1px; + left: 10px; + border-top: 1px solid #dddddd; + box-sizing: border-box; + + @media print { + border: none; + } + } + } + +} + +.itinerary-co2-line { + position: relative; + + .divider-top, + .divider-bottom { + position: relative; + width: 400px; + height: 1px; + left: -5px; + border-top: 1px solid #dddddd; + box-sizing: border-box; + + @media print { + border: none; + } + } + + .co2-description-container { + display: flex; + gap: 20.01px; + justify-content: space-between; + align-items: start; + margin-top: 5px; + margin-bottom: 5px; + + .icon-container { + .icon { + &.co2-leaf { + height: 25.06px; + width: 25.6px; + margin-top: 5px; + margin-left: 5px; + } + } + } + + .itinerary-co2-description { + width: 275px; + height: 80px; + left: 70px; + top: 528px; + + font-family: $font-family; + font-style: normal; + font-weight: 325; + font-size: 15px; + line-height: 20px; + letter-spacing: -0.03em; + display: flex; + flex-direction: column; + color: #666666; + } + + } + + .co2-container { + display: flex; + align-items: center; + justify-content: space-between; + margin: 11px 10px 10px 15px; + + @include min-width(tablet) { + margin-right: 15px; + } + + .co2-title-container { + display: flex; + gap: 11.34px; + justify-content: space-between; + align-items: center; + + .icon-container { + .icon { + &.co2-leaf { + height: 13.91px; + width: 14.22px; + } + } + } + + .itinerary-co2-title { + width: 146px; + height: 18px; + left: 48px; + top: 528px; + + font-family: $font-family; + font-style: normal; + font-weight: 325; + font-size: 15px; + line-height: 18px; + /* identical to box height */ + display: flex; + align-items: center; + letter-spacing: -0.03em; + + /* Grayscale/HSL Black 75 */ + color: #666666; + } + } + + .itinerary-co2-value-container { + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: center; + padding: 2px 4px; + gap: 4px; + + min-width: 37px; + height: 20px; + right: 82.38px; + top: 416px; + overflow: visible; + + background: rgba(100, 190, 30, 0.15); + border-radius: 4px; + + .itinerary-co2-value { + min-width: 29px; + height: 16px; + + font-family: $font-family; //'Gotham Rounded'; + font-style: normal; + font-weight: 325; + font-size: 13px; + line-height: 16px; + color: #3B7F00; + /* identical to box height */ + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + letter-spacing: -0.03em; + } + } + } +} + @media print { .itinerary-main { display: block; diff --git a/app/component/summary-row.scss b/app/component/summary-row.scss index 4ce13cdf6f..fbb3e76c14 100644 --- a/app/component/summary-row.scss +++ b/app/component/summary-row.scss @@ -114,6 +114,49 @@ flex-grow: 1; margin-right: 20px; } + + .itinerary-co2-value-container { + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: center; + padding: 2px 4px; + gap: 4px; + margin-right: 6.38px; + min-width: 37px; + height: 20px; + right: 82.38px; + top: 416px; + overflow: visible; + background: rgba(100, 190, 30, 0.15); + border-radius: 4px; + + .icon-container { + .icon { + &.co2-leaf { + width: 11px; + height: 11px; + margin-left: 2px; + } + } + } + .itinerary-co2-value { + min-width: 29px; + height: 16px; + + font-family: $font-family; + font-style: normal; + font-weight: 325; + font-size: 13px; + line-height: 16px; + color: #3B7F00; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + letter-spacing: -0.03em; + } + } } .itinerary-end-time-and-distance { diff --git a/app/configurations/config.default.js b/app/configurations/config.default.js index d586e4b53d..494e2a019e 100644 --- a/app/configurations/config.default.js +++ b/app/configurations/config.default.js @@ -189,6 +189,7 @@ export default { defaultSettings: { accessibilityOption: 0, bikeSpeed: 5.55, + forceCarRouting: false, ticketTypes: 'none', walkBoardCost: 120, walkReluctance: 1.8, @@ -197,6 +198,7 @@ export default { includeParkAndRideSuggestions: false, includeCarSuggestions: false, showBikeAndParkItineraries: false, + showCO2InItinerarySummary: false, }, /** @@ -860,4 +862,8 @@ export default { }, showAlternativeLegs: true, + + // DT-5325 + showCO2InItinerarySummary: false, + forceCarRouting: false, }; diff --git a/app/configurations/config.hsl.js b/app/configurations/config.hsl.js index b4d8b88782..22cab4b195 100644 --- a/app/configurations/config.hsl.js +++ b/app/configurations/config.hsl.js @@ -47,6 +47,7 @@ export default { BANNERS: BANNER_URL, HSL_FI_SUGGESTIONS: 'https://content.hsl.fi/api/v1/search/suggestions', EMBEDDED_SEARCH_GENERATION: '/reittiopas-elementti', + EMISSIONSINFO: 'https://www.hsl.fi/hsl/sahkobussit/ymparisto-lukuina', }, indexPath: 'etusivu', @@ -498,6 +499,10 @@ export default { showBikeAndPublicItineraries: true, showBikeAndParkItineraries: true, + // DT-5325 + showCO2InItinerarySummary: true, + forceCarRouting: true, + includeCarSuggestions: false, includeParkAndRideSuggestions: true, // Include both bike and park and bike and public diff --git a/app/configurations/config.jyvaskyla.js b/app/configurations/config.jyvaskyla.js index c5a0aad82a..6a9a6e3fbf 100644 --- a/app/configurations/config.jyvaskyla.js +++ b/app/configurations/config.jyvaskyla.js @@ -163,4 +163,7 @@ export default configMerger(walttiConfig, { stops: true, itinerary: true, }, + // DT-5325 + showCO2InItinerarySummary: true, + forceCarRouting: true, }); diff --git a/app/prop-types/ItineraryShape.js b/app/prop-types/ItineraryShape.js index e7a5fb12b3..3c72ff4349 100644 --- a/app/prop-types/ItineraryShape.js +++ b/app/prop-types/ItineraryShape.js @@ -4,4 +4,5 @@ export default PropTypes.shape({ endTime: PropTypes.number, startTime: PropTypes.number, legs: PropTypes.arrayOf(PropTypes.object), + emissions: PropTypes.number, }); diff --git a/app/translations.js b/app/translations.js index ffedfbf9a2..bb8c7a1fb0 100644 --- a/app/translations.js +++ b/app/translations.js @@ -2228,6 +2228,10 @@ const translations = { inquiry: 'Mitä pidät uudesta Reittioppaasta? Kerro se meille! ', instructions: 'Ohjeet', 'is-open': 'Avoinna:', + 'itinerary-co2.description': + 'Tämän matkan CO2 päästö on {co2value} g, joka on {co2percentage} % verrattuna henkilöautolla suoritettuun matkaan.', + 'itinerary-co2.link': 'Näin vähennämme päästöjä ›', + 'itinerary-co2.title': 'Matkan CO2-päästöt', 'itinerary-details.biking-leg': '{time} pyöräile kohteesta {origin} {distance} {to} {destination}. Matka-aika {duration}', 'itinerary-details.car-leg': diff --git a/app/util/planParamUtil.js b/app/util/planParamUtil.js index 49f4760968..ca6c1f8b19 100644 --- a/app/util/planParamUtil.js +++ b/app/util/planParamUtil.js @@ -174,11 +174,15 @@ const getShouldMakeCarQuery = ( settings, defaultSettings, ) => { + const forceCarRouting = config.forceCarRouting + ? config.forceCarRouting + : defaultSettings.forceCarRouting; + const includeCarSuggestions = settings.includeCarSuggestions + ? settings.includeCarSuggestions + : defaultSettings.includeCarSuggestions; return ( - linearDistance > config.suggestCarMinDistance && - (settings.includeCarSuggestions - ? settings.includeCarSuggestions - : defaultSettings.includeCarSuggestions) + forceCarRouting || + (linearDistance > config.suggestCarMinDistance && includeCarSuggestions) ); }; diff --git a/app/util/queryUtils.js b/app/util/queryUtils.js index 2b4008e09e..73faaeb9a5 100644 --- a/app/util/queryUtils.js +++ b/app/util/queryUtils.js @@ -264,6 +264,7 @@ export const moreItinerariesQuery = graphql` endTime ...ItineraryTab_itinerary ...SummaryPlanContainer_itineraries + emissions legs { mode ...ItineraryLine_legs diff --git a/static/assets/svg-sprite.default.svg b/static/assets/svg-sprite.default.svg index 5ae7236eaf..ecd8821972 100644 --- a/static/assets/svg-sprite.default.svg +++ b/static/assets/svg-sprite.default.svg @@ -2730,5 +2730,8 @@ + + + diff --git a/static/assets/svg-sprite.hsl.svg b/static/assets/svg-sprite.hsl.svg index f76807fe45..b59e9a849d 100644 --- a/static/assets/svg-sprite.hsl.svg +++ b/static/assets/svg-sprite.hsl.svg @@ -2659,5 +2659,8 @@ + + + diff --git a/test/e2e/mock-data/SummaryPageQueryResponse.json b/test/e2e/mock-data/SummaryPageQueryResponse.json index dc3aee0732..3a77b2da03 100644 --- a/test/e2e/mock-data/SummaryPageQueryResponse.json +++ b/test/e2e/mock-data/SummaryPageQueryResponse.json @@ -33,6 +33,7 @@ "type":"regular" } ], + "emissions": 123.4, "legs":[ { "mode":"WALK", @@ -1389,6 +1390,7 @@ "type":"regular" } ], + "emissions": 123.4, "legs":[ { "mode":"WALK", @@ -3915,6 +3917,7 @@ "type":"regular" } ], + "emissions": 123.4, "legs":[ { "mode":"WALK", @@ -7257,6 +7260,7 @@ "type":"regular" } ], + "emissions": 123.4, "legs":[ { "mode":"WALK", @@ -10314,6 +10318,7 @@ "type":"regular" } ], + "emissions": 123.4, "legs":[ { "mode":"WALK", From ec566329844818cfaa99d99b0db498204150bdac Mon Sep 17 00:00:00 2001 From: sharhio Date: Wed, 5 Jul 2023 15:37:26 +0300 Subject: [PATCH 02/37] feat: dt-5325 co2 schemas --- build/schema.graphql | 2 ++ build/schema.json | 12 ++++++++++++ .../schema/schema.graphql | 2 ++ 3 files changed, 16 insertions(+) diff --git a/build/schema.graphql b/build/schema.graphql index dd6bf02526..76dd44d059 100644 --- a/build/schema.graphql +++ b/build/schema.graphql @@ -1374,6 +1374,8 @@ type Itinerary { """How far the user has to walk, in meters.""" walkDistance: Float + emissions: Float + """ A list of Legs. Each Leg is either a walking (cycling, car) portion of the itinerary, or a transit leg on a particular vehicle. So a itinerary where the diff --git a/build/schema.json b/build/schema.json index cee36de199..d57933a0c7 100644 --- a/build/schema.json +++ b/build/schema.json @@ -3070,6 +3070,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "emissions", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "legs", "description": "A list of Legs. Each Leg is either a walking (cycling, car) portion of the\nitinerary, or a transit leg on a particular vehicle. So a itinerary where the\nuser walks to the Q train, transfers to the 6, then walks to their\ndestination, has four legs.", diff --git a/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql b/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql index dd6bf02526..76dd44d059 100644 --- a/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql +++ b/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql @@ -1374,6 +1374,8 @@ type Itinerary { """How far the user has to walk, in meters.""" walkDistance: Float + emissions: Float + """ A list of Legs. Each Leg is either a walking (cycling, car) portion of the itinerary, or a transit leg on a particular vehicle. So a itinerary where the From 8e597114381b6e9ed66337adedd3f424464f2a66 Mon Sep 17 00:00:00 2001 From: sharhio Date: Wed, 5 Jul 2023 17:12:46 +0300 Subject: [PATCH 03/37] feat: dt-5325 co2 schema update --- build/schema.graphql | 1 + build/schema.json | 2 +- .../digitransit-search-util-query-utils/schema/schema.graphql | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/build/schema.graphql b/build/schema.graphql index 76dd44d059..ae1519a5ad 100644 --- a/build/schema.graphql +++ b/build/schema.graphql @@ -1374,6 +1374,7 @@ type Itinerary { """How far the user has to walk, in meters.""" walkDistance: Float + """CO2 emissions of the trip on this itinerary, in grams per person.""" emissions: Float """ diff --git a/build/schema.json b/build/schema.json index d57933a0c7..70c70f3666 100644 --- a/build/schema.json +++ b/build/schema.json @@ -3072,7 +3072,7 @@ }, { "name": "emissions", - "description": null, + "description": "CO2 emissions of the trip on this itinerary, in grams per person.", "args": [], "type": { "kind": "SCALAR", diff --git a/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql b/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql index 76dd44d059..ae1519a5ad 100644 --- a/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql +++ b/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql @@ -1374,6 +1374,7 @@ type Itinerary { """How far the user has to walk, in meters.""" walkDistance: Float + """CO2 emissions of the trip on this itinerary, in grams per person.""" emissions: Float """ From bc1e0c5c1c5e36dae46db1710ffc65350b531f76 Mon Sep 17 00:00:00 2001 From: Janne Antikainen Date: Wed, 19 Jul 2023 12:43:16 +0300 Subject: [PATCH 04/37] chore: mobile styling, show emission info in mobile also --- app/component/ItineraryTab.js | 5 ++--- app/component/MobileItineraryWrapper.js | 3 +++ app/component/SummaryPage.js | 3 ++- app/component/itinerary.scss | 12 ++++++++++-- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/app/component/ItineraryTab.js b/app/component/ItineraryTab.js index 2465b0dbca..a19ada5f98 100644 --- a/app/component/ItineraryTab.js +++ b/app/component/ItineraryTab.js @@ -186,7 +186,6 @@ class ItineraryTab extends React.Component { if (!itinerary || !itinerary.legs[0]) { return null; } - const fares = getFares(itinerary.fares, getRoutes(itinerary.legs), config); const extraProps = this.setExtraProps(itinerary); const legsWithRentalBike = compressLegs(itinerary.legs).filter(leg => @@ -297,9 +296,9 @@ class ItineraryTab extends React.Component { />) ), config.showCO2InItinerarySummary && co2value > 0 && ( -
+
-
+
diff --git a/app/component/MobileItineraryWrapper.js b/app/component/MobileItineraryWrapper.js index 5456b41cde..c01b6cc2d0 100644 --- a/app/component/MobileItineraryWrapper.js +++ b/app/component/MobileItineraryWrapper.js @@ -40,6 +40,7 @@ const MobileItineraryWrapper = (props, context) => { focusToPoint={props.focusToPoint} focusToLeg={props.focusToLeg} isMobile + carItinerary={props.carItinerary} />
); @@ -72,12 +73,14 @@ MobileItineraryWrapper.propTypes = { plan: PropTypes.object, serviceTimeRange: PropTypes.object.isRequired, onSwipe: PropTypes.func, + carItinerary: PropTypes.array, }; MobileItineraryWrapper.contextTypes = { router: routerShape.isRequired, match: matchShape.isRequired, intl: intlShape.isRequired, + carItinerary: [], }; export default MobileItineraryWrapper; diff --git a/app/component/SummaryPage.js b/app/component/SummaryPage.js index 12dd05da3e..3d90011b99 100644 --- a/app/component/SummaryPage.js +++ b/app/component/SummaryPage.js @@ -2471,7 +2471,7 @@ class SummaryPage extends React.Component { focusToPoint={this.focusToPoint} focusToLeg={this.focusToLeg} isMobile={false} - carItinerary={this.state.carPlan?.itineraries} + carItinerary={carPlan?.itineraries} />
); @@ -2703,6 +2703,7 @@ class SummaryPage extends React.Component { serviceTimeRange={this.props.serviceTimeRange} focusToLeg={this.focusToLeg} onSwipe={this.changeHash} + carItinerary={carPlan?.itineraries} > {this.props.content && combinedItineraries.map((itinerary, i) => diff --git a/app/component/itinerary.scss b/app/component/itinerary.scss index 9708aac172..0440c38794 100644 --- a/app/component/itinerary.scss +++ b/app/component/itinerary.scss @@ -190,6 +190,12 @@ $itinerary-tab-switch-height: 48px; .itinerary-co2-information { padding: 0 65px; + &.mobile { + padding: 0 15px; + .divider-bottom { + width: 365px; + } + } } .itinerary-co2-comparison { @@ -206,7 +212,6 @@ $itinerary-tab-switch-height: 48px; left: 10px; border-top: 1px solid #dddddd; box-sizing: border-box; - @media print { border: none; } @@ -275,7 +280,10 @@ $itinerary-tab-switch-height: 48px; align-items: center; justify-content: space-between; margin: 11px 10px 10px 15px; - + &.mobile { + flex-direction: row; + margin: 11px 10px 10px 6px + } @include min-width(tablet) { margin-right: 15px; } From 67aa24468e5dea5b910bac6fbdbc648f97709d4b Mon Sep 17 00:00:00 2001 From: Janne Antikainen Date: Wed, 19 Jul 2023 14:32:19 +0300 Subject: [PATCH 05/37] chore: add placeholders for translations --- app/translations.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/translations.js b/app/translations.js index 29cb9f18b7..9101f73b72 100644 --- a/app/translations.js +++ b/app/translations.js @@ -1092,6 +1092,10 @@ const translations = { inquiry: 'How did you find the new Journey Planner? Please tell us!', instructions: 'Instructions', 'is-open': 'Open:', + 'itinerary-co2.description': + 'Tämän matkan CO2 päästö on {co2value} g, joka on {co2percentage} % verrattuna henkilöautolla suoritettuun matkaan.', + 'itinerary-co2.link': 'Näin vähennämme päästöjä ›', + 'itinerary-co2.title': 'Matkan CO2-päästöt', 'itinerary-details.biking-leg': 'At {time} cycle {distance} from {origin} to {to} {destination}. Estimated time {duration}', 'itinerary-details.car-leg': @@ -4137,6 +4141,10 @@ const translations = { inquiry: 'Vad tycker du om den nya Reseplaneraren. Berätta för oss!', instructions: 'Anvisningar', 'is-open': 'Öppet:', + 'itinerary-co2.description': + 'Tämän matkan CO2 päästö on {co2value} g, joka on {co2percentage} % verrattuna henkilöautolla suoritettuun matkaan.', + 'itinerary-co2.link': 'Näin vähennämme päästöjä ›', + 'itinerary-co2.title': 'Matkan CO2-päästöt', 'itinerary-details.biking-leg': '{time} cykla {distance} från {origin} till {to} {destination}. Restid {duration}', 'itinerary-details.car-leg': From a22b193e0717acddeca840bfb302b6dacf17ea24 Mon Sep 17 00:00:00 2001 From: Janne Antikainen Date: Wed, 19 Jul 2023 14:39:42 +0300 Subject: [PATCH 06/37] chore: update localStorage --- app/store/localStorage.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/store/localStorage.js b/app/store/localStorage.js index 345d6173d8..f49037f929 100644 --- a/app/store/localStorage.js +++ b/app/store/localStorage.js @@ -132,6 +132,14 @@ export function setCustomizedSettings(data) { data.showBikeAndParkItineraries, oldSettings.showBikeAndParkItineraries, ), + forceCarRouting: getValueOrDefault( + data.forceCarRouting, + oldSettings.forceCarRouting, + ), + showCO2InItinerarySummary: getValueOrDefault( + data.showCO2InItinerarySummary, + oldSettings.showCO2InItinerarySummary, + ), }; setItem('customizedSettings', newSettings); From b3488b2c6925e2481efc0d328bf3bfec3bfb071c Mon Sep 17 00:00:00 2001 From: Janne Antikainen Date: Mon, 24 Jul 2023 14:27:56 +0300 Subject: [PATCH 07/37] refactor: do not add car value when car route is not available --- app/component/ItineraryTab.js | 15 +++++++++++---- app/component/itinerary.scss | 4 +++- app/translations.js | 6 ++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/app/component/ItineraryTab.js b/app/component/ItineraryTab.js index a19ada5f98..6eb667443d 100644 --- a/app/component/ItineraryTab.js +++ b/app/component/ItineraryTab.js @@ -220,6 +220,7 @@ class ItineraryTab extends React.Component { const co2value = itinerary.emissions ? Math.round(itinerary.emissions) : 0; const carCo2Value = this.props.carItinerary && this.props.carItinerary.length > 0 ? Math.round(this.props.carItinerary[0].emissions) : 0; const co2percentage = co2value > 0 && carCo2Value > 0 ? Math.round((co2value / carCo2Value) * 100) : 0; + const co2SimpleDesc = co2percentage === 0 && carCo2Value === 0; return (

@@ -354,22 +355,28 @@ class ItineraryTab extends React.Component { /> {config.showRouteInformation && }

- {config.showCO2InItinerarySummary && co2value > 0 && co2percentage > 0 ? ( + {config.showCO2InItinerarySummary && co2value > 0 ? (
- - + { carCo2Value > 0 && co2percentage > 0 ? ( + />): + } {config.URL.EMISSIONSINFO && (
Date: Wed, 26 Jul 2023 10:54:41 +0300 Subject: [PATCH 08/37] refactor: remove loading as it stays forever if no data --- app/component/ItineraryTab.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/component/ItineraryTab.js b/app/component/ItineraryTab.js index 6eb667443d..72b343b8d8 100644 --- a/app/component/ItineraryTab.js +++ b/app/component/ItineraryTab.js @@ -44,7 +44,6 @@ import { import CityBikeDurationInfo from './CityBikeDurationInfo'; import { getCityBikeNetworkId } from '../util/citybikes'; import { FareShape } from '../util/shapes'; -import Loading from './Loading'; const AlertShape = PropTypes.shape({ alertSeverityLevel: PropTypes.string }); @@ -396,10 +395,8 @@ class ItineraryTab extends React.Component {
- ) : config.forceCarRouting && - (
- -
)} + ) : + (null)} {this.shouldShowDisclaimer(config) && (
Date: Wed, 26 Jul 2023 13:32:22 +0300 Subject: [PATCH 09/37] feat: translations and links to emissions info --- app/component/ItineraryTab.js | 7 +++++-- app/configurations/config.hsl.js | 6 +++++- app/translations.js | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/component/ItineraryTab.js b/app/component/ItineraryTab.js index 72b343b8d8..d94687b8f0 100644 --- a/app/component/ItineraryTab.js +++ b/app/component/ItineraryTab.js @@ -81,6 +81,7 @@ class ItineraryTab extends React.Component { focusToLeg: PropTypes.func.isRequired, isMobile: PropTypes.bool.isRequired, currentTime: PropTypes.number.isRequired, + lang: PropTypes.string.isRequired, hideTitle: PropTypes.bool, carItinerary: PropTypes.array, }; @@ -220,6 +221,7 @@ class ItineraryTab extends React.Component { const carCo2Value = this.props.carItinerary && this.props.carItinerary.length > 0 ? Math.round(this.props.carItinerary[0].emissions) : 0; const co2percentage = co2value > 0 && carCo2Value > 0 ? Math.round((co2value / carCo2Value) * 100) : 0; const co2SimpleDesc = co2percentage === 0 && carCo2Value === 0; + return (

@@ -379,7 +381,7 @@ class ItineraryTab extends React.Component { {config.URL.EMISSIONSINFO && (
({ + connectToStores(ItineraryTab, ['TimeStore', 'PreferencesStore'], context => ({ currentTime: context.getStore('TimeStore').getCurrentTime().unix(), + lang: context.getStore('PreferencesStore').getLanguage(), })), { plan: graphql` diff --git a/app/configurations/config.hsl.js b/app/configurations/config.hsl.js index 19e8b90072..11caa7355c 100644 --- a/app/configurations/config.hsl.js +++ b/app/configurations/config.hsl.js @@ -47,7 +47,11 @@ export default { BANNERS: BANNER_URL, HSL_FI_SUGGESTIONS: 'https://content.hsl.fi/api/v1/search/suggestions', EMBEDDED_SEARCH_GENERATION: '/reittiopas-elementti', - EMISSIONSINFO: 'https://www.hsl.fi/hsl/sahkobussit/ymparisto-lukuina', + EMISSIONSINFO: { + fi: 'https://www.hsl.fi/hsl/sahkobussit/ymparisto-lukuina', + sv: ' http://www.hsl.fi/sv/reseplaneraren_co2', + en: ' http://www.hsl.fi/en/journey_planner_co2', + }, }, indexPath: 'etusivu', diff --git a/app/translations.js b/app/translations.js index d4302bb086..7bf1455e55 100644 --- a/app/translations.js +++ b/app/translations.js @@ -1096,7 +1096,7 @@ const translations = { 'Tämän matkan CO2 päästö on {co2value} g, joka on {co2percentage} % verrattuna henkilöautolla suoritettuun matkaan.', 'itinerary-co2.description-simple': 'Tämän matkan CO2 päästö on {co2value} g.', - 'itinerary-co2.link': 'Näin vähennämme päästöjä ›', + 'itinerary-co2.link': 'This is how we reduce emissions ›', 'itinerary-co2.title': 'Matkan CO2-päästöt', 'itinerary-details.biking-leg': 'At {time} cycle {distance} from {origin} to {to} {destination}. Estimated time {duration}', @@ -4149,7 +4149,7 @@ const translations = { 'Tämän matkan CO2 päästö on {co2value} g, joka on {co2percentage} % verrattuna henkilöautolla suoritettuun matkaan.', 'itinerary-co2.description-simple': 'Tämän matkan CO2 päästö on {co2value} g.', - 'itinerary-co2.link': 'Näin vähennämme päästöjä ›', + 'itinerary-co2.link': 'Så minskar vi utsläppen ›', 'itinerary-co2.title': 'Matkan CO2-päästöt', 'itinerary-details.biking-leg': '{time} cykla {distance} från {origin} till {to} {destination}. Restid {duration}', From 6de024dd40f6203bb21424715cfefea74ac4a439 Mon Sep 17 00:00:00 2001 From: Janne Antikainen Date: Wed, 26 Jul 2023 13:35:08 +0300 Subject: [PATCH 10/37] chore: edit test --- test/unit/ItineraryTab.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/unit/ItineraryTab.test.js b/test/unit/ItineraryTab.test.js index db03bbd6f8..85de12847d 100644 --- a/test/unit/ItineraryTab.test.js +++ b/test/unit/ItineraryTab.test.js @@ -19,6 +19,7 @@ describe('', () => { }, isMobile: false, currentTime: 0, + lang: 'fi', }; const wrapper = shallowWithIntl(, { context: { ...mockContext }, From c6929bea76cca5f6cd8ea93b1c9e5346519300ac Mon Sep 17 00:00:00 2001 From: sharhio Date: Mon, 7 Aug 2023 10:10:06 +0300 Subject: [PATCH 11/37] feat: ft-5325 simplifying css --- app/component/ItineraryTab.js | 1 - app/component/itinerary.scss | 31 ++++--------------------------- 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/app/component/ItineraryTab.js b/app/component/ItineraryTab.js index d94687b8f0..1508204673 100644 --- a/app/component/ItineraryTab.js +++ b/app/component/ItineraryTab.js @@ -314,7 +314,6 @@ class ItineraryTab extends React.Component {
{co2value} g

-
), diff --git a/app/component/itinerary.scss b/app/component/itinerary.scss index 062e83f2d3..28e41adec5 100644 --- a/app/component/itinerary.scss +++ b/app/component/itinerary.scss @@ -132,7 +132,7 @@ $itinerary-tab-switch-height: 48px; } } - div.itinerary-ticket-information { + div.itinerary-ticket-information, .itinerary-co2-information { flex-grow: 0; flex-shrink: 0; } @@ -189,7 +189,6 @@ $itinerary-tab-switch-height: 48px; } .itinerary-co2-information { - padding: 0 65px; &.mobile { padding: 0 15px; .divider-bottom { @@ -198,28 +197,6 @@ $itinerary-tab-switch-height: 48px; } } -.itinerary-co2-comparison { - padding: 0 0; - - .itinerary-co2-line { - position: relative; - - .divider-top, - .divider-bottom { - position: relative; - width: 400px; - height: 1px; - left: 10px; - border-top: 1px solid #dddddd; - box-sizing: border-box; - @media print { - border: none; - } - } - } - -} - .itinerary-co2-line { position: relative; @@ -609,7 +586,7 @@ $itinerary-tab-switch-height: 48px; } } -.itinerary-ticket-information { +.itinerary-ticket-information, .itinerary-co2-information { display: flex; color: $gray; font-weight: $font-weight-book; @@ -641,7 +618,7 @@ $itinerary-tab-switch-height: 48px; } } - .itinerary-ticket-type { + .itinerary-ticket-type, .itinerary-co2-line { display: flex; flex-direction: column; flex-grow: 1; @@ -739,7 +716,7 @@ $itinerary-tab-switch-height: 48px; } .desktop { - .itinerary-ticket-information { + .itinerary-ticket-information, .itinerary-co2-information { font-size: 0.9375rem; width: 400px; margin: 0 60px; From cffe172ff68eaeaa2f37ff54b1f6b95835aee63e Mon Sep 17 00:00:00 2001 From: sharhio Date: Mon, 7 Aug 2023 12:05:01 +0300 Subject: [PATCH 12/37] feat: dt-5325 no localstorage, no separate force car setting --- app/component/ItineraryTab.js | 3 +-- app/configurations/config.hsl.js | 1 - app/store/localStorage.js | 8 -------- app/util/planParamUtil.js | 7 ++++--- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/app/component/ItineraryTab.js b/app/component/ItineraryTab.js index 1508204673..f67f992916 100644 --- a/app/component/ItineraryTab.js +++ b/app/component/ItineraryTab.js @@ -416,9 +416,8 @@ class ItineraryTab extends React.Component { } const withRelay = createFragmentContainer( - connectToStores(ItineraryTab, ['TimeStore', 'PreferencesStore'], context => ({ + connectToStores(ItineraryTab, ['TimeStore'], context => ({ currentTime: context.getStore('TimeStore').getCurrentTime().unix(), - lang: context.getStore('PreferencesStore').getLanguage(), })), { plan: graphql` diff --git a/app/configurations/config.hsl.js b/app/configurations/config.hsl.js index 11caa7355c..b21deec9d2 100644 --- a/app/configurations/config.hsl.js +++ b/app/configurations/config.hsl.js @@ -508,7 +508,6 @@ export default { // DT-5325 showCO2InItinerarySummary: true, - forceCarRouting: true, includeCarSuggestions: false, includeParkAndRideSuggestions: true, diff --git a/app/store/localStorage.js b/app/store/localStorage.js index f49037f929..345d6173d8 100644 --- a/app/store/localStorage.js +++ b/app/store/localStorage.js @@ -132,14 +132,6 @@ export function setCustomizedSettings(data) { data.showBikeAndParkItineraries, oldSettings.showBikeAndParkItineraries, ), - forceCarRouting: getValueOrDefault( - data.forceCarRouting, - oldSettings.forceCarRouting, - ), - showCO2InItinerarySummary: getValueOrDefault( - data.showCO2InItinerarySummary, - oldSettings.showCO2InItinerarySummary, - ), }; setItem('customizedSettings', newSettings); diff --git a/app/util/planParamUtil.js b/app/util/planParamUtil.js index ca6c1f8b19..17bb2f811a 100644 --- a/app/util/planParamUtil.js +++ b/app/util/planParamUtil.js @@ -174,9 +174,10 @@ const getShouldMakeCarQuery = ( settings, defaultSettings, ) => { - const forceCarRouting = config.forceCarRouting - ? config.forceCarRouting - : defaultSettings.forceCarRouting; + const forceCarRouting = config.showCO2InItinerarySummary + ? config.showCO2InItinerarySummary + : defaultSettings.showCO2InItinerarySummary; + const includeCarSuggestions = settings.includeCarSuggestions ? settings.includeCarSuggestions : defaultSettings.includeCarSuggestions; From 18b462849911c2ecf7b3d465a6d0a40be8cf9767 Mon Sep 17 00:00:00 2001 From: sharhio Date: Mon, 7 Aug 2023 12:28:55 +0300 Subject: [PATCH 13/37] feat: dt-5325 documentation for co2 setting, removed from defaultsettings --- app/configurations/config.default.js | 5 +---- app/configurations/config.hsl.js | 2 +- app/configurations/config.jyvaskyla.js | 3 +-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/app/configurations/config.default.js b/app/configurations/config.default.js index 494e2a019e..e41ee485f5 100644 --- a/app/configurations/config.default.js +++ b/app/configurations/config.default.js @@ -189,7 +189,6 @@ export default { defaultSettings: { accessibilityOption: 0, bikeSpeed: 5.55, - forceCarRouting: false, ticketTypes: 'none', walkBoardCost: 120, walkReluctance: 1.8, @@ -198,7 +197,6 @@ export default { includeParkAndRideSuggestions: false, includeCarSuggestions: false, showBikeAndParkItineraries: false, - showCO2InItinerarySummary: false, }, /** @@ -863,7 +861,6 @@ export default { showAlternativeLegs: true, - // DT-5325 + // Notice! Turning on this setting forces the search for car routes. showCO2InItinerarySummary: false, - forceCarRouting: false, }; diff --git a/app/configurations/config.hsl.js b/app/configurations/config.hsl.js index b21deec9d2..a52b12bcc0 100644 --- a/app/configurations/config.hsl.js +++ b/app/configurations/config.hsl.js @@ -506,7 +506,7 @@ export default { showBikeAndPublicItineraries: true, showBikeAndParkItineraries: true, - // DT-5325 + // DT-5325 Notice! Turning on this setting forces the search for car routes. showCO2InItinerarySummary: true, includeCarSuggestions: false, diff --git a/app/configurations/config.jyvaskyla.js b/app/configurations/config.jyvaskyla.js index 6a9a6e3fbf..8dcc70bdb5 100644 --- a/app/configurations/config.jyvaskyla.js +++ b/app/configurations/config.jyvaskyla.js @@ -163,7 +163,6 @@ export default configMerger(walttiConfig, { stops: true, itinerary: true, }, - // DT-5325 + // DT-5325 Notice! Turning on this setting forces the search for car routes. showCO2InItinerarySummary: true, - forceCarRouting: true, }); From e1ed13f0d1d8644e585c686e551cadc9108c2eb4 Mon Sep 17 00:00:00 2001 From: sharhio Date: Tue, 8 Aug 2023 08:31:25 +0300 Subject: [PATCH 14/37] feat: dt-5325 accessibility improvements --- app/component/ItineraryTab.js | 42 +++++++++++++++++------------------ app/component/SummaryRow.js | 12 ++++++++++ 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/app/component/ItineraryTab.js b/app/component/ItineraryTab.js index f67f992916..5fec447d1b 100644 --- a/app/component/ItineraryTab.js +++ b/app/component/ItineraryTab.js @@ -303,7 +303,7 @@ class ItineraryTab extends React.Component {
- +
- + { carCo2Value > 0 && co2percentage > 0 ? ( ): - } {config.URL.EMISSIONSINFO && ( -
- - - -
- )} +
+ + + +
+ )}
- ) : - (null)} + ) : + (null)} {this.shouldShowDisclaimer(config) && (
); + const co2summary = ( +
+ +
+ ); const ariaLabelMessage = intl.formatMessage( { @@ -733,6 +744,7 @@ const SummaryRow = ( />

{textSummary} + {config.showCO2InItinerarySummary && co2value > 0 && co2summary}
Date: Wed, 13 Sep 2023 08:37:22 +0300 Subject: [PATCH 15/37] feat: dt-5325 allow showing zero emissions --- app/component/ItineraryTab.js | 6 +++--- app/component/SummaryRow.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/component/ItineraryTab.js b/app/component/ItineraryTab.js index 5fec447d1b..b942d9d898 100644 --- a/app/component/ItineraryTab.js +++ b/app/component/ItineraryTab.js @@ -297,7 +297,7 @@ class ItineraryTab extends React.Component { legs={itinerary.legs} />) ), - config.showCO2InItinerarySummary && co2value > 0 && ( + config.showCO2InItinerarySummary && co2value >= 0 && (
@@ -355,7 +355,7 @@ class ItineraryTab extends React.Component { /> {config.showRouteInformation && }
- {config.showCO2InItinerarySummary && co2value > 0 ? ( + {config.showCO2InItinerarySummary && co2value >= 0 ? (
@@ -363,7 +363,7 @@ class ItineraryTab extends React.Component {
- { carCo2Value > 0 && co2percentage > 0 ? ( = 0 && co2percentage >= 0 ? ( {textSummary} - {config.showCO2InItinerarySummary && co2value > 0 && co2summary} + {config.showCO2InItinerarySummary && co2value >= 0 && co2summary}
)} - {config.showCO2InItinerarySummary && co2value > 0 && ( + {config.showCO2InItinerarySummary && co2value >= 0 && (
{lowestCo2value === co2value && ( From 066feac5c4f7e60c7405d366a43486892ae86493 Mon Sep 17 00:00:00 2001 From: sharhio Date: Thu, 14 Sep 2023 13:07:38 +0300 Subject: [PATCH 16/37] feat: dt-5325 show 0 emissions, short description when no car iti --- .../ItinerarySummaryListContainer.js | 2 +- app/component/ItineraryTab.js | 45 ++++++++++--------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js b/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js index 80088f4390..2edd0c7498 100644 --- a/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js +++ b/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js @@ -57,7 +57,7 @@ function ItinerarySummaryListContainer( ) { const lowestCo2value = Math.round( itineraries - .filter(itinerary => itinerary.emissions > 0) + .filter(itinerary => itinerary.emissions >= 0) .reduce((a, b) => { return a.emissions < b.emissions ? a : b; }, 0).emissions, diff --git a/app/component/ItineraryTab.js b/app/component/ItineraryTab.js index b942d9d898..8034a120c8 100644 --- a/app/component/ItineraryTab.js +++ b/app/component/ItineraryTab.js @@ -220,7 +220,7 @@ class ItineraryTab extends React.Component { const co2value = itinerary.emissions ? Math.round(itinerary.emissions) : 0; const carCo2Value = this.props.carItinerary && this.props.carItinerary.length > 0 ? Math.round(this.props.carItinerary[0].emissions) : 0; const co2percentage = co2value > 0 && carCo2Value > 0 ? Math.round((co2value / carCo2Value) * 100) : 0; - const co2SimpleDesc = co2percentage === 0 && carCo2Value === 0; + const co2SimpleDesc = co2percentage === 0 || carCo2Value === 0; return (
@@ -244,7 +244,7 @@ class ItineraryTab extends React.Component { futureText={extraProps.futureText} isMultiRow={extraProps.isMultiRow} isMobile={this.props.isMobile} - hideBottomDivider={shouldShowFarePurchaseInfo(config,breakpoint,fares)} + hideBottomDivider={shouldShowFarePurchaseInfo(config, breakpoint, fares)} /> ) : ( <> @@ -286,21 +286,21 @@ class ItineraryTab extends React.Component { /> ), shouldShowFareInfo(config) && ( - shouldShowFarePurchaseInfo(config,breakpoint,fares) ? ( + shouldShowFarePurchaseInfo(config, breakpoint, fares) ? ( ) : - ( ) ), config.showCO2InItinerarySummary && co2value >= 0 && ( -
+
-
+
@@ -362,21 +362,24 @@ class ItineraryTab extends React.Component {
- - { carCo2Value >= 0 && co2percentage >= 0 ? ( ): - } + + {co2SimpleDesc ? + + : + + } {config.URL.EMISSIONSINFO && (
Date: Mon, 18 Sep 2023 13:45:11 +0300 Subject: [PATCH 17/37] fix: dt-5325 emissions value nonzero if doesnt exist --- app/component/ItineraryTab.js | 2 +- app/component/SummaryRow.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/component/ItineraryTab.js b/app/component/ItineraryTab.js index 8034a120c8..ef73bbee05 100644 --- a/app/component/ItineraryTab.js +++ b/app/component/ItineraryTab.js @@ -217,7 +217,7 @@ class ItineraryTab extends React.Component { const suggestionIndex = this.context.match.params.secondHash ? Number(this.context.match.params.secondHash) + 1 : Number(this.context.match.params.hash) + 1; - const co2value = itinerary.emissions ? Math.round(itinerary.emissions) : 0; + const co2value = itinerary.emissions ? Math.round(itinerary.emissions) : -1; const carCo2Value = this.props.carItinerary && this.props.carItinerary.length > 0 ? Math.round(this.props.carItinerary[0].emissions) : 0; const co2percentage = co2value > 0 && carCo2Value > 0 ? Math.round((co2value / carCo2Value) * 100) : 0; const co2SimpleDesc = co2percentage === 0 || carCo2Value === 0; diff --git a/app/component/SummaryRow.js b/app/component/SummaryRow.js index 0427c666c3..20ecd7706f 100644 --- a/app/component/SummaryRow.js +++ b/app/component/SummaryRow.js @@ -237,7 +237,7 @@ const SummaryRow = ( const startTime = moment(data.startTime); const endTime = moment(data.endTime); const duration = endTime.diff(startTime); - const co2value = Math.round(data.emissions); + const co2value = data.emissions ? Math.round(data.emissions) : -1; const mobile = bp => !(bp === 'large'); const legs = []; From 5e68ad15b34a6f9f717a375d8bb666347fd4dc16 Mon Sep 17 00:00:00 2001 From: sharhio Date: Tue, 19 Sep 2023 14:57:18 +0300 Subject: [PATCH 18/37] chore: dt-5325 emissions turned on in matka --- app/configurations/config.matka.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/configurations/config.matka.js b/app/configurations/config.matka.js index e9f081e82a..8af6218fe7 100644 --- a/app/configurations/config.matka.js +++ b/app/configurations/config.matka.js @@ -510,4 +510,6 @@ export default { virtualMonitorBaseUrl: 'https://matkamonitori.digitransit.fi/', }, }, + // Notice! Turning on this setting forces the search for car routes. + showCO2InItinerarySummary: true, }; From b73671b4a396be4418ee0f231e174d7b95f24d81 Mon Sep 17 00:00:00 2001 From: sharhio Date: Wed, 20 Sep 2023 09:24:14 +0300 Subject: [PATCH 19/37] fix: dt-5325 emission zero values --- app/component/ItineraryTab.js | 2 +- app/component/SummaryRow.js | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/component/ItineraryTab.js b/app/component/ItineraryTab.js index ef73bbee05..bccbe54c28 100644 --- a/app/component/ItineraryTab.js +++ b/app/component/ItineraryTab.js @@ -217,7 +217,7 @@ class ItineraryTab extends React.Component { const suggestionIndex = this.context.match.params.secondHash ? Number(this.context.match.params.secondHash) + 1 : Number(this.context.match.params.hash) + 1; - const co2value = itinerary.emissions ? Math.round(itinerary.emissions) : -1; + const co2value = typeof itinerary.emissions === 'number' && itinerary.emissions >= 0 ? Math.round(itinerary.emissions) : -1; const carCo2Value = this.props.carItinerary && this.props.carItinerary.length > 0 ? Math.round(this.props.carItinerary[0].emissions) : 0; const co2percentage = co2value > 0 && carCo2Value > 0 ? Math.round((co2value / carCo2Value) * 100) : 0; const co2SimpleDesc = co2percentage === 0 || carCo2Value === 0; diff --git a/app/component/SummaryRow.js b/app/component/SummaryRow.js index 20ecd7706f..56ab882dd4 100644 --- a/app/component/SummaryRow.js +++ b/app/component/SummaryRow.js @@ -237,8 +237,10 @@ const SummaryRow = ( const startTime = moment(data.startTime); const endTime = moment(data.endTime); const duration = endTime.diff(startTime); - const co2value = data.emissions ? Math.round(data.emissions) : -1; - + const co2value = + typeof data.emissions === 'number' && data.emissions >= 0 + ? Math.round(data.emissions) + : -1; const mobile = bp => !(bp === 'large'); const legs = []; let noTransitLegs = true; From b69bab37fa2d7e1c424c8ac9c39a5f1daed1e14f Mon Sep 17 00:00:00 2001 From: sharhio Date: Wed, 4 Oct 2023 15:47:01 +0300 Subject: [PATCH 20/37] fix: dt-5325 lowest co2 value filterting fix --- .../ItinerarySummaryListContainer.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js b/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js index 2edd0c7498..c3b8de5d18 100644 --- a/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js +++ b/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js @@ -57,7 +57,12 @@ function ItinerarySummaryListContainer( ) { const lowestCo2value = Math.round( itineraries - .filter(itinerary => itinerary.emissions >= 0) + .filter( + itinerary => + itinerary.emissions !== undefined && + itinerary.emissions !== null && + itinerary.emissions >= 0, + ) .reduce((a, b) => { return a.emissions < b.emissions ? a : b; }, 0).emissions, From 2a7dcca372b46b4bd978184b1cec2cfc27b2c430 Mon Sep 17 00:00:00 2001 From: sharhio Date: Wed, 18 Oct 2023 15:24:10 +0300 Subject: [PATCH 21/37] feat: dt-5325 co2 type changes --- .../ItinerarySummaryListContainer.js | 15 +- app/component/ItineraryTab.js | 10 +- app/component/SummaryPage.js | 20 +- app/component/SummaryPlanContainer.js | 8 +- app/component/SummaryRow.js | 5 +- app/prop-types/ItineraryShape.js | 4 +- app/util/queryUtils.js | 4 +- build/schema.graphql | 248 ++++++++++-- build/schema.json | 383 ++++++++++++++---- .../schema/schema.graphql | 248 ++++++++++-- 10 files changed, 765 insertions(+), 180 deletions(-) diff --git a/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js b/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js index c3b8de5d18..eb97d8869c 100644 --- a/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js +++ b/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js @@ -57,15 +57,10 @@ function ItinerarySummaryListContainer( ) { const lowestCo2value = Math.round( itineraries - .filter( - itinerary => - itinerary.emissions !== undefined && - itinerary.emissions !== null && - itinerary.emissions >= 0, - ) + .filter(itinerary => itinerary.emissions?.co2grams >= 0) .reduce((a, b) => { - return a.emissions < b.emissions ? a : b; - }, 0).emissions, + return a.emissions?.co2grams < b.emissions?.co2grams ? a : b; + }, 0).emissions?.co2grams, ); const summaries = itineraries.map((itinerary, i) => ( = 0 ? Math.round(itinerary.emissions) : -1; + const co2value = typeof itinerary.emissions?.co2grams === 'number' && itinerary.emissions?.co2grams >= 0 ? Math.round(itinerary.emissions?.co2grams) : -1; const carCo2Value = this.props.carItinerary && this.props.carItinerary.length > 0 ? Math.round(this.props.carItinerary[0].emissions) : 0; const co2percentage = co2value > 0 && carCo2Value > 0 ? Math.round((co2value / carCo2Value) * 100) : 0; const co2SimpleDesc = co2percentage === 0 || carCo2Value === 0; @@ -450,7 +452,9 @@ const withRelay = createFragmentContainer( } type } - emissions + emissions { + co2grams + } legs { mode nextLegs(numberOfLegs: 2 originModesWithParentStation: [RAIL] destinationModesWithParentStation: [RAIL]) { diff --git a/app/component/SummaryPage.js b/app/component/SummaryPage.js index 84df8c0454..3bf24ae0e5 100644 --- a/app/component/SummaryPage.js +++ b/app/component/SummaryPage.js @@ -737,7 +737,9 @@ class SummaryPage extends React.Component { endTime ...ItineraryTab_itinerary ...SummaryPlanContainer_itineraries - emissions + emissions { + co2grams + } legs { mode ...ItineraryLine_legs @@ -791,7 +793,9 @@ class SummaryPage extends React.Component { endTime ...ItineraryTab_itinerary ...SummaryPlanContainer_itineraries - emissions + emissions { + co2grams + } legs { mode ...ItineraryLine_legs @@ -851,7 +855,9 @@ class SummaryPage extends React.Component { endTime ...ItineraryTab_itinerary ...SummaryPlanContainer_itineraries - emissions + emissions { + co2grams + } legs { mode ...ItineraryLine_legs @@ -1032,7 +1038,9 @@ class SummaryPage extends React.Component { endTime ...ItineraryTab_itinerary ...SummaryPlanContainer_itineraries - emissions + emissions { + co2grams + } legs { mode ...ItineraryLine_legs @@ -2963,7 +2971,9 @@ const containerComponent = createRefetchContainer( endTime ...ItineraryTab_itinerary ...SummaryPlanContainer_itineraries - emissions + emissions { + co2grams + } legs { mode ...ItineraryLine_legs diff --git a/app/component/SummaryPlanContainer.js b/app/component/SummaryPlanContainer.js index 13f2d79c37..60e0e69fb7 100644 --- a/app/component/SummaryPlanContainer.js +++ b/app/component/SummaryPlanContainer.js @@ -344,7 +344,9 @@ const connectedContainer = createFragmentContainer( itineraries { startTime endTime - emissions + emissions { + co2grams + } legs { mode ...ItineraryLine_legs @@ -399,7 +401,9 @@ const connectedContainer = createFragmentContainer( ...ItinerarySummaryListContainer_itineraries endTime startTime - emissions + emissions { + co2grams + } legs { mode to { diff --git a/app/component/SummaryRow.js b/app/component/SummaryRow.js index 56ab882dd4..f0cc885c83 100644 --- a/app/component/SummaryRow.js +++ b/app/component/SummaryRow.js @@ -238,8 +238,9 @@ const SummaryRow = ( const endTime = moment(data.endTime); const duration = endTime.diff(startTime); const co2value = - typeof data.emissions === 'number' && data.emissions >= 0 - ? Math.round(data.emissions) + typeof data.emissions?.co2grams === 'number' && + data.emissions?.co2grams >= 0 + ? Math.round(data.emissions?.co2grams) : -1; const mobile = bp => !(bp === 'large'); const legs = []; diff --git a/app/prop-types/ItineraryShape.js b/app/prop-types/ItineraryShape.js index 3c72ff4349..b6ad8fb3e0 100644 --- a/app/prop-types/ItineraryShape.js +++ b/app/prop-types/ItineraryShape.js @@ -4,5 +4,7 @@ export default PropTypes.shape({ endTime: PropTypes.number, startTime: PropTypes.number, legs: PropTypes.arrayOf(PropTypes.object), - emissions: PropTypes.number, + emissions: PropTypes.shape({ + co2grams: PropTypes.number, + }), }); diff --git a/app/util/queryUtils.js b/app/util/queryUtils.js index 73faaeb9a5..e750a93a1b 100644 --- a/app/util/queryUtils.js +++ b/app/util/queryUtils.js @@ -264,7 +264,9 @@ export const moreItinerariesQuery = graphql` endTime ...ItineraryTab_itinerary ...SummaryPlanContainer_itineraries - emissions + emissions { + co2grams + } legs { mode ...ItineraryLine_legs diff --git a/build/schema.graphql b/build/schema.graphql index ae1519a5ad..97dc8a0955 100644 --- a/build/schema.graphql +++ b/build/schema.graphql @@ -846,9 +846,12 @@ type debugOutput { } """ -Departure row is a location, which lists departures of a certain pattern from a -stop. Departure rows are identified with the pattern, so querying departure rows -will return only departures from one stop per pattern +Departure row is a combination of a pattern and a stop of that pattern. + +They are de-duplicated so for each pattern there will only be a single departure row. + +This is useful if you want to show a list of stop/pattern combinations but want each pattern to be +listed only once. """ type DepartureRow implements Node & PlaceInterface { """ @@ -897,6 +900,13 @@ type elevationProfileComponent { elevation: Float } +type Emissions { + """ + CO₂ emissions of the trip on this itinerary, in grams per person. + """ + co2grams: Float +} + type fare { type: String @deprecated @@ -1236,29 +1246,59 @@ input InputUnpreferred { } enum RoutingErrorCode { - """No transit connection was found between the origin and destination withing the operating day or the next day""" + """ + No transit connection was found between the origin and destination within the operating day or + the next day, not even sub-optimal ones. + """ NO_TRANSIT_CONNECTION - """Transit connection was found, but it was outside the search window, see metadata for the next search window""" + """ + A transit connection was found, but it was outside the search window. See the metadata for a token + for retrieving the result outside the search window. + """ NO_TRANSIT_CONNECTION_IN_SEARCH_WINDOW - """The date specified is outside the range of data currently loaded into the system""" + """ + The date specified is outside the range of data currently loaded into the system as it is too + far into the future or the past. + + The specific date range of the system is configurable by an administrator and also depends on + the input data provided. + """ OUTSIDE_SERVICE_PERIOD - """The coordinates are outside the bounds of the data currently loaded into the system""" + """ + The coordinates are outside the geographic bounds of the transit and street data currently loaded + into the system and therefore cannot return any results. + """ OUTSIDE_BOUNDS - """The specified location is not close to any streets or transit stops""" + """ + The specified location is not close to any streets or transit stops currently loaded into the + system, even though it is generally within its bounds. + + This can happen when there is only transit but no street data coverage at the location in + question. + """ LOCATION_NOT_FOUND - """No stops are reachable from the location specified. You can try searching using a different access or egress mode""" + """ + No stops are reachable from the start or end locations specified. + + You can try searching using a different access or egress mode, for example cycling instead of walking, + increase the walking/cycling/driving speed or have an administrator change the system's configuration + so that stops further away are considered. + """ NO_STOPS_IN_RANGE - """The origin and destination are so close to each other, that walking is always better, but no direct mode was specified for the search""" - WALKING_BETTER_THAN_TRANSIT + """ + Transit connections were requested and found but because it is easier to just walk all the way + to the destination they were removed. - """An unknown error happened during the search. The details have been logged to the server logs""" - SYSTEM_ERROR + If you want to still show the transit results, you need to make walking less desirable by + increasing the walk reluctance. + """ + WALKING_BETTER_THAN_TRANSIT } enum InputField { @@ -1300,23 +1340,59 @@ type FareMedium { "A container for both a fare product (a ticket) and its relationship to the itinerary." type FareProductUse { """ - Identifier local to the itinerary that allows to cross-reference and deduplicate - fare products that span more than one leg. + Represents the use of a single instance of a fare product throughout the itinerary. It can + be used to cross-reference and de-duplicate fare products that are applicable for more than one + leg. If you want to uniquely identify the fare product itself (not its use) use the product's `id`. ### Example: Day pass - When a day pass is valid for all three legs in the itinerary it will appear - for each leg but with the same use-`id`. - - *It is the responsibility of the API consumers to display the day pass as a product for the - entire itinerary rather than three day passes!* + The day pass is valid for both legs in the itinerary. It is listed as the applicable `product` for each leg, + and the same FareProductUse id is shown, indicating that only one pass was used/bought. + + **Illustration** + ```yaml + itinerary: + leg1: + fareProducts: + id: "AAA" // id of a FareProductUse instance + product: + id: "day-pass" // product id + name: "Day Pass" + leg2: + fareProducts: + id: "AAA" // identical to leg1. the passenger needs to buy ONE pass, not two. + product: + id: "day-pass" // product id + name: "Day Pass" + ``` + + **It is the responsibility of the API consumers to display the day pass as a product for the + entire itinerary rather than two day passes!** ### Example: Several single tickets If you have two legs and need to buy two single tickets they will appear in each leg with the - same `product.id` but different use-`id`. + same `FareProduct.id` but different `FareProductUse.id`. + + **Illustration** + ```yaml + itinerary: + leg1: + fareProducts: + id: "AAA" // id of a FareProductUse instance, not product id + product: + id: "single-ticket" // product id + name: "Single Ticket" + leg2: + fareProducts: + id: "BBB" // different to leg1. the passenger needs to buy two single tickets. + product: + id: "single-ticket" // product id + name: "Single Ticket" + ``` + """ id: String! @@ -1325,7 +1401,31 @@ type FareProductUse { } "A fare product (a ticket) to be bought by a passenger" -type FareProduct { +interface FareProduct { + "Identifier for the fare product." + id: String! + + """ + Human readable name of the product, for example example "Day pass" or "Single ticket". + """ + name: String! + + "The category of riders this product applies to, for example students or pensioners." + riderCategory: RiderCategory + + """ + The 'medium' that this product applies to, for example "Oyster Card" or "Berlin Ticket App". + + This communicates to riders that a specific way of buying or keeping this product is required. + """ + medium: FareMedium +} + +""" +The standard case of a fare product: it only has a single price to be paid by the passenger +and no discounts are applied. +""" +type DefaultFareProduct implements FareProduct { "Identifier for the fare product." id: String! @@ -1374,8 +1474,8 @@ type Itinerary { """How far the user has to walk, in meters.""" walkDistance: Float - """CO2 emissions of the trip on this itinerary, in grams per person.""" - emissions: Float + """Emissions of the trip on this itinerary.""" + emissions: Emissions """ A list of Legs. Each Leg is either a walking (cycling, car) portion of the @@ -1385,12 +1485,6 @@ type Itinerary { """ legs: [Leg]! - """ - Information about the fares for this itinerary. This is primarily a GTFS Fares V1 interface - will be removed in the future. - """ - fares: [fare] @deprecated(reason: "Use the leg's `fareProducts`.") - """ How much elevation is gained, in total, over the course of the itinerary, in meters. """ @@ -1423,13 +1517,24 @@ type Itinerary { More information is available in the [feature documentation](https://docs.opentripplanner.org/en/dev-2.x/sandbox/IBIAccessibilityScore/). """ accessibilityScore: Float + + # + # deprecated fields + # + + """ + Information about the fares for this itinerary. This is primarily a GTFS Fares V1 interface + will be removed in the future. + """ + fares: [fare] @deprecated(reason: "Use the leg's `fareProducts`.") } "A currency" type Currency { "ISO-4217 currency code, for example `USD` or `EUR`." code: String! - """Fractional digits of this currency. A value of 2 would express that in this currency + """ + Fractional digits of this currency. A value of 2 would express that in this currency 100 minor units make up one major unit. Expressed more concretely: 100 Euro-cents make up one Euro. @@ -1446,7 +1551,10 @@ type Money { "The currency of this money amount." currency: Currency! """ - Money as a fractional amount, so 3.10 USD is represented as `3.1`. + Money in the major currency unit, so 3.10 USD is represented as `3.1`. + + If you want to get the minor currency unit (310 cents), multiply with + (10 to the power of `currency.digits`). """ amount: Float! } @@ -1573,8 +1681,16 @@ type Leg { Whether the destination of this leg (field `to`) is one of the intermediate places specified in the query. """ intermediatePlace: Boolean + + "The turn-by-turn navigation instructions." steps: [step] + """ + For transit legs, the headsign that the vehicle shows at the stop where the passenger boards. + For non-transit legs, null. + """ + headsign: String + """ This is used to indicate if boarding this leg is possible only with special arrangements. """ @@ -1719,7 +1835,7 @@ enum Mode { GONDOLA """Only used internally. No use for API users.""" - LEG_SWITCH + LEG_SWITCH @deprecated """RAIL""" RAIL @@ -2052,6 +2168,22 @@ type BookingInfo { dropOffMessage: String } +"The board/alight position in between two stops of the pattern of a trip with continuous pickup/drop off." +type PositionBetweenStops { + "Position of the previous stop in the pattern. Positions are not required to start from 0 or be consecutive." + previousPosition: Int + "Position of the next stop in the pattern. Positions are not required to start from 0 or be consecutive." + nextPosition: Int +} + +"Stop position at a specific stop." +type PositionAtStop { + "Position of the stop in the pattern. Positions are not required to start from 0 or be consecutive." + position: Int +} + +union StopPosition = PositionAtStop | PositionBetweenStops + type Place { """ For transit stops, the name of the stop. For points of interest, the name of the POI. @@ -2083,8 +2215,21 @@ type Place { """The stop related to the place.""" stop: Stop - """The bike rental station related to the place""" - bikeRentalStation: BikeRentalStation @deprecated(reason: "Use vehicleRentalStation and rentalVehicle instead") + """ + The position of the stop in the pattern. This is not required to start from 0 or be consecutive - any + increasing integer sequence along the stops is valid. + + The purpose of this field is to identify the stop within the pattern so it can be cross-referenced + between it and the itinerary. It is safe to cross-reference when done quickly, i.e. within seconds. + However, it should be noted that realtime updates can change the values, so don't store it for + longer amounts of time. + + Depending on the source data, this might not be the GTFS `stop_sequence` but another value, perhaps + even generated. + + The position can be either at a certain stop or in between two for trips where this is possible. + """ + stopPosition: StopPosition """The vehicle rental station related to the place""" vehicleRentalStation: VehicleRentalStation @@ -2092,14 +2237,17 @@ type Place { """The rental vehicle related to the place""" rentalVehicle: RentalVehicle + """The vehicle parking related to the place""" + vehicleParking: VehicleParking + + """The bike rental station related to the place""" + bikeRentalStation: BikeRentalStation @deprecated(reason: "Use vehicleRentalStation and rentalVehicle instead") + """The bike parking related to the place""" bikePark: BikePark @deprecated(reason: "bikePark is deprecated. Use vehicleParking instead.") """The car parking related to the place""" carPark: CarPark @deprecated(reason: "carPark is deprecated. Use vehicleParking instead.") - - """The vehicle parking related to the place""" - vehicleParking: VehicleParking } type placeAtDistance implements Node { @@ -2363,7 +2511,7 @@ type QueryType { maxResults: Int = 20 """ - Only return places that are one of these types, e.g. `STOP` or `BICYCLE_RENT` + Only return places that are one of these types, e.g. `STOP` or `VEHICLE_RENT` """ filterByPlaceTypes: [FilterPlaceType] @@ -2602,6 +2750,8 @@ type QueryType { """ vehicleRentalStation(id: String!): VehicleRentalStation + ## Deprecated fields + """Get all bike parks""" bikeParks: [BikePark] @deprecated(reason: "bikeParks is deprecated. Use vehicleParkings instead.") @@ -3237,6 +3387,9 @@ enum RelativeDirection { ELEVATOR UTURN_LEFT UTURN_RIGHT + ENTER_STATION + EXIT_STATION + FOLLOW_SIGNS } """The cardinal (compass) direction taken when engaging a walking/driving step.""" @@ -3590,6 +3743,20 @@ type Stoptime { """The stop where this arrival/departure happens""" stop: Stop + """ + The sequence of the stop in the pattern. This is not required to start from 0 or be consecutive - any + increasing integer sequence along the stops is valid. + + The purpose of this field is to identify the stop within the pattern so it can be cross-referenced + between it and the itinerary. It is safe to cross-reference when done quickly, i.e. within seconds. + However, it should be noted that realtime updates can change the values, so don't store it for + longer amounts of time. + + Depending on the source data, this might not be the GTFS `stop_sequence` but another value, perhaps + even generated. + """ + stopPosition: Int + """ Scheduled arrival time. Format: seconds since midnight of the departure date """ @@ -3732,9 +3899,10 @@ type Trip implements Node { """List of dates when this trip is in service. Format: YYYYMMDD""" activeDates: [String] tripShortName: String + """Headsign of the vehicle when running on this trip""" tripHeadsign( - """If translated headsign is found from gtfs translation.txt and wanted language is not same as + """If a translated headsign is found from GTFS translation.txt and wanted language is not same as feed's language then returns wanted translation. Otherwise uses name from trip_headsign.txt. """ language: String): String diff --git a/build/schema.json b/build/schema.json index 70c70f3666..a294e52370 100644 --- a/build/schema.json +++ b/build/schema.json @@ -1921,10 +1921,99 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "DefaultFareProduct", + "description": "The standard case of a fare product: it only has a single price to be paid by the passenger\nand no discounts are applied.", + "fields": [ + { + "name": "id", + "description": "Identifier for the fare product.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": "Human readable name of the product, for example example \"Day pass\" or \"Single ticket\".", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "price", + "description": "The price of the product", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Money", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "riderCategory", + "description": "The category of riders this product applies to, for example students or pensioners.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "RiderCategory", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "medium", + "description": "The 'medium' that this product applies to, for example \"Oyster Card\" or \"Berlin Ticket App\".\n\nThis communicates to riders that a specific way of buying or keeping this product is required.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "FareMedium", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + { + "kind": "INTERFACE", + "name": "FareProduct", + "ofType": null + } + ], + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "DepartureRow", - "description": "Departure row is a location, which lists departures of a certain pattern from a\nstop. Departure rows are identified with the pattern, so querying departure rows\nwill return only departures from one stop per pattern", + "description": "Departure row is a combination of a pattern and a stop of that pattern.\n\nThey are de-duplicated so for each pattern there will only be a single departure row.\n\nThis is useful if you want to show a list of stop/pattern combinations but want each pattern to be\nlisted only once.", "fields": [ { "name": "id", @@ -2084,6 +2173,29 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "Emissions", + "description": null, + "fields": [ + { + "name": "co2grams", + "description": "CO₂ emissions of the trip on this itinerary, in grams per person.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "FareMedium", @@ -2124,7 +2236,7 @@ "possibleTypes": null }, { - "kind": "OBJECT", + "kind": "INTERFACE", "name": "FareProduct", "description": "A fare product (a ticket) to be bought by a passenger", "fields": [ @@ -2160,22 +2272,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "price", - "description": "The price of the product", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "Money", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "riderCategory", "description": "The category of riders this product applies to, for example students or pensioners.", @@ -2204,7 +2300,13 @@ "inputFields": null, "interfaces": [], "enumValues": null, - "possibleTypes": null + "possibleTypes": [ + { + "kind": "OBJECT", + "name": "DefaultFareProduct", + "ofType": null + } + ] }, { "kind": "OBJECT", @@ -2213,7 +2315,7 @@ "fields": [ { "name": "id", - "description": "Identifier local to the itinerary that allows to cross-reference and deduplicate\nfare products that span more than one leg.\n\nIf you want to uniquely identify the fare product itself (not its use) use the product's `id`.\n\n### Example: Day pass\n\nWhen a day pass is valid for all three legs in the itinerary it will appear\nfor each leg but with the same use-`id`.\n\n*It is the responsibility of the API consumers to display the day pass as a product for the\nentire itinerary rather than three day passes!*\n\n### Example: Several single tickets\n\nIf you have two legs and need to buy two single tickets they will appear in each leg with the\nsame `product.id` but different use-`id`.", + "description": "Represents the use of a single instance of a fare product throughout the itinerary. It can\nbe used to cross-reference and de-duplicate fare products that are applicable for more than one\nleg.\n\nIf you want to uniquely identify the fare product itself (not its use) use the product's `id`.\n\n### Example: Day pass\n\nThe day pass is valid for both legs in the itinerary. It is listed as the applicable `product` for each leg,\nand the same FareProductUse id is shown, indicating that only one pass was used/bought.\n\n**Illustration**\n```yaml\nitinerary:\n leg1:\n fareProducts:\n id: \"AAA\" // id of a FareProductUse instance\n product:\n id: \"day-pass\" // product id\n name: \"Day Pass\"\n leg2:\n fareProducts:\n id: \"AAA\" // identical to leg1. the passenger needs to buy ONE pass, not two.\n product:\n id: \"day-pass\" // product id\n name: \"Day Pass\"\n```\n\n**It is the responsibility of the API consumers to display the day pass as a product for the\nentire itinerary rather than two day passes!**\n\n### Example: Several single tickets\n\nIf you have two legs and need to buy two single tickets they will appear in each leg with the\nsame `FareProduct.id` but different `FareProductUse.id`.\n\n**Illustration**\n```yaml\nitinerary:\n leg1:\n fareProducts:\n id: \"AAA\" // id of a FareProductUse instance, not product id\n product:\n id: \"single-ticket\" // product id\n name: \"Single Ticket\"\n leg2:\n fareProducts:\n id: \"BBB\" // different to leg1. the passenger needs to buy two single tickets.\n product:\n id: \"single-ticket\" // product id\n name: \"Single Ticket\"\n```", "args": [], "type": { "kind": "NON_NULL", @@ -2232,7 +2334,7 @@ "description": "The purchasable fare product", "args": [], "type": { - "kind": "OBJECT", + "kind": "INTERFACE", "name": "FareProduct", "ofType": null }, @@ -3072,11 +3174,11 @@ }, { "name": "emissions", - "description": "CO2 emissions of the trip on this itinerary, in grams per person.", + "description": "Emissions of the trip on this itinerary.", "args": [], "type": { - "kind": "SCALAR", - "name": "Float", + "kind": "OBJECT", + "name": "Emissions", "ofType": null }, "isDeprecated": false, @@ -3102,22 +3204,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "fares", - "description": "Information about the fares for this itinerary. This is primarily a GTFS Fares V1 interface\nwill be removed in the future.", - "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "fare", - "ofType": null - } - }, - "isDeprecated": true, - "deprecationReason": "Use the leg's `fareProducts`." - }, { "name": "elevationGained", "description": "How much elevation is gained, in total, over the course of the itinerary, in meters.", @@ -3185,6 +3271,22 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "fares", + "description": "Information about the fares for this itinerary. This is primarily a GTFS Fares V1 interface\nwill be removed in the future.", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "fare", + "ofType": null + } + }, + "isDeprecated": true, + "deprecationReason": "Use the leg's `fareProducts`." } ], "inputFields": null, @@ -3491,7 +3593,7 @@ }, { "name": "steps", - "description": null, + "description": "The turn-by-turn navigation instructions.", "args": [], "type": { "kind": "LIST", @@ -3505,6 +3607,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "headsign", + "description": "For transit legs, the headsign that the vehicle shows at the stop where the passenger boards.\nFor non-transit legs, null.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "pickupType", "description": "This is used to indicate if boarding this leg is possible only with special arrangements.", @@ -3900,8 +4014,8 @@ { "name": "LEG_SWITCH", "description": "Only used internally. No use for API users.", - "isDeprecated": false, - "deprecationReason": null + "isDeprecated": true, + "deprecationReason": "No longer supported" }, { "name": "RAIL", @@ -3989,7 +4103,7 @@ }, { "name": "amount", - "description": "Money as a fractional amount, so 3.10 USD is represented as `3.1`.", + "description": "Money in the major currency unit, so 3.10 USD is represented as `3.1`.\n\nIf you want to get the minor currency unit (310 cents), multiply with\n(10 to the power of `currency.digits`).", "args": [], "type": { "kind": "NON_NULL", @@ -4833,16 +4947,16 @@ "deprecationReason": null }, { - "name": "bikeRentalStation", - "description": "The bike rental station related to the place", + "name": "stopPosition", + "description": "The position of the stop in the pattern. This is not required to start from 0 or be consecutive - any\nincreasing integer sequence along the stops is valid.\n\nThe purpose of this field is to identify the stop within the pattern so it can be cross-referenced\nbetween it and the itinerary. It is safe to cross-reference when done quickly, i.e. within seconds.\nHowever, it should be noted that realtime updates can change the values, so don't store it for\nlonger amounts of time.\n\nDepending on the source data, this might not be the GTFS `stop_sequence` but another value, perhaps\neven generated.\n\nThe position can be either at a certain stop or in between two for trips where this is possible.", "args": [], "type": { - "kind": "OBJECT", - "name": "BikeRentalStation", + "kind": "UNION", + "name": "StopPosition", "ofType": null }, - "isDeprecated": true, - "deprecationReason": "Use vehicleRentalStation and rentalVehicle instead" + "isDeprecated": false, + "deprecationReason": null }, { "name": "vehicleRentalStation", @@ -4868,6 +4982,30 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "vehicleParking", + "description": "The vehicle parking related to the place", + "args": [], + "type": { + "kind": "OBJECT", + "name": "VehicleParking", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "bikeRentalStation", + "description": "The bike rental station related to the place", + "args": [], + "type": { + "kind": "OBJECT", + "name": "BikeRentalStation", + "ofType": null + }, + "isDeprecated": true, + "deprecationReason": "Use vehicleRentalStation and rentalVehicle instead" + }, { "name": "bikePark", "description": "The bike parking related to the place", @@ -4891,18 +5029,6 @@ }, "isDeprecated": true, "deprecationReason": "carPark is deprecated. Use vehicleParking instead." - }, - { - "name": "vehicleParking", - "description": "The vehicle parking related to the place", - "args": [], - "type": { - "kind": "OBJECT", - "name": "VehicleParking", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null } ], "inputFields": null, @@ -5227,6 +5353,64 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "PositionAtStop", + "description": "Stop position at a specific stop.", + "fields": [ + { + "name": "position", + "description": "Position of the stop in the pattern. Positions are not required to start from 0 or be consecutive.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "PositionBetweenStops", + "description": "The board/alight position in between two stops of the pattern of a trip with continuous pickup/drop off.", + "fields": [ + { + "name": "previousPosition", + "description": "Position of the previous stop in the pattern. Positions are not required to start from 0 or be consecutive.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "nextPosition", + "description": "Position of the next stop in the pattern. Positions are not required to start from 0 or be consecutive.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "ENUM", "name": "PropulsionType", @@ -5763,7 +5947,7 @@ }, { "name": "filterByPlaceTypes", - "description": "Only return places that are one of these types, e.g. `STOP` or `BICYCLE_RENT`", + "description": "Only return places that are one of these types, e.g. `STOP` or `VEHICLE_RENT`", "type": { "kind": "LIST", "name": null, @@ -7535,6 +7719,24 @@ "description": null, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "ENTER_STATION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "EXIT_STATION", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FOLLOW_SIGNS", + "description": null, + "isDeprecated": false, + "deprecationReason": null } ], "possibleTypes": null @@ -8309,49 +8511,43 @@ "enumValues": [ { "name": "NO_TRANSIT_CONNECTION", - "description": "No transit connection was found between the origin and destination withing the operating day or the next day", + "description": "No transit connection was found between the origin and destination within the operating day or\nthe next day, not even sub-optimal ones.", "isDeprecated": false, "deprecationReason": null }, { "name": "NO_TRANSIT_CONNECTION_IN_SEARCH_WINDOW", - "description": "Transit connection was found, but it was outside the search window, see metadata for the next search window", + "description": "A transit connection was found, but it was outside the search window. See the metadata for a token\nfor retrieving the result outside the search window.", "isDeprecated": false, "deprecationReason": null }, { "name": "OUTSIDE_SERVICE_PERIOD", - "description": "The date specified is outside the range of data currently loaded into the system", + "description": "The date specified is outside the range of data currently loaded into the system as it is too\nfar into the future or the past.\n\nThe specific date range of the system is configurable by an administrator and also depends on\nthe input data provided.", "isDeprecated": false, "deprecationReason": null }, { "name": "OUTSIDE_BOUNDS", - "description": "The coordinates are outside the bounds of the data currently loaded into the system", + "description": "The coordinates are outside the geographic bounds of the transit and street data currently loaded\ninto the system and therefore cannot return any results.", "isDeprecated": false, "deprecationReason": null }, { "name": "LOCATION_NOT_FOUND", - "description": "The specified location is not close to any streets or transit stops", + "description": "The specified location is not close to any streets or transit stops currently loaded into the\nsystem, even though it is generally within its bounds.\n\nThis can happen when there is only transit but no street data coverage at the location in\nquestion.", "isDeprecated": false, "deprecationReason": null }, { "name": "NO_STOPS_IN_RANGE", - "description": "No stops are reachable from the location specified. You can try searching using a different access or egress mode", + "description": "No stops are reachable from the start or end locations specified.\n\nYou can try searching using a different access or egress mode, for example cycling instead of walking,\nincrease the walking/cycling/driving speed or have an administrator change the system's configuration\nso that stops further away are considered.", "isDeprecated": false, "deprecationReason": null }, { "name": "WALKING_BETTER_THAN_TRANSIT", - "description": "The origin and destination are so close to each other, that walking is always better, but no direct mode was specified for the search", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "SYSTEM_ERROR", - "description": "An unknown error happened during the search. The details have been logged to the server logs", + "description": "Transit connections were requested and found but because it is easier to just walk all the way\nto the destination they were removed.\n\nIf you want to still show the transit results, you need to make walking less desirable by\nincreasing the walk reluctance.", "isDeprecated": false, "deprecationReason": null } @@ -9203,6 +9399,27 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "UNION", + "name": "StopPosition", + "description": null, + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": [ + { + "kind": "OBJECT", + "name": "PositionAtStop", + "ofType": null + }, + { + "kind": "OBJECT", + "name": "PositionBetweenStops", + "ofType": null + } + ] + }, { "kind": "OBJECT", "name": "StopRelationship", @@ -9263,6 +9480,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "stopPosition", + "description": "The sequence of the stop in the pattern. This is not required to start from 0 or be consecutive - any\nincreasing integer sequence along the stops is valid.\n\nThe purpose of this field is to identify the stop within the pattern so it can be cross-referenced\nbetween it and the itinerary. It is safe to cross-reference when done quickly, i.e. within seconds.\nHowever, it should be noted that realtime updates can change the values, so don't store it for\nlonger amounts of time.\n\nDepending on the source data, this might not be the GTFS `stop_sequence` but another value, perhaps\neven generated.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "scheduledArrival", "description": "Scheduled arrival time. Format: seconds since midnight of the departure date", @@ -9889,7 +10118,7 @@ "args": [ { "name": "language", - "description": "If translated headsign is found from gtfs translation.txt and wanted language is not same as\nfeed's language then returns wanted translation. Otherwise uses name from trip_headsign.txt.", + "description": "If a translated headsign is found from GTFS translation.txt and wanted language is not same as\nfeed's language then returns wanted translation. Otherwise uses name from trip_headsign.txt.", "type": { "kind": "SCALAR", "name": "String", diff --git a/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql b/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql index ae1519a5ad..97dc8a0955 100644 --- a/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql +++ b/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql @@ -846,9 +846,12 @@ type debugOutput { } """ -Departure row is a location, which lists departures of a certain pattern from a -stop. Departure rows are identified with the pattern, so querying departure rows -will return only departures from one stop per pattern +Departure row is a combination of a pattern and a stop of that pattern. + +They are de-duplicated so for each pattern there will only be a single departure row. + +This is useful if you want to show a list of stop/pattern combinations but want each pattern to be +listed only once. """ type DepartureRow implements Node & PlaceInterface { """ @@ -897,6 +900,13 @@ type elevationProfileComponent { elevation: Float } +type Emissions { + """ + CO₂ emissions of the trip on this itinerary, in grams per person. + """ + co2grams: Float +} + type fare { type: String @deprecated @@ -1236,29 +1246,59 @@ input InputUnpreferred { } enum RoutingErrorCode { - """No transit connection was found between the origin and destination withing the operating day or the next day""" + """ + No transit connection was found between the origin and destination within the operating day or + the next day, not even sub-optimal ones. + """ NO_TRANSIT_CONNECTION - """Transit connection was found, but it was outside the search window, see metadata for the next search window""" + """ + A transit connection was found, but it was outside the search window. See the metadata for a token + for retrieving the result outside the search window. + """ NO_TRANSIT_CONNECTION_IN_SEARCH_WINDOW - """The date specified is outside the range of data currently loaded into the system""" + """ + The date specified is outside the range of data currently loaded into the system as it is too + far into the future or the past. + + The specific date range of the system is configurable by an administrator and also depends on + the input data provided. + """ OUTSIDE_SERVICE_PERIOD - """The coordinates are outside the bounds of the data currently loaded into the system""" + """ + The coordinates are outside the geographic bounds of the transit and street data currently loaded + into the system and therefore cannot return any results. + """ OUTSIDE_BOUNDS - """The specified location is not close to any streets or transit stops""" + """ + The specified location is not close to any streets or transit stops currently loaded into the + system, even though it is generally within its bounds. + + This can happen when there is only transit but no street data coverage at the location in + question. + """ LOCATION_NOT_FOUND - """No stops are reachable from the location specified. You can try searching using a different access or egress mode""" + """ + No stops are reachable from the start or end locations specified. + + You can try searching using a different access or egress mode, for example cycling instead of walking, + increase the walking/cycling/driving speed or have an administrator change the system's configuration + so that stops further away are considered. + """ NO_STOPS_IN_RANGE - """The origin and destination are so close to each other, that walking is always better, but no direct mode was specified for the search""" - WALKING_BETTER_THAN_TRANSIT + """ + Transit connections were requested and found but because it is easier to just walk all the way + to the destination they were removed. - """An unknown error happened during the search. The details have been logged to the server logs""" - SYSTEM_ERROR + If you want to still show the transit results, you need to make walking less desirable by + increasing the walk reluctance. + """ + WALKING_BETTER_THAN_TRANSIT } enum InputField { @@ -1300,23 +1340,59 @@ type FareMedium { "A container for both a fare product (a ticket) and its relationship to the itinerary." type FareProductUse { """ - Identifier local to the itinerary that allows to cross-reference and deduplicate - fare products that span more than one leg. + Represents the use of a single instance of a fare product throughout the itinerary. It can + be used to cross-reference and de-duplicate fare products that are applicable for more than one + leg. If you want to uniquely identify the fare product itself (not its use) use the product's `id`. ### Example: Day pass - When a day pass is valid for all three legs in the itinerary it will appear - for each leg but with the same use-`id`. - - *It is the responsibility of the API consumers to display the day pass as a product for the - entire itinerary rather than three day passes!* + The day pass is valid for both legs in the itinerary. It is listed as the applicable `product` for each leg, + and the same FareProductUse id is shown, indicating that only one pass was used/bought. + + **Illustration** + ```yaml + itinerary: + leg1: + fareProducts: + id: "AAA" // id of a FareProductUse instance + product: + id: "day-pass" // product id + name: "Day Pass" + leg2: + fareProducts: + id: "AAA" // identical to leg1. the passenger needs to buy ONE pass, not two. + product: + id: "day-pass" // product id + name: "Day Pass" + ``` + + **It is the responsibility of the API consumers to display the day pass as a product for the + entire itinerary rather than two day passes!** ### Example: Several single tickets If you have two legs and need to buy two single tickets they will appear in each leg with the - same `product.id` but different use-`id`. + same `FareProduct.id` but different `FareProductUse.id`. + + **Illustration** + ```yaml + itinerary: + leg1: + fareProducts: + id: "AAA" // id of a FareProductUse instance, not product id + product: + id: "single-ticket" // product id + name: "Single Ticket" + leg2: + fareProducts: + id: "BBB" // different to leg1. the passenger needs to buy two single tickets. + product: + id: "single-ticket" // product id + name: "Single Ticket" + ``` + """ id: String! @@ -1325,7 +1401,31 @@ type FareProductUse { } "A fare product (a ticket) to be bought by a passenger" -type FareProduct { +interface FareProduct { + "Identifier for the fare product." + id: String! + + """ + Human readable name of the product, for example example "Day pass" or "Single ticket". + """ + name: String! + + "The category of riders this product applies to, for example students or pensioners." + riderCategory: RiderCategory + + """ + The 'medium' that this product applies to, for example "Oyster Card" or "Berlin Ticket App". + + This communicates to riders that a specific way of buying or keeping this product is required. + """ + medium: FareMedium +} + +""" +The standard case of a fare product: it only has a single price to be paid by the passenger +and no discounts are applied. +""" +type DefaultFareProduct implements FareProduct { "Identifier for the fare product." id: String! @@ -1374,8 +1474,8 @@ type Itinerary { """How far the user has to walk, in meters.""" walkDistance: Float - """CO2 emissions of the trip on this itinerary, in grams per person.""" - emissions: Float + """Emissions of the trip on this itinerary.""" + emissions: Emissions """ A list of Legs. Each Leg is either a walking (cycling, car) portion of the @@ -1385,12 +1485,6 @@ type Itinerary { """ legs: [Leg]! - """ - Information about the fares for this itinerary. This is primarily a GTFS Fares V1 interface - will be removed in the future. - """ - fares: [fare] @deprecated(reason: "Use the leg's `fareProducts`.") - """ How much elevation is gained, in total, over the course of the itinerary, in meters. """ @@ -1423,13 +1517,24 @@ type Itinerary { More information is available in the [feature documentation](https://docs.opentripplanner.org/en/dev-2.x/sandbox/IBIAccessibilityScore/). """ accessibilityScore: Float + + # + # deprecated fields + # + + """ + Information about the fares for this itinerary. This is primarily a GTFS Fares V1 interface + will be removed in the future. + """ + fares: [fare] @deprecated(reason: "Use the leg's `fareProducts`.") } "A currency" type Currency { "ISO-4217 currency code, for example `USD` or `EUR`." code: String! - """Fractional digits of this currency. A value of 2 would express that in this currency + """ + Fractional digits of this currency. A value of 2 would express that in this currency 100 minor units make up one major unit. Expressed more concretely: 100 Euro-cents make up one Euro. @@ -1446,7 +1551,10 @@ type Money { "The currency of this money amount." currency: Currency! """ - Money as a fractional amount, so 3.10 USD is represented as `3.1`. + Money in the major currency unit, so 3.10 USD is represented as `3.1`. + + If you want to get the minor currency unit (310 cents), multiply with + (10 to the power of `currency.digits`). """ amount: Float! } @@ -1573,8 +1681,16 @@ type Leg { Whether the destination of this leg (field `to`) is one of the intermediate places specified in the query. """ intermediatePlace: Boolean + + "The turn-by-turn navigation instructions." steps: [step] + """ + For transit legs, the headsign that the vehicle shows at the stop where the passenger boards. + For non-transit legs, null. + """ + headsign: String + """ This is used to indicate if boarding this leg is possible only with special arrangements. """ @@ -1719,7 +1835,7 @@ enum Mode { GONDOLA """Only used internally. No use for API users.""" - LEG_SWITCH + LEG_SWITCH @deprecated """RAIL""" RAIL @@ -2052,6 +2168,22 @@ type BookingInfo { dropOffMessage: String } +"The board/alight position in between two stops of the pattern of a trip with continuous pickup/drop off." +type PositionBetweenStops { + "Position of the previous stop in the pattern. Positions are not required to start from 0 or be consecutive." + previousPosition: Int + "Position of the next stop in the pattern. Positions are not required to start from 0 or be consecutive." + nextPosition: Int +} + +"Stop position at a specific stop." +type PositionAtStop { + "Position of the stop in the pattern. Positions are not required to start from 0 or be consecutive." + position: Int +} + +union StopPosition = PositionAtStop | PositionBetweenStops + type Place { """ For transit stops, the name of the stop. For points of interest, the name of the POI. @@ -2083,8 +2215,21 @@ type Place { """The stop related to the place.""" stop: Stop - """The bike rental station related to the place""" - bikeRentalStation: BikeRentalStation @deprecated(reason: "Use vehicleRentalStation and rentalVehicle instead") + """ + The position of the stop in the pattern. This is not required to start from 0 or be consecutive - any + increasing integer sequence along the stops is valid. + + The purpose of this field is to identify the stop within the pattern so it can be cross-referenced + between it and the itinerary. It is safe to cross-reference when done quickly, i.e. within seconds. + However, it should be noted that realtime updates can change the values, so don't store it for + longer amounts of time. + + Depending on the source data, this might not be the GTFS `stop_sequence` but another value, perhaps + even generated. + + The position can be either at a certain stop or in between two for trips where this is possible. + """ + stopPosition: StopPosition """The vehicle rental station related to the place""" vehicleRentalStation: VehicleRentalStation @@ -2092,14 +2237,17 @@ type Place { """The rental vehicle related to the place""" rentalVehicle: RentalVehicle + """The vehicle parking related to the place""" + vehicleParking: VehicleParking + + """The bike rental station related to the place""" + bikeRentalStation: BikeRentalStation @deprecated(reason: "Use vehicleRentalStation and rentalVehicle instead") + """The bike parking related to the place""" bikePark: BikePark @deprecated(reason: "bikePark is deprecated. Use vehicleParking instead.") """The car parking related to the place""" carPark: CarPark @deprecated(reason: "carPark is deprecated. Use vehicleParking instead.") - - """The vehicle parking related to the place""" - vehicleParking: VehicleParking } type placeAtDistance implements Node { @@ -2363,7 +2511,7 @@ type QueryType { maxResults: Int = 20 """ - Only return places that are one of these types, e.g. `STOP` or `BICYCLE_RENT` + Only return places that are one of these types, e.g. `STOP` or `VEHICLE_RENT` """ filterByPlaceTypes: [FilterPlaceType] @@ -2602,6 +2750,8 @@ type QueryType { """ vehicleRentalStation(id: String!): VehicleRentalStation + ## Deprecated fields + """Get all bike parks""" bikeParks: [BikePark] @deprecated(reason: "bikeParks is deprecated. Use vehicleParkings instead.") @@ -3237,6 +3387,9 @@ enum RelativeDirection { ELEVATOR UTURN_LEFT UTURN_RIGHT + ENTER_STATION + EXIT_STATION + FOLLOW_SIGNS } """The cardinal (compass) direction taken when engaging a walking/driving step.""" @@ -3590,6 +3743,20 @@ type Stoptime { """The stop where this arrival/departure happens""" stop: Stop + """ + The sequence of the stop in the pattern. This is not required to start from 0 or be consecutive - any + increasing integer sequence along the stops is valid. + + The purpose of this field is to identify the stop within the pattern so it can be cross-referenced + between it and the itinerary. It is safe to cross-reference when done quickly, i.e. within seconds. + However, it should be noted that realtime updates can change the values, so don't store it for + longer amounts of time. + + Depending on the source data, this might not be the GTFS `stop_sequence` but another value, perhaps + even generated. + """ + stopPosition: Int + """ Scheduled arrival time. Format: seconds since midnight of the departure date """ @@ -3732,9 +3899,10 @@ type Trip implements Node { """List of dates when this trip is in service. Format: YYYYMMDD""" activeDates: [String] tripShortName: String + """Headsign of the vehicle when running on this trip""" tripHeadsign( - """If translated headsign is found from gtfs translation.txt and wanted language is not same as + """If a translated headsign is found from GTFS translation.txt and wanted language is not same as feed's language then returns wanted translation. Otherwise uses name from trip_headsign.txt. """ language: String): String From 878dda85da214b493054a6f153445fc74e571bc1 Mon Sep 17 00:00:00 2001 From: sharhio Date: Thu, 19 Oct 2023 14:34:46 +0300 Subject: [PATCH 22/37] feat: dt-5325 emissions naming changes, car value fix --- .../ItinerarySummaryListContainer.js | 12 ++++++----- app/component/ItineraryTab.js | 13 ++++++------ app/component/SummaryPage.js | 20 +++++++++---------- app/component/SummaryPlanContainer.js | 8 ++++---- app/component/SummaryRow.js | 6 +++--- app/prop-types/ItineraryShape.js | 4 ++-- app/util/queryUtils.js | 4 ++-- build/schema.graphql | 6 +++--- build/schema.json | 6 +++--- .../schema/schema.graphql | 6 +++--- 10 files changed, 43 insertions(+), 42 deletions(-) diff --git a/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js b/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js index eb97d8869c..67d66099f9 100644 --- a/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js +++ b/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js @@ -57,10 +57,12 @@ function ItinerarySummaryListContainer( ) { const lowestCo2value = Math.round( itineraries - .filter(itinerary => itinerary.emissions?.co2grams >= 0) + .filter(itinerary => itinerary.emissionsPerPerson?.co2Grams >= 0) .reduce((a, b) => { - return a.emissions?.co2grams < b.emissions?.co2grams ? a : b; - }, 0).emissions?.co2grams, + return a.emissionsPerPerson?.co2Grams < b.emissionsPerPerson?.co2Grams + ? a + : b; + }, 0).emissionsPerPerson?.co2Grams, ); const summaries = itineraries.map((itinerary, i) => ( = 0 ? Math.round(itinerary.emissions?.co2grams) : -1; - const carCo2Value = this.props.carItinerary && this.props.carItinerary.length > 0 ? Math.round(this.props.carItinerary[0].emissions) : 0; + const co2value = typeof itinerary.emissionsPerPerson?.co2Grams === 'number' && itinerary.emissionsPerPerson?.co2Grams >= 0 ? Math.round(itinerary.emissionsPerPerson?.co2Grams) : -1; + const carCo2Value = this.props.carItinerary && this.props.carItinerary.length > 0 ? Math.round(this.props.carItinerary[0].emissionsPerPerson?.co2Grams) : 0; const co2percentage = co2value > 0 && carCo2Value > 0 ? Math.round((co2value / carCo2Value) * 100) : 0; const co2SimpleDesc = co2percentage === 0 || carCo2Value === 0; - return (

@@ -452,8 +451,8 @@ const withRelay = createFragmentContainer( } type } - emissions { - co2grams + emissionsPerPerson { + co2Grams } legs { mode diff --git a/app/component/SummaryPage.js b/app/component/SummaryPage.js index 3bf24ae0e5..e35c36ae97 100644 --- a/app/component/SummaryPage.js +++ b/app/component/SummaryPage.js @@ -737,8 +737,8 @@ class SummaryPage extends React.Component { endTime ...ItineraryTab_itinerary ...SummaryPlanContainer_itineraries - emissions { - co2grams + emissionsPerPerson { + co2Grams } legs { mode @@ -793,8 +793,8 @@ class SummaryPage extends React.Component { endTime ...ItineraryTab_itinerary ...SummaryPlanContainer_itineraries - emissions { - co2grams + emissionsPerPerson { + co2Grams } legs { mode @@ -855,8 +855,8 @@ class SummaryPage extends React.Component { endTime ...ItineraryTab_itinerary ...SummaryPlanContainer_itineraries - emissions { - co2grams + emissionsPerPerson { + co2Grams } legs { mode @@ -1038,8 +1038,8 @@ class SummaryPage extends React.Component { endTime ...ItineraryTab_itinerary ...SummaryPlanContainer_itineraries - emissions { - co2grams + emissionsPerPerson { + co2Grams } legs { mode @@ -2971,8 +2971,8 @@ const containerComponent = createRefetchContainer( endTime ...ItineraryTab_itinerary ...SummaryPlanContainer_itineraries - emissions { - co2grams + emissionsPerPerson { + co2Grams } legs { mode diff --git a/app/component/SummaryPlanContainer.js b/app/component/SummaryPlanContainer.js index 60e0e69fb7..33d358b900 100644 --- a/app/component/SummaryPlanContainer.js +++ b/app/component/SummaryPlanContainer.js @@ -344,8 +344,8 @@ const connectedContainer = createFragmentContainer( itineraries { startTime endTime - emissions { - co2grams + emissionsPerPerson { + co2Grams } legs { mode @@ -401,8 +401,8 @@ const connectedContainer = createFragmentContainer( ...ItinerarySummaryListContainer_itineraries endTime startTime - emissions { - co2grams + emissionsPerPerson { + co2Grams } legs { mode diff --git a/app/component/SummaryRow.js b/app/component/SummaryRow.js index f0cc885c83..6f2b986a0b 100644 --- a/app/component/SummaryRow.js +++ b/app/component/SummaryRow.js @@ -238,9 +238,9 @@ const SummaryRow = ( const endTime = moment(data.endTime); const duration = endTime.diff(startTime); const co2value = - typeof data.emissions?.co2grams === 'number' && - data.emissions?.co2grams >= 0 - ? Math.round(data.emissions?.co2grams) + typeof data.emissionsPerPerson?.co2Grams === 'number' && + data.emissionsPerPerson?.co2Grams >= 0 + ? Math.round(data.emissionsPerPerson?.co2Grams) : -1; const mobile = bp => !(bp === 'large'); const legs = []; diff --git a/app/prop-types/ItineraryShape.js b/app/prop-types/ItineraryShape.js index b6ad8fb3e0..7e33256503 100644 --- a/app/prop-types/ItineraryShape.js +++ b/app/prop-types/ItineraryShape.js @@ -4,7 +4,7 @@ export default PropTypes.shape({ endTime: PropTypes.number, startTime: PropTypes.number, legs: PropTypes.arrayOf(PropTypes.object), - emissions: PropTypes.shape({ - co2grams: PropTypes.number, + emissionsPerPerson: PropTypes.shape({ + co2Grams: PropTypes.number, }), }); diff --git a/app/util/queryUtils.js b/app/util/queryUtils.js index e750a93a1b..3a59f860a3 100644 --- a/app/util/queryUtils.js +++ b/app/util/queryUtils.js @@ -264,8 +264,8 @@ export const moreItinerariesQuery = graphql` endTime ...ItineraryTab_itinerary ...SummaryPlanContainer_itineraries - emissions { - co2grams + emissionsPerPerson { + co2Grams } legs { mode diff --git a/build/schema.graphql b/build/schema.graphql index 97dc8a0955..319f825ca1 100644 --- a/build/schema.graphql +++ b/build/schema.graphql @@ -904,7 +904,7 @@ type Emissions { """ CO₂ emissions of the trip on this itinerary, in grams per person. """ - co2grams: Float + co2Grams: Float } type fare { @@ -1474,8 +1474,8 @@ type Itinerary { """How far the user has to walk, in meters.""" walkDistance: Float - """Emissions of the trip on this itinerary.""" - emissions: Emissions + """Emissions of this itinerary per traveler.""" + emissionsPerPerson: Emissions """ A list of Legs. Each Leg is either a walking (cycling, car) portion of the diff --git a/build/schema.json b/build/schema.json index a294e52370..008de162fc 100644 --- a/build/schema.json +++ b/build/schema.json @@ -2179,7 +2179,7 @@ "description": null, "fields": [ { - "name": "co2grams", + "name": "co2Grams", "description": "CO₂ emissions of the trip on this itinerary, in grams per person.", "args": [], "type": { @@ -3173,8 +3173,8 @@ "deprecationReason": null }, { - "name": "emissions", - "description": "Emissions of the trip on this itinerary.", + "name": "emissionsPerPerson", + "description": "Emissions of this itinerary per traveler.", "args": [], "type": { "kind": "OBJECT", diff --git a/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql b/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql index 97dc8a0955..319f825ca1 100644 --- a/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql +++ b/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql @@ -904,7 +904,7 @@ type Emissions { """ CO₂ emissions of the trip on this itinerary, in grams per person. """ - co2grams: Float + co2Grams: Float } type fare { @@ -1474,8 +1474,8 @@ type Itinerary { """How far the user has to walk, in meters.""" walkDistance: Float - """Emissions of the trip on this itinerary.""" - emissions: Emissions + """Emissions of this itinerary per traveler.""" + emissionsPerPerson: Emissions """ A list of Legs. Each Leg is either a walking (cycling, car) portion of the From 5c06dbf26bf392fa0b70fb31dcceee829039f5d4 Mon Sep 17 00:00:00 2001 From: Janne Antikainen Date: Mon, 23 Oct 2023 12:29:34 +0300 Subject: [PATCH 23/37] refactor: fix text and adjust new text to description of co2 --- app/component/ItineraryTab.js | 5 ++--- app/component/itinerary.scss | 3 +-- app/translations.js | 10 +++++----- build/schema.graphql | 2 +- .../schema/schema.graphql | 2 +- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/app/component/ItineraryTab.js b/app/component/ItineraryTab.js index bb97864bb3..7ed52736fc 100644 --- a/app/component/ItineraryTab.js +++ b/app/component/ItineraryTab.js @@ -221,8 +221,7 @@ class ItineraryTab extends React.Component { : Number(this.context.match.params.hash) + 1; const co2value = typeof itinerary.emissionsPerPerson?.co2Grams === 'number' && itinerary.emissionsPerPerson?.co2Grams >= 0 ? Math.round(itinerary.emissionsPerPerson?.co2Grams) : -1; const carCo2Value = this.props.carItinerary && this.props.carItinerary.length > 0 ? Math.round(this.props.carItinerary[0].emissionsPerPerson?.co2Grams) : 0; - const co2percentage = co2value > 0 && carCo2Value > 0 ? Math.round((co2value / carCo2Value) * 100) : 0; - const co2SimpleDesc = co2percentage === 0 || carCo2Value === 0; + const co2SimpleDesc = carCo2Value === 0; return (

@@ -377,7 +376,7 @@ class ItineraryTab extends React.Component { defaultMessage="CO2 emissions for this route" values={{ co2value, - co2percentage, + carCo2Value, }} /> } diff --git a/app/component/itinerary.scss b/app/component/itinerary.scss index 7de0de6b55..e64f92ef71 100644 --- a/app/component/itinerary.scss +++ b/app/component/itinerary.scss @@ -238,11 +238,10 @@ $itinerary-tab-switch-height: 48px; .itinerary-co2-description { width: 275px; - height: 80px; left: 70px; top: 528px; &.simple { - height: 0; + width: auto; } font-family: $font-family; font-style: normal; diff --git a/app/translations.js b/app/translations.js index 53ebf4c9d1..d064f258ef 100644 --- a/app/translations.js +++ b/app/translations.js @@ -1094,9 +1094,9 @@ const translations = { instructions: 'Instructions', 'is-open': 'Open:', 'itinerary-co2.description': - 'Tämän matkan CO2 päästö on {co2value} g, joka on {co2percentage} % verrattuna henkilöautolla suoritettuun matkaan.', + '{co2value} g of CO2 emissions will be generated on this journey. A car would generate {carCo2Value} g of CO2 on the same journey.', 'itinerary-co2.description-simple': - 'Tämän matkan CO2 päästö on {co2value} g.', + '{co2value} g of CO2 emissions will be generated on this journey.', 'itinerary-co2.link': 'This is how we reduce emissions ›', 'itinerary-co2.title': 'Matkan CO2-päästöt', 'itinerary-details.biking-leg': @@ -2239,7 +2239,7 @@ const translations = { instructions: 'Ohjeet', 'is-open': 'Avoinna:', 'itinerary-co2.description': - 'Tämän matkan CO2 päästö on {co2value} g, joka on {co2percentage} % verrattuna henkilöautolla suoritettuun matkaan.', + 'Tämän matkan CO2 päästö on {co2value} g. Autolla kuljettuna se olisi ollut {carCo2Value} g.', 'itinerary-co2.description-simple': 'Tämän matkan CO2 päästö on {co2value} g.', 'itinerary-co2.link': 'Näin vähennämme päästöjä ›', @@ -4155,9 +4155,9 @@ const translations = { instructions: 'Anvisningar', 'is-open': 'Öppet:', 'itinerary-co2.description': - 'Tämän matkan CO2 päästö on {co2value} g, joka on {co2percentage} % verrattuna henkilöautolla suoritettuun matkaan.', + 'CO2-utsläppen för denna resa är {co2value} g. En bil skulle generera {carCo2Value} g CO2 på samma resa.', 'itinerary-co2.description-simple': - 'Tämän matkan CO2 päästö on {co2value} g.', + 'CO2-utsläppen för denna resa är {co2value} g.', 'itinerary-co2.link': 'Så minskar vi utsläppen ›', 'itinerary-co2.title': 'Matkan CO2-päästöt', 'itinerary-details.biking-leg': diff --git a/build/schema.graphql b/build/schema.graphql index 319f825ca1..4448cc8506 100644 --- a/build/schema.graphql +++ b/build/schema.graphql @@ -902,7 +902,7 @@ type elevationProfileComponent { type Emissions { """ - CO₂ emissions of the trip on this itinerary, in grams per person. + CO₂ emissions in grams. """ co2Grams: Float } diff --git a/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql b/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql index 319f825ca1..4448cc8506 100644 --- a/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql +++ b/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql @@ -902,7 +902,7 @@ type elevationProfileComponent { type Emissions { """ - CO₂ emissions of the trip on this itinerary, in grams per person. + CO₂ emissions in grams. """ co2Grams: Float } From 92eb64df315167f9222bbeaf44e482273498d1de Mon Sep 17 00:00:00 2001 From: sharhio Date: Tue, 24 Oct 2023 13:51:52 +0300 Subject: [PATCH 24/37] feat: dt-5325 emissions schema changes --- .../ItinerarySummaryListContainer.js | 10 ++++------ app/component/ItineraryTab.js | 8 ++++---- app/component/SummaryPage.js | 10 +++++----- app/component/SummaryPlanContainer.js | 4 ++-- app/component/SummaryRow.js | 6 +++--- app/prop-types/ItineraryShape.js | 2 +- app/util/queryUtils.js | 2 +- build/schema.graphql | 4 +++- build/schema.json | 16 +++++++++++++--- .../schema/schema.graphql | 4 +++- 10 files changed, 39 insertions(+), 27 deletions(-) diff --git a/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js b/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js index 67d66099f9..d9c8f7b69a 100644 --- a/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js +++ b/app/component/ItinerarySummaryListContainer/ItinerarySummaryListContainer.js @@ -57,12 +57,10 @@ function ItinerarySummaryListContainer( ) { const lowestCo2value = Math.round( itineraries - .filter(itinerary => itinerary.emissionsPerPerson?.co2Grams >= 0) + .filter(itinerary => itinerary.emissionsPerPerson?.co2 >= 0) .reduce((a, b) => { - return a.emissionsPerPerson?.co2Grams < b.emissionsPerPerson?.co2Grams - ? a - : b; - }, 0).emissionsPerPerson?.co2Grams, + return a.emissionsPerPerson?.co2 < b.emissionsPerPerson?.co2 ? a : b; + }, 0).emissionsPerPerson?.co2, ); const summaries = itineraries.map((itinerary, i) => ( = 0 ? Math.round(itinerary.emissionsPerPerson?.co2Grams) : -1; - const carCo2Value = this.props.carItinerary && this.props.carItinerary.length > 0 ? Math.round(this.props.carItinerary[0].emissionsPerPerson?.co2Grams) : 0; + const co2value = typeof itinerary.emissionsPerPerson?.co2 === 'number' && itinerary.emissionsPerPerson?.co2 >= 0 ? Math.round(itinerary.emissionsPerPerson?.co2) : -1; + const carCo2Value = this.props.carItinerary && this.props.carItinerary.length > 0 ? Math.round(this.props.carItinerary[0].emissionsPerPerson?.co2) : 0; const co2SimpleDesc = carCo2Value === 0; return (
@@ -451,7 +451,7 @@ const withRelay = createFragmentContainer( type } emissionsPerPerson { - co2Grams + co2 } legs { mode diff --git a/app/component/SummaryPage.js b/app/component/SummaryPage.js index e35c36ae97..2342b7abd3 100644 --- a/app/component/SummaryPage.js +++ b/app/component/SummaryPage.js @@ -738,7 +738,7 @@ class SummaryPage extends React.Component { ...ItineraryTab_itinerary ...SummaryPlanContainer_itineraries emissionsPerPerson { - co2Grams + co2 } legs { mode @@ -794,7 +794,7 @@ class SummaryPage extends React.Component { ...ItineraryTab_itinerary ...SummaryPlanContainer_itineraries emissionsPerPerson { - co2Grams + co2 } legs { mode @@ -856,7 +856,7 @@ class SummaryPage extends React.Component { ...ItineraryTab_itinerary ...SummaryPlanContainer_itineraries emissionsPerPerson { - co2Grams + co2 } legs { mode @@ -1039,7 +1039,7 @@ class SummaryPage extends React.Component { ...ItineraryTab_itinerary ...SummaryPlanContainer_itineraries emissionsPerPerson { - co2Grams + co2 } legs { mode @@ -2972,7 +2972,7 @@ const containerComponent = createRefetchContainer( ...ItineraryTab_itinerary ...SummaryPlanContainer_itineraries emissionsPerPerson { - co2Grams + co2 } legs { mode diff --git a/app/component/SummaryPlanContainer.js b/app/component/SummaryPlanContainer.js index 33d358b900..062d422906 100644 --- a/app/component/SummaryPlanContainer.js +++ b/app/component/SummaryPlanContainer.js @@ -345,7 +345,7 @@ const connectedContainer = createFragmentContainer( startTime endTime emissionsPerPerson { - co2Grams + co2 } legs { mode @@ -402,7 +402,7 @@ const connectedContainer = createFragmentContainer( endTime startTime emissionsPerPerson { - co2Grams + co2 } legs { mode diff --git a/app/component/SummaryRow.js b/app/component/SummaryRow.js index 6f2b986a0b..7feb0e3622 100644 --- a/app/component/SummaryRow.js +++ b/app/component/SummaryRow.js @@ -238,9 +238,9 @@ const SummaryRow = ( const endTime = moment(data.endTime); const duration = endTime.diff(startTime); const co2value = - typeof data.emissionsPerPerson?.co2Grams === 'number' && - data.emissionsPerPerson?.co2Grams >= 0 - ? Math.round(data.emissionsPerPerson?.co2Grams) + typeof data.emissionsPerPerson?.co2 === 'number' && + data.emissionsPerPerson?.co2 >= 0 + ? Math.round(data.emissionsPerPerson?.co2) : -1; const mobile = bp => !(bp === 'large'); const legs = []; diff --git a/app/prop-types/ItineraryShape.js b/app/prop-types/ItineraryShape.js index 7e33256503..123c85a199 100644 --- a/app/prop-types/ItineraryShape.js +++ b/app/prop-types/ItineraryShape.js @@ -5,6 +5,6 @@ export default PropTypes.shape({ startTime: PropTypes.number, legs: PropTypes.arrayOf(PropTypes.object), emissionsPerPerson: PropTypes.shape({ - co2Grams: PropTypes.number, + co2: PropTypes.number, }), }); diff --git a/app/util/queryUtils.js b/app/util/queryUtils.js index 3a59f860a3..2a742170cc 100644 --- a/app/util/queryUtils.js +++ b/app/util/queryUtils.js @@ -265,7 +265,7 @@ export const moreItinerariesQuery = graphql` ...ItineraryTab_itinerary ...SummaryPlanContainer_itineraries emissionsPerPerson { - co2Grams + co2 } legs { mode diff --git a/build/schema.graphql b/build/schema.graphql index 4448cc8506..9eabdff1e1 100644 --- a/build/schema.graphql +++ b/build/schema.graphql @@ -904,7 +904,7 @@ type Emissions { """ CO₂ emissions in grams. """ - co2Grams: Float + co2: Grams } type fare { @@ -1008,6 +1008,8 @@ type Geometry { scalar GeoJson +scalar Grams + type StopGeometries { """Representation of the stop geometries as GeoJSON (https://geojson.org/)""" geoJson: GeoJson, diff --git a/build/schema.json b/build/schema.json index 008de162fc..a943a3b42c 100644 --- a/build/schema.json +++ b/build/schema.json @@ -2179,12 +2179,12 @@ "description": null, "fields": [ { - "name": "co2Grams", - "description": "CO₂ emissions of the trip on this itinerary, in grams per person.", + "name": "co2", + "description": "CO₂ emissions in grams.", "args": [], "type": { "kind": "SCALAR", - "name": "Float", + "name": "Grams", "ofType": null }, "isDeprecated": false, @@ -2609,6 +2609,16 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "SCALAR", + "name": "Grams", + "description": "", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, { "kind": "SCALAR", "name": "ID", diff --git a/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql b/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql index 4448cc8506..9eabdff1e1 100644 --- a/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql +++ b/digitransit-search-util/packages/digitransit-search-util-query-utils/schema/schema.graphql @@ -904,7 +904,7 @@ type Emissions { """ CO₂ emissions in grams. """ - co2Grams: Float + co2: Grams } type fare { @@ -1008,6 +1008,8 @@ type Geometry { scalar GeoJson +scalar Grams + type StopGeometries { """Representation of the stop geometries as GeoJSON (https://geojson.org/)""" geoJson: GeoJson, From 5cba79858932ad4490441b2231eaffba626a6fb3 Mon Sep 17 00:00:00 2001 From: sharhio Date: Tue, 31 Oct 2023 10:45:41 +0200 Subject: [PATCH 25/37] fix: otp schema location update --- docs/GraphQL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/GraphQL.md b/docs/GraphQL.md index 5f9452977f..cacf6299d4 100644 --- a/docs/GraphQL.md +++ b/docs/GraphQL.md @@ -11,4 +11,4 @@ When running otp in localhost, this usually translates to something like: - `cd build; SCHEMA_SRC=~/OpenTripPlanner/src/ext/resources/legacygraphqlapi/schema.graphqls OTP_URL=http://localhost:8080/otp/routers/hsl/index/graphql node generate-schema.js` + `cd build; SCHEMA_SRC=~/OpenTripPlanner/src/main/resources/org/opentripplanner/apis/gtfs/schema.graphqls OTP_URL=http://localhost:8080/otp/routers/hsl/index/graphql node generate-schema.js` From abe684f4f343143fffcdf7a7a57e8e9f0471f1e8 Mon Sep 17 00:00:00 2001 From: sharhio Date: Tue, 7 Nov 2023 16:25:59 +0200 Subject: [PATCH 26/37] fix: dt-5325 translations --- app/component/ItineraryTab.js | 4 ++-- app/component/SummaryRow.js | 2 +- app/translations.js | 18 +++++++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/component/ItineraryTab.js b/app/component/ItineraryTab.js index e3ee2c3158..166acd8302 100644 --- a/app/component/ItineraryTab.js +++ b/app/component/ItineraryTab.js @@ -365,7 +365,7 @@ class ItineraryTab extends React.Component { {co2SimpleDesc ? Date: Wed, 15 Nov 2023 14:24:30 +0200 Subject: [PATCH 27/37] fix: dt-5325 consistent texts, clearer screen reader text --- app/component/ItineraryTab.js | 33 +++++++++++++++++++++------------ app/translations.js | 24 +++++++++++++++++++++--- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/app/component/ItineraryTab.js b/app/component/ItineraryTab.js index ec43ce4e71..09d21292c1 100644 --- a/app/component/ItineraryTab.js +++ b/app/component/ItineraryTab.js @@ -225,8 +225,10 @@ class ItineraryTab extends React.Component { : Number(this.context.match.params.hash) + 1; const co2value = typeof itinerary.emissionsPerPerson?.co2 === 'number' && itinerary.emissionsPerPerson?.co2 >= 0 ? Math.round(itinerary.emissionsPerPerson?.co2) : -1; - const carCo2Value = this.props.carItinerary && this.props.carItinerary.length > 0 ? Math.round(this.props.carItinerary[0].emissionsPerPerson?.co2) : 0; - const co2SimpleDesc = carCo2Value === 0; + const itineraryIsCar = itinerary.legs.every((leg) => leg.mode === 'CAR'); + const carCo2Value = !itineraryIsCar && this.props.carItinerary && this.props.carItinerary.length > 0 ? Math.round(this.props.carItinerary[0].emissionsPerPerson?.co2) : 0; + const co2SimpleDesc = carCo2Value === 0 || itineraryIsCar; + const co2DescriptionId = co2SimpleDesc ? "itinerary-co2.description-simple" : "itinerary-co2.description"; const itineraryContainsCallLegs = itinerary.legs.some(leg => isCallAgencyPickupType(leg)); @@ -311,9 +313,15 @@ class ItineraryTab extends React.Component {
- + + + @@ -393,23 +401,24 @@ class ItineraryTab extends React.Component {
- {co2SimpleDesc ? -

{textSummary} - {config.showCO2InItinerarySummary && co2value >= 0 && co2summary} + {config.showCO2InItinerarySummary && + co2value !== null && + co2value >= 0 && + co2summary}
)} - {config.showCO2InItinerarySummary && co2value >= 0 && ( -
- {lowestCo2value === co2value && ( - - )} -
{co2value} g
-
- )} + {config.showCO2InItinerarySummary && + co2value !== null && + co2value >= 0 && ( +
+ {lowestCo2value === co2value && ( + + )} +
{co2value} g
+
+ )}
From a2e9dd059f95dec3094cd7232200d4a769ce51ed Mon Sep 17 00:00:00 2001 From: sharhio Date: Tue, 21 Nov 2023 15:00:52 +0200 Subject: [PATCH 34/37] fix: dt-5325 grammar --- app/translations.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/translations.js b/app/translations.js index e31a751388..4978a67162 100644 --- a/app/translations.js +++ b/app/translations.js @@ -2293,13 +2293,13 @@ const translations = { instructions: 'Ohjeet', 'is-open': 'Avoinna:', 'itinerary-co2.description': - 'Tämän matkan CO₂-päästöt on {co2value} g. Autolla kuljettuna se olisi ollut {carCo2Value} g.', + 'Tämän matkan CO₂-päästöt ovat {co2value} g. Autolla kuljettuna ne olisivat olleet {carCo2Value} g.', 'itinerary-co2.description-simple': - 'Tämän matkan CO₂-päästöt on {co2value} g.', + 'Tämän matkan CO₂-päästöt ovat {co2value} g.', 'itinerary-co2.description-simple-sr': - 'Tämän matkan hiilidioksidipäästöt on {co2value} g.', + 'Tämän matkan hiilidioksidipäästöt ovat {co2value} g.', 'itinerary-co2.description-sr': - 'Tämän matkan hiilidioksidipäästöt on {co2value} g. Autolla kuljettuna se olisi ollut {carCo2Value} g.', + 'Tämän matkan hiilidioksidipäästöt ovat {co2value} g. Autolla kuljettuna ne olisivat olleet {carCo2Value} g.', 'itinerary-co2.link': 'Näin vähennämme päästöjä ›', 'itinerary-co2.title': 'Matkan CO₂-päästöt', 'itinerary-co2.title-sr': 'Matkan hiilidioksidipäästöt', From d606ab43cc59b4db59cde41ff55d9558e29788dd Mon Sep 17 00:00:00 2001 From: sharhio Date: Tue, 21 Nov 2023 15:10:36 +0200 Subject: [PATCH 35/37] fix: dt-5325 open link in new tab --- app/component/Emissions.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/component/Emissions.js b/app/component/Emissions.js index 87472f400d..1006967f80 100644 --- a/app/component/Emissions.js +++ b/app/component/Emissions.js @@ -54,7 +54,11 @@ const Emissions = ({ itinerary, carItinerary, emissionsInfolink }) => { {emissionsInfolink && (
- + Date: Tue, 21 Nov 2023 15:56:27 +0200 Subject: [PATCH 36/37] fix: dt-5325 no car value for park and ride without ride + link style --- app/component/Emissions.js | 27 ++++++++++++++------------- app/component/itinerary.scss | 6 ++++++ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/app/component/Emissions.js b/app/component/Emissions.js index 1006967f80..5cfcbd567e 100644 --- a/app/component/Emissions.js +++ b/app/component/Emissions.js @@ -8,7 +8,9 @@ import { getCo2Value } from '../util/itineraryUtils'; const Emissions = ({ itinerary, carItinerary, emissionsInfolink }) => { const co2value = getCo2Value(itinerary); - const itineraryIsCar = itinerary.legs.every(leg => leg.mode === 'CAR'); + const itineraryIsCar = itinerary.legs.every( + leg => leg.mode === 'CAR' || leg.mode === 'WALK', + ); const carCo2Value = !itineraryIsCar && carItinerary ? Math.round(carItinerary?.emissionsPerPerson?.co2) @@ -53,18 +55,17 @@ const Emissions = ({ itinerary, carItinerary, emissionsInfolink }) => { /> {emissionsInfolink && ( - + + + )}
diff --git a/app/component/itinerary.scss b/app/component/itinerary.scss index d854ff241c..01cef27016 100644 --- a/app/component/itinerary.scss +++ b/app/component/itinerary.scss @@ -253,7 +253,13 @@ $itinerary-tab-switch-height: 48px; flex-direction: column; color: #666666; } + } + .emissions-info-link { + text-decoration: none; + font-weight: $font-weight-medium; + color: $primary-color; + font-size: $font-size-small; } .co2-container { From eee619daddc22a88b8f8d2c1d263e33a1141b1c1 Mon Sep 17 00:00:00 2001 From: sharhio Date: Tue, 21 Nov 2023 16:49:23 +0200 Subject: [PATCH 37/37] fix: dt-5325 emissions description scaling --- app/component/itinerary.scss | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/app/component/itinerary.scss b/app/component/itinerary.scss index 01cef27016..865685b5de 100644 --- a/app/component/itinerary.scss +++ b/app/component/itinerary.scss @@ -205,12 +205,9 @@ $itinerary-tab-switch-height: 48px; .divider-top, .divider-bottom { - position: relative; - width: 400px; - height: 1px; - left: -5px; - border-top: 1px solid #dddddd; - box-sizing: border-box; + border-bottom: 1px solid #dddddd; + margin-left: 10px; + margin-right: 10px; @media print { border: none; @@ -237,7 +234,7 @@ $itinerary-tab-switch-height: 48px; } .itinerary-co2-description { - width: 275px; + width: auto; left: 70px; top: 528px; &.simple {