Skip to content

Commit

Permalink
Merge pull request #5204 from HSLdevcom/via-point-mvp
Browse files Browse the repository at this point in the history
add via points to expanded view of the itinerary
  • Loading branch information
vesameskanen authored Jan 9, 2025
2 parents fcb5128 + 2d46b30 commit f968968
Show file tree
Hide file tree
Showing 16 changed files with 161 additions and 31 deletions.
6 changes: 6 additions & 0 deletions app/component/itinerary/BicycleLeg.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,12 @@ export default function BicycleLeg(
to={`/${PREFIX_STOPS}/${fromStop.gtfsId}`}
>
{origin}
{leg.isViaPoint && (
<Icon
img="icon-icon_mapMarker-via"
className="itinerary-mapmarker-icon"
/>
)}
<Icon
img="icon-icon_arrow-collapse--right"
className="itinerary-arrow-icon"
Expand Down
7 changes: 7 additions & 0 deletions app/component/itinerary/BikeParkLeg.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { displayDistance } from '../../util/geo-utils';
import { durationToString } from '../../util/timeUtils';
import ItineraryCircleLineWithIcon from './ItineraryCircleLineWithIcon';
import ItineraryMapAction from './ItineraryMapAction';
import Icon from '../Icon';

const BikeParkLeg = (
{ leg, index, focusAction, bikePark },
Expand Down Expand Up @@ -55,6 +56,12 @@ const BikeParkLeg = (
> */}
<div className="address">
<FormattedMessage id="bike-park" />
{leg.isViaPoint && (
<Icon
img="icon-icon_mapMarker-via"
className="itinerary-mapmarker-icon"
/>
)}
{/* TODO */}
{/* {bikePark && (
<Icon
Expand Down
6 changes: 6 additions & 0 deletions app/component/itinerary/CarLeg.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ export default function CarLeg(props, { config, intl }) {
<div className="address-container">
<div className="address">
{address}
{props.leg.isViaPoint && (
<Icon
img="icon-icon_mapMarker-via"
className="itinerary-mapmarker-icon"
/>
)}
{props.leg.from.stop && (
<Icon
img="icon-icon_arrow-collapse--right"
Expand Down
6 changes: 6 additions & 0 deletions app/component/itinerary/CarParkLeg.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ function CarParkLeg(props, { config, intl }) {
>
<div className="address">
<FormattedMessage id="car_park" defaultMessage="Park & Ride" />
{props.leg.isViaPoint && (
<Icon
img="icon-icon_mapMarker-via"
className="itinerary-mapmarker-icon"
/>
)}
{props.carPark && (
<Icon
img="icon-icon_arrow-collapse--right"
Expand Down
9 changes: 9 additions & 0 deletions app/component/itinerary/IntermediateLeg.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ function IntermediateLeg(
previousZoneId,
currentZoneId,
nextZoneId,
isViaPoint,
isCanceled,
isLastPlace,
},
Expand Down Expand Up @@ -151,6 +152,12 @@ function IntermediateLeg(
</span>
{` ${name}`}
</div>
{isViaPoint && (
<Icon
img="icon-icon_mapMarker-via"
className="itinerary-mapmarker-icon"
/>
)}
<Icon
img="icon-icon_arrow-collapse--right"
className="itinerary-arrow-icon"
Expand All @@ -176,6 +183,7 @@ IntermediateLeg.propTypes = {
previousZoneId: PropTypes.string,
currentZoneId: PropTypes.string,
nextZoneId: PropTypes.string,
isViaPoint: PropTypes.bool,
isLastPlace: PropTypes.bool,
gtfsId: PropTypes.string,
isCanceled: PropTypes.bool,
Expand All @@ -191,6 +199,7 @@ IntermediateLeg.defaultProps = {
isCanceled: false,
realTime: false,
isLastPlace: false,
isViaPoint: false,
gtfsId: undefined,
color: undefined,
};
Expand Down
9 changes: 5 additions & 4 deletions app/component/itinerary/Itinerary.js
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,8 @@ const Itinerary = (
}
nameLengthSum += 10; // every leg requires some minimum space
if (
leg.intermediatePlace ||
connectsFromViaPoint(leg, intermediatePlaces)
i > 0 &&
(leg.intermediatePlace || connectsFromViaPoint(leg, intermediatePlaces))
) {
intermediateSlack +=
legTime(leg.start) - legTime(compressedLegs[i - 1].end); // calculate time spent at each intermediate place
Expand Down Expand Up @@ -364,8 +364,9 @@ const Itinerary = (
const isNextLegLast = i + 1 === compressedLegs.length - 1;
const shouldRenderLastLeg =
isNextLegLast && lastLegLength < renderBarThreshold;
const previousLeg = compressedLegs[i - 1];
const nextLeg = compressedLegs[i + 1];
const previousLeg = i > 0 ? compressedLegs[i - 1] : null;
const nextLeg =
i < compressedLegs.length - 1 ? compressedLegs[i + 1] : null;
let legLength = relativeLength(endMs - startMs);
const longName = !leg?.route?.shortName || leg?.route?.shortName.length > 5;

Expand Down
22 changes: 18 additions & 4 deletions app/component/itinerary/Legs.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable react/no-array-index-key */
import PropTypes from 'prop-types';
import React from 'react';
import { matchShape } from 'found';
import {
configShape,
fareShape,
Expand All @@ -25,12 +26,14 @@ import {
isCallAgencyLeg,
isLegOnFoot,
legTime,
markViaPoints,
getBoardingLeg,
} from '../../util/legUtils';
import { getRouteMode } from '../../util/modeUtils';
import { addAnalyticsEvent } from '../../util/analyticsUtils';
import Profile from './Profile';
import BikeParkLeg from './BikeParkLeg';
import { getIntermediatePlaces } from '../../util/otpStrings';

const stopCode = stop => stop && stop.code && <StopCode code={stop.code} />;

Expand All @@ -53,7 +56,10 @@ export default class Legs extends React.Component {
relayEnvironment: relayShape,
};

static contextTypes = { config: configShape };
static contextTypes = {
config: configShape,
match: matchShape,
};

static defaultProps = {
fares: [],
Expand Down Expand Up @@ -95,7 +101,10 @@ export default class Legs extends React.Component {
} = this.props;
const { waitThreshold } = this.context.config.itinerary;

const compressedLegs = compressLegs(itinerary.legs, true).map(leg => ({
const { location } = this.context.match;
const intermediatePlaces = getIntermediatePlaces(location.query);
const itineraryLegs = markViaPoints(itinerary.legs, intermediatePlaces);
const compressedLegs = compressLegs(itineraryLegs, true).map(leg => ({
showBikeBoardingInformation,
showCarBoardingInformation,
...leg,
Expand Down Expand Up @@ -163,10 +172,15 @@ export default class Legs extends React.Component {
!isNextLegInterlining &&
leg.to.stop
) {
const waitLegProps = { ...leg };
if (nextLeg && nextLeg.isViaPoint) {
waitLegProps.isViaPoint = true;
nextLeg.isViaPoint = false;
}
waitLeg = (
<WaitLeg
index={j}
leg={leg}
leg={waitLegProps}
start={leg.end}
waitTime={waitTime}
focusAction={this.focus(leg.to)}
Expand Down Expand Up @@ -289,7 +303,7 @@ export default class Legs extends React.Component {
legs.push(
<WalkLeg
index={numberOfLegs}
leg={lastLeg}
leg={{ ...lastLeg, isViaPoint: false }}
previousLeg={lastLeg}
focusAction={this.focus(lastLeg.to)}
focusToLeg={this.focusToLeg(lastLeg)}
Expand Down
7 changes: 7 additions & 0 deletions app/component/itinerary/TransitLeg.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ class TransitLeg extends React.Component {
getZoneLabel(nextZoneId, this.context.config)) ||
undefined
}
isViaPoint={place.isViaPoint}
isLastPlace={isLastPlace}
isCanceled={isCanceled}
/>
Expand Down Expand Up @@ -478,6 +479,12 @@ class TransitLeg extends React.Component {
to={`/${PREFIX_STOPS}/${leg.from.stop.gtfsId}`}
>
{leg.from.name}
{leg.isViaPoint && (
<Icon
img="icon-icon_mapMarker-via"
className="itinerary-mapmarker-icon"
/>
)}
<Icon
img="icon-icon_arrow-collapse--right"
className="itinerary-arrow-icon"
Expand Down
6 changes: 6 additions & 0 deletions app/component/itinerary/WaitLeg.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ function WaitLeg(
to={`/${PREFIX_STOPS}/${leg.to.stop.gtfsId}`}
>
{leg.to.name}
{leg.isViaPoint && (
<Icon
img="icon-icon_mapMarker-via"
className="itinerary-mapmarker-icon"
/>
)}
<Icon
img="icon-icon_arrow-collapse--right"
className="itinerary-arrow-icon"
Expand Down
6 changes: 6 additions & 0 deletions app/component/itinerary/WalkLeg.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,12 @@ function WalkLeg(
to={`/${PREFIX_STOPS}/${leg[toOrFrom].stop.gtfsId}`}
>
{returnNotice || leg[toOrFrom].name}
{leg.isViaPoint && (
<Icon
img="icon-icon_mapMarker-via"
className="itinerary-mapmarker-icon"
/>
)}
{leg[toOrFrom].stop && (
<Icon
img="icon-icon_arrow-collapse--right"
Expand Down
10 changes: 10 additions & 0 deletions app/component/itinerary/itinerary.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1585,6 +1585,11 @@ $itinerary-tab-switch-height: 48px;
margin-left: 6px;
}

.itinerary-mapmarker-icon {
font-size: 12px;
margin-left: 6px;
}

.itinerary-intermediate-stop-name {
flex-grow: 0;
}
Expand Down Expand Up @@ -1764,6 +1769,11 @@ $itinerary-tab-switch-height: 48px;
font-size: 10px;
margin-left: 6px;
}

&.itinerary-mapmarker-icon {
font-size: 12px;
margin-left: 6px;
}
}
}

Expand Down
77 changes: 61 additions & 16 deletions app/util/legUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,29 @@ function syntheticEndpoint(originalEndpoint, place) {
};
}

// Once a via place is matched, it is used and will not match again.
function includesAndRemove(array, id) {
const index = array.indexOf(id);
if (index >= 0) {
array.splice(index, 1);
return true;
}
return false;
}

function isViaPointMatch(stop, viaPoints) {
return (
stop &&
(includesAndRemove(viaPoints, stop.gtfsId) ||
(stop.parentStation &&
includesAndRemove(viaPoints, stop.parentStation.gtfsId)))
);
}

/**
* Adds intermediate: true to legs if their start point should have a via point
* marker, possibly splitting legs in case the via point belongs in the middle.
* Once a via point is used, it is not matched again.
*
* @param originalLegs Leg objects from graphql query
* @param viaPlaces Location objects (otpToLocation) from query parameter
Expand All @@ -245,27 +265,14 @@ function syntheticEndpoint(originalEndpoint, place) {
export function splitLegsAtViaPoints(originalLegs, viaPlaces) {
const splitLegs = [];
// Once a via place is matched, it is used and will not match again.
function includesAndRemove(array, id) {
const index = array.indexOf(id);
if (index >= 0) {
array.splice(index, 1);
return true;
}
return false;
}
const viaPoints = viaPlaces.map(p => p.gtfsId);
const isViaPointMatch = stop =>
stop &&
(includesAndRemove(viaPoints, stop.gtfsId) ||
(stop.parentStation &&
includesAndRemove(viaPoints, stop.parentStation.gtfsId)));
let nextLegStartsWithIntermediate = false;
originalLegs.forEach(originalLeg => {
const leg = { ...originalLeg };
const { intermediatePlaces } = leg;
if (
nextLegStartsWithIntermediate ||
(leg.transitLeg && isViaPointMatch(leg.from.stop))
(leg.transitLeg && isViaPointMatch(leg.from.stop, viaPoints))
) {
leg.intermediatePlace = true;
nextLegStartsWithIntermediate = false;
Expand All @@ -274,7 +281,7 @@ export function splitLegsAtViaPoints(originalLegs, viaPlaces) {
let start = 0;
let lastSplit = -1;
intermediatePlaces.forEach((place, i) => {
if (isViaPointMatch(place.stop)) {
if (isViaPointMatch(place.stop, viaPoints)) {
const leftLeg = {
...leg,
to: syntheticEndpoint(leg.to, place),
Expand All @@ -297,12 +304,50 @@ export function splitLegsAtViaPoints(originalLegs, viaPlaces) {
}
}
splitLegs.push(leg);
if (leg.transitLeg && isViaPointMatch(leg.to.stop)) {
if (leg.transitLeg && isViaPointMatch(leg.to.stop, viaPoints)) {
nextLegStartsWithIntermediate = true;
}
});
return splitLegs;
}

/**
* Mark via points to legs and possible intermediatePlaces in them. Once a via
* point is matched, it is not used again. Used for expanded view of the
* itinerary.
*
* @param originalLegs Leg objects from graphql query
* @param viaPlaces Location objects (otpToLocation) from query parameter
* @returns {*[]}
*/
export function markViaPoints(originalLegs, viaPlaces) {
const legs = [];
const viaPoints = viaPlaces.map(p => p.gtfsId);
originalLegs.forEach(leg => {
const isViaPoint = isViaPointMatch(leg.from.stop, viaPoints);
if (leg.intermediatePlaces) {
const intermediatePlaces = [];
leg.intermediatePlaces.forEach(place => {
intermediatePlaces.push({
...place,
isViaPoint: isViaPointMatch(place.stop, viaPoints),
});
});
legs.push({
...leg,
intermediatePlaces,
isViaPoint,
});
} else {
legs.push({
...leg,
isViaPoint,
});
}
});
return legs;
}

/**
* Compresses the incoming legs (affects only legs with mode BICYCLE, WALK or CITYBIKE). These are combined
* so that the person will be walking their bicycle and there won't be multiple similar legs
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@digitransit-component/digitransit-component-autosuggest-panel",
"version": "4.0.1",
"version": "4.0.2",
"description": "digitransit-component autosuggest-panel module",
"main": "index.js",
"files": [
Expand Down
Loading

0 comments on commit f968968

Please sign in to comment.