From c081109a7274cf45c6af7dd9770cbafa34dd14b6 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 11 Dec 2024 15:31:14 +0200 Subject: [PATCH 1/9] fix: no navi alerts from legs which have been already traveled --- .../itinerary/navigator/NaviCardContainer.js | 2 +- .../itinerary/navigator/NaviUtils.js | 31 +++++++++++++------ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/app/component/itinerary/navigator/NaviCardContainer.js b/app/component/itinerary/navigator/NaviCardContainer.js index 89366f9817..619130f5d7 100644 --- a/app/component/itinerary/navigator/NaviCardContainer.js +++ b/app/component/itinerary/navigator/NaviCardContainer.js @@ -82,7 +82,7 @@ function NaviCardContainer( // Alerts for NaviStack addMessages( incomingMessages, - getItineraryAlerts(legs, intl, messages, match.params, router), + getItineraryAlerts(legs, time, intl, messages, match.params, router), ); if (currentLeg) { diff --git a/app/component/itinerary/navigator/NaviUtils.js b/app/component/itinerary/navigator/NaviUtils.js index 16483126ff..c98fc83752 100644 --- a/app/component/itinerary/navigator/NaviUtils.js +++ b/app/component/itinerary/navigator/NaviUtils.js @@ -8,7 +8,7 @@ import { getItineraryPagePath } from '../../../util/path'; const TRANSFER_SLACK = 60000; const DISPLAY_MESSAGE_THRESHOLD = 120 * 1000; // 2 minutes -function findTransferProblem(legs) { +function findTransferProblem(legs, time) { for (let i = 1; i < legs.length - 1; i++) { const prev = legs[i - 1]; const leg = legs[i]; @@ -16,7 +16,8 @@ function findTransferProblem(legs) { if (prev.transitLeg && leg.transitLeg && !leg.interlineWithPreviousLeg) { // transfer at a stop - if (legTime(leg.start) - legTime(prev.end) < TRANSFER_SLACK) { + const start = legTime(leg.start); + if (start > time && start - legTime(prev.end) < TRANSFER_SLACK) { return [prev, leg]; } } @@ -25,10 +26,13 @@ function findTransferProblem(legs) { // transfer with some walking const t1 = legTime(prev.end); const t2 = legTime(next.start); - const transferDuration = legTime(leg.end) - legTime(leg.start); - const slack = t2 - t1 - transferDuration; - if (slack < TRANSFER_SLACK) { - return [prev, next]; + if (t2 > time) { + // transfer is not over yet + const transferDuration = legTime(leg.end) - legTime(leg.start); + const slack = t2 - t1 - transferDuration; + if (slack < TRANSFER_SLACK) { + return [prev, next]; + } } } } @@ -144,8 +148,17 @@ export const getTransitLegState = (leg, intl, messages, time) => { return state; }; -export const getItineraryAlerts = (legs, intl, messages, location, router) => { - const canceled = legs.filter(leg => leg.realtimeState === 'CANCELED'); +export const getItineraryAlerts = ( + legs, + time, + intl, + messages, + location, + router, +) => { + const canceled = legs.filter( + leg => leg.realtimeState === 'CANCELED' && legTime(leg.start) > time, + ); let content; const alerts = legs.flatMap(leg => { return leg.alerts @@ -178,7 +191,7 @@ export const getItineraryAlerts = (legs, intl, messages, location, router) => { id: alert.id, })); }); - const transferProblem = findTransferProblem(legs); + const transferProblem = findTransferProblem(legs, time); const abortTrip = ; const withShowRoutesBtn = children => (
From 87a4c4a1f52de8617b94c863caa3e3ccae0ab196 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 11 Dec 2024 15:40:28 +0200 Subject: [PATCH 2/9] fix: consistent validation for user position --- app/component/itinerary/navigator/NaviContainer.js | 7 +++++-- app/component/itinerary/navigator/NaviInstructions.js | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/component/itinerary/navigator/NaviContainer.js b/app/component/itinerary/navigator/NaviContainer.js index e4a4092a53..ba4551cf40 100644 --- a/app/component/itinerary/navigator/NaviContainer.js +++ b/app/component/itinerary/navigator/NaviContainer.js @@ -27,7 +27,10 @@ function NaviContainer( ) { const [isPositioningAllowed, setPositioningAllowed] = useState(false); - const position = getStore('PositionStore').getLocationState(); + let position = getStore('PositionStore').getLocationState(); + if (!position.hasLocation) { + position = null; + } const { realTimeLegs, @@ -41,7 +44,7 @@ function NaviContainer( } = useRealtimeLegs(relayEnvironment, legs); useEffect(() => { - if (position.hasLocation) { + if (position) { mapRef?.enableMapTracking(); setPositioningAllowed(true); } else { diff --git a/app/component/itinerary/navigator/NaviInstructions.js b/app/component/itinerary/navigator/NaviInstructions.js index 0786f8bbbb..adbb6a5291 100644 --- a/app/component/itinerary/navigator/NaviInstructions.js +++ b/app/component/itinerary/navigator/NaviInstructions.js @@ -20,7 +20,7 @@ export default function NaviInstructions( if (legType === LEGTYPE.MOVE) { let remainingTraversal; - if (position?.lat && position?.lon) { + if (position) { // TODO: maybe apply only when distance is close enough to the path const posXY = GeodeticToEnu(position.lat, position.lon, origin); const { traversed } = pathProgress(posXY, leg.geometry); From a574de6c9cfdf0999cf1b3fe20408cb5dbfe591d Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 11 Dec 2024 16:10:27 +0200 Subject: [PATCH 3/9] feat: consider user location in transfer alerts --- .../itinerary/navigator/NaviCardContainer.js | 10 +++++- .../itinerary/navigator/NaviUtils.js | 33 ++++++++++++++----- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/app/component/itinerary/navigator/NaviCardContainer.js b/app/component/itinerary/navigator/NaviCardContainer.js index 619130f5d7..ebc486fe94 100644 --- a/app/component/itinerary/navigator/NaviCardContainer.js +++ b/app/component/itinerary/navigator/NaviCardContainer.js @@ -82,7 +82,15 @@ function NaviCardContainer( // Alerts for NaviStack addMessages( incomingMessages, - getItineraryAlerts(legs, time, intl, messages, match.params, router), + getItineraryAlerts( + legs, + time, + position, + intl, + messages, + match.params, + router, + ), ); if (currentLeg) { diff --git a/app/component/itinerary/navigator/NaviUtils.js b/app/component/itinerary/navigator/NaviUtils.js index c98fc83752..4e115cddde 100644 --- a/app/component/itinerary/navigator/NaviUtils.js +++ b/app/component/itinerary/navigator/NaviUtils.js @@ -1,4 +1,5 @@ import React from 'react'; +import distance from '@digitransit-search-util/digitransit-search-util-distance'; import { FormattedMessage } from 'react-intl'; import { legTime } from '../../../util/legUtils'; import { timeStr } from '../../../util/timeUtils'; @@ -8,7 +9,10 @@ import { getItineraryPagePath } from '../../../util/path'; const TRANSFER_SLACK = 60000; const DISPLAY_MESSAGE_THRESHOLD = 120 * 1000; // 2 minutes -function findTransferProblem(legs, time) { + +export const DESTINATION_RADIUS = 20; // meters + +function findTransferProblem(legs, time, position) { for (let i = 1; i < legs.length - 1; i++) { const prev = legs[i - 1]; const leg = legs[i]; @@ -28,9 +32,19 @@ function findTransferProblem(legs, time) { const t2 = legTime(next.start); if (t2 > time) { // transfer is not over yet - const transferDuration = legTime(leg.end) - legTime(leg.start); - const slack = t2 - t1 - transferDuration; - if (slack < TRANSFER_SLACK) { + let failed; + if (t1 > t2) { + // certain failure + failed = true; + } else { + const transferDuration = legTime(leg.end) - legTime(leg.start); + const slack = t2 - t1 - transferDuration; + // check if user is already at the next departure stop + const atStop = + position && distance(position, leg.to) <= DESTINATION_RADIUS; + failed = !atStop && slack < TRANSFER_SLACK; + } + if (failed) { return [prev, next]; } } @@ -151,6 +165,7 @@ export const getTransitLegState = (leg, intl, messages, time) => { export const getItineraryAlerts = ( legs, time, + position, intl, messages, location, @@ -191,7 +206,7 @@ export const getItineraryAlerts = ( id: alert.id, })); }); - const transferProblem = findTransferProblem(legs, time); + const transferProblem = findTransferProblem(legs, time, position); const abortTrip = ; const withShowRoutesBtn = children => (
@@ -378,7 +393,7 @@ export function pathProgress(pos, geom) { const lengths = []; let p1 = geom[0]; - let distance = dist(pos, p1); + let dst = dist(pos, p1); let minI = 0; let minF = 0; let totalLength = 0; @@ -405,8 +420,8 @@ export function pathProgress(pos, geom) { f = dp / d; // normalize cDist = Math.sqrt(dlt.x * dlt.x + dlt.y * dlt.y - f * f); // pythag. } - if (cDist < distance) { - distance = cDist; + if (cDist < dst) { + dst = cDist; minI = i; minF = f; } @@ -426,5 +441,5 @@ export function pathProgress(pos, geom) { y: geom[minI].y + minF * dy, }; - return { projected, distance, traversed }; + return { projected, distance: dst, traversed }; } From 553a84f9d10dc62718da85c20ca80f0fd6502404 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 11 Dec 2024 16:14:09 +0200 Subject: [PATCH 4/9] fix: shared value for radius checks --- app/component/itinerary/navigator/NaviCardContainer.js | 2 +- app/component/itinerary/navigator/NaviContainer.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/component/itinerary/navigator/NaviCardContainer.js b/app/component/itinerary/navigator/NaviCardContainer.js index ebc486fe94..c2ac059209 100644 --- a/app/component/itinerary/navigator/NaviCardContainer.js +++ b/app/component/itinerary/navigator/NaviCardContainer.js @@ -12,9 +12,9 @@ import { getItineraryAlerts, getTransitLegState, LEGTYPE, + DESTINATION_RADIUS, } from './NaviUtils'; -const DESTINATION_RADIUS = 20; // meters const TIME_AT_DESTINATION = 3; // * 10 seconds const TOPBAR_PADDING = 8; // pixels diff --git a/app/component/itinerary/navigator/NaviContainer.js b/app/component/itinerary/navigator/NaviContainer.js index ba4551cf40..42174e8661 100644 --- a/app/component/itinerary/navigator/NaviContainer.js +++ b/app/component/itinerary/navigator/NaviContainer.js @@ -9,8 +9,8 @@ import NaviBottom from './NaviBottom'; import NaviCardContainer from './NaviCardContainer'; import { useRealtimeLegs } from './hooks/useRealtimeLegs'; import NavigatorOutroModal from './navigatoroutro/NavigatorOutroModal'; +import { DESTINATION_RADIUS } from './NaviUtils'; -const DESTINATION_RADIUS = 20; // meters const ADDITIONAL_ARRIVAL_TIME = 60000; // 60 seconds in ms function NaviContainer( From b35168debbf000ccb5fa8be8af6422b4637e26e0 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 11 Dec 2024 16:36:58 +0200 Subject: [PATCH 5/9] fix: wrong proptypes --- app/component/itinerary/navigator/NaviCardContainer.js | 2 +- app/component/itinerary/navigator/NaviContainer.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/component/itinerary/navigator/NaviCardContainer.js b/app/component/itinerary/navigator/NaviCardContainer.js index c2ac059209..5975c2aa84 100644 --- a/app/component/itinerary/navigator/NaviCardContainer.js +++ b/app/component/itinerary/navigator/NaviCardContainer.js @@ -218,7 +218,7 @@ NaviCardContainer.propTypes = { lat: PropTypes.number, lon: PropTypes.number, }), - mapLayerRef: PropTypes.func.isRequired, + mapLayerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), origin: PropTypes.shape({ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired, diff --git a/app/component/itinerary/navigator/NaviContainer.js b/app/component/itinerary/navigator/NaviContainer.js index 42174e8661..a63fb81e31 100644 --- a/app/component/itinerary/navigator/NaviContainer.js +++ b/app/component/itinerary/navigator/NaviContainer.js @@ -110,7 +110,7 @@ NaviContainer.propTypes = { isNavigatorIntroDismissed: PropTypes.bool, // eslint-disable-next-line mapRef: PropTypes.object, - mapLayerRef: PropTypes.func.isRequired, + mapLayerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), }; NaviContainer.contextTypes = { From 063c57d6ac59d66dd65228377af0b891611dad5b Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Wed, 11 Dec 2024 16:49:31 +0200 Subject: [PATCH 6/9] fix: render position based remaining distance and time in green --- app/component/itinerary/navigator/NaviInstructions.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/component/itinerary/navigator/NaviInstructions.js b/app/component/itinerary/navigator/NaviInstructions.js index adbb6a5291..eda269f8c9 100644 --- a/app/component/itinerary/navigator/NaviInstructions.js +++ b/app/component/itinerary/navigator/NaviInstructions.js @@ -19,12 +19,13 @@ export default function NaviInstructions( if (legType === LEGTYPE.MOVE) { let remainingTraversal; - + let rtDuration; if (position) { // TODO: maybe apply only when distance is close enough to the path const posXY = GeodeticToEnu(position.lat, position.lon, origin); const { traversed } = pathProgress(posXY, leg.geometry); remainingTraversal = 1.0 - traversed; + rtDuration = true; } else { // estimate from elapsed time remainingTraversal = Math.max( @@ -43,7 +44,7 @@ export default function NaviInstructions( {legDestination(intl, leg, null, nextLeg)}
-
+
{displayDistance(distance, config, intl.formatNumber)} ( {durationToString(duration * 1000)})
From aa2adbf775a9d2f1fad286506eb448e2bebab928 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 12 Dec 2024 08:49:27 +0200 Subject: [PATCH 7/9] feat: classify severity of transfer problems --- .../itinerary/navigator/NaviCardContainer.js | 4 +- .../itinerary/navigator/NaviContainer.js | 3 +- .../itinerary/navigator/NaviInstructions.js | 27 +-- .../itinerary/navigator/NaviUtils.js | 217 +++++++++++------- 4 files changed, 144 insertions(+), 107 deletions(-) diff --git a/app/component/itinerary/navigator/NaviCardContainer.js b/app/component/itinerary/navigator/NaviCardContainer.js index 5975c2aa84..a835a4c226 100644 --- a/app/component/itinerary/navigator/NaviCardContainer.js +++ b/app/component/itinerary/navigator/NaviCardContainer.js @@ -86,6 +86,7 @@ function NaviCardContainer( legs, time, position, + origin, intl, messages, match.params, @@ -218,7 +219,8 @@ NaviCardContainer.propTypes = { lat: PropTypes.number, lon: PropTypes.number, }), - mapLayerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + mapLayerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]) + .isRequired, origin: PropTypes.shape({ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired, diff --git a/app/component/itinerary/navigator/NaviContainer.js b/app/component/itinerary/navigator/NaviContainer.js index a63fb81e31..505916eb41 100644 --- a/app/component/itinerary/navigator/NaviContainer.js +++ b/app/component/itinerary/navigator/NaviContainer.js @@ -110,7 +110,8 @@ NaviContainer.propTypes = { isNavigatorIntroDismissed: PropTypes.bool, // eslint-disable-next-line mapRef: PropTypes.object, - mapLayerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + mapLayerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]) + .isRequired, }; NaviContainer.contextTypes = { diff --git a/app/component/itinerary/navigator/NaviInstructions.js b/app/component/itinerary/navigator/NaviInstructions.js index eda269f8c9..7af09f1909 100644 --- a/app/component/itinerary/navigator/NaviInstructions.js +++ b/app/component/itinerary/navigator/NaviInstructions.js @@ -2,11 +2,11 @@ import React from 'react'; import { FormattedMessage, intlShape } from 'react-intl'; import PropTypes from 'prop-types'; import cx from 'classnames'; -import { GeodeticToEnu, displayDistance } from '../../../util/geo-utils'; +import { displayDistance } from '../../../util/geo-utils'; import { legShape, configShape } from '../../../util/shapes'; import { legDestination, legTimeStr, legTime } from '../../../util/legUtils'; import RouteNumber from '../../RouteNumber'; -import { LEGTYPE, getLocalizedMode, pathProgress } from './NaviUtils'; +import { LEGTYPE, getLocalizedMode, getRemainingTraversal } from './NaviUtils'; import { durationToString } from '../../../util/timeUtils'; export default function NaviInstructions( @@ -18,21 +18,12 @@ export default function NaviInstructions( ); if (legType === LEGTYPE.MOVE) { - let remainingTraversal; - let rtDuration; - if (position) { - // TODO: maybe apply only when distance is close enough to the path - const posXY = GeodeticToEnu(position.lat, position.lon, origin); - const { traversed } = pathProgress(posXY, leg.geometry); - remainingTraversal = 1.0 - traversed; - rtDuration = true; - } else { - // estimate from elapsed time - remainingTraversal = Math.max( - (legTime(leg.end) - time) / (leg.duration * 1000), - 0, - ); - } + const remainingTraversal = getRemainingTraversal( + leg, + position, + origin, + time, + ); const duration = leg.duration * remainingTraversal; const distance = leg.distance * remainingTraversal; @@ -44,7 +35,7 @@ export default function NaviInstructions( {legDestination(intl, leg, null, nextLeg)}
-
+
{displayDistance(distance, config, intl.formatNumber)} ( {durationToString(duration * 1000)})
diff --git a/app/component/itinerary/navigator/NaviUtils.js b/app/component/itinerary/navigator/NaviUtils.js index 4e115cddde..e0a3a54e90 100644 --- a/app/component/itinerary/navigator/NaviUtils.js +++ b/app/component/itinerary/navigator/NaviUtils.js @@ -1,6 +1,7 @@ import React from 'react'; import distance from '@digitransit-search-util/digitransit-search-util-distance'; import { FormattedMessage } from 'react-intl'; +import { GeodeticToEnu } from '../../../util/geo-utils'; import { legTime } from '../../../util/legUtils'; import { timeStr } from '../../../util/timeUtils'; import { getFaresFromLegs } from '../../../util/fareUtils'; @@ -12,7 +13,90 @@ const DISPLAY_MESSAGE_THRESHOLD = 120 * 1000; // 2 minutes export const DESTINATION_RADIUS = 20; // meters -function findTransferProblem(legs, time, position) { +function dist(p1, p2) { + const dx = p2.x - p1.x; + const dy = p2.y - p1.y; + return Math.sqrt(dx * dx + dy * dy); +} + +function vSub(p1, p2) { + const dx = p1.x - p2.x; + const dy = p1.y - p2.y; + return { dx, dy }; +} + +// compute how big part of a path has been traversed +// returns position's projection to path, distance from path +// and the ratio traversed/full length +export function pathProgress(pos, geom) { + const lengths = []; + + let p1 = geom[0]; + let dst = dist(pos, p1); + let minI = 0; + let minF = 0; + let totalLength = 0; + + for (let i = 0; i < geom.length - 1; i++) { + const p2 = geom[i + 1]; + const { dx, dy } = vSub(p2, p1); + const d = Math.sqrt(dx * dx + dy * dy); + lengths.push(d); + totalLength += d; + + if (d > 0.001) { + // interval distance in meters, safety check + const dlt = vSub(pos, p1); + const dp = dlt.dx * dx + dlt.dy * dy; // dot prod + + if (dp > 0) { + let f; + let cDist; + if (dp > 1) { + cDist = dist(p2, pos); + f = 1; + } else { + f = dp / d; // normalize + cDist = Math.sqrt(dlt.x * dlt.x + dlt.y * dlt.y - f * f); // pythag. + } + if (cDist < dst) { + dst = cDist; + minI = i; + minF = f; + } + } + } + p1 = p2; + } + + let traversed = minF * lengths[minI]; // last partial segment + for (let i = 0; i < minI; i++) { + traversed += lengths[i]; + } + traversed /= totalLength; + const { dx, dy } = vSub(geom[minI + 1], geom[minI]); + const projected = { + x: geom[minI].x + minF * dx, + y: geom[minI].y + minF * dy, + }; + + return { projected, distance: dst, traversed }; +} + +export function getRemainingTraversal(leg, pos, origin, time) { + if (pos) { + // TODO: maybe apply only when distance is close enough to the path + const posXY = GeodeticToEnu(pos.lat, pos.lon, origin); + const { traversed } = pathProgress(posXY, leg.geometry); + return 1.0 - traversed; + } + // estimate from elapsed time + return Math.max((legTime(leg.end) - time) / (leg.duration * 1000), 0); +} + +function findTransferProblems(legs, time, position, origin) { + const problems = []; + for (let i = 1; i < legs.length - 1; i++) { const prev = legs[i - 1]; const leg = legs[i]; @@ -21,8 +105,13 @@ function findTransferProblem(legs, time, position) { if (prev.transitLeg && leg.transitLeg && !leg.interlineWithPreviousLeg) { // transfer at a stop const start = legTime(leg.start); - if (start > time && start - legTime(prev.end) < TRANSFER_SLACK) { - return [prev, leg]; + const end = legTime(prev.end); + if (start > time && start - end < TRANSFER_SLACK) { + problems.push({ + severity: start > end ? 'ALERT' : 'WARNING', + fromLeg: prev, + toLeg: leg, + }); } } @@ -32,25 +121,43 @@ function findTransferProblem(legs, time, position) { const t2 = legTime(next.start); if (t2 > time) { // transfer is not over yet - let failed; if (t1 > t2) { - // certain failure - failed = true; + // certain failure, next transit departs before previous arrives + problems.push({ + severity: 'ALERT', + fromLeg: prev, + toLeg: next, + }); } else { const transferDuration = legTime(leg.end) - legTime(leg.start); - const slack = t2 - t1 - transferDuration; // check if user is already at the next departure stop const atStop = position && distance(position, leg.to) <= DESTINATION_RADIUS; - failed = !atStop && slack < TRANSFER_SLACK; - } - if (failed) { - return [prev, next]; + const slack = t2 - t1 - transferDuration; + if (!atStop && slack < TRANSFER_SLACK) { + // original transfer not possible + let severity = 'WARNING'; + const toGo = getRemainingTraversal(leg, position, origin, time); + const timeLeft = (t2 - time) / 1000; + if (toGo > 0 && timeLeft > 0) { + const originalSpeed = leg.distance / leg.duration; + const newSpeed = (toGo * leg.distance) / timeLeft; + if (newSpeed > 2 * originalSpeed) { + // double speed compared to user's routing preference + severity = 'ALERT'; + } + } + problems.push({ + severity, + fromLeg: prev, + toLeg: next, + }); + } } } } } - return null; + return problems; } export const getLocalizedMode = (mode, intl) => { return intl.formatMessage({ @@ -166,6 +273,7 @@ export const getItineraryAlerts = ( legs, time, position, + origin, intl, messages, location, @@ -206,7 +314,6 @@ export const getItineraryAlerts = ( id: alert.id, })); }); - const transferProblem = findTransferProblem(legs, time, position); const abortTrip = ; const withShowRoutesBtn = children => (
@@ -256,16 +363,22 @@ export const getItineraryAlerts = ( }); } - if (transferProblem !== null) { - const transferId = `transfer-${transferProblem[0].legId}-${transferProblem[1].legId}}`; + const transferProblems = findTransferProblems(legs, time, position, origin); + if (transferProblems.length) { + let prob = transferProblems.find(p => p.severity === 'ALERT'); + if (!prob) { + // just take first + [prob] = transferProblems; + } + const transferId = `transfer-${prob.fromLeg.legId}-${prob.toLeg.legId}}`; if (!messages.get(transferId)) { content = withShowRoutesBtn(
{abortTrip} @@ -373,73 +486,3 @@ export const LEGTYPE = { PENDING: 'PENDING', END: 'END', }; - -function dist(p1, p2) { - const dx = p2.x - p1.x; - const dy = p2.y - p1.y; - return Math.sqrt(dx * dx + dy * dy); -} - -function vSub(p1, p2) { - const dx = p1.x - p2.x; - const dy = p1.y - p2.y; - return { dx, dy }; -} - -// compute how big part of a path has been traversed -// returns position's projection to path, distance from path -// and the ratio traversed/full length -export function pathProgress(pos, geom) { - const lengths = []; - - let p1 = geom[0]; - let dst = dist(pos, p1); - let minI = 0; - let minF = 0; - let totalLength = 0; - - for (let i = 0; i < geom.length - 1; i++) { - const p2 = geom[i + 1]; - const { dx, dy } = vSub(p2, p1); - const d = Math.sqrt(dx * dx + dy * dy); - lengths.push(d); - totalLength += d; - - if (d > 0.001) { - // interval distance in meters, safety check - const dlt = vSub(pos, p1); - const dp = dlt.dx * dx + dlt.dy * dy; // dot prod - - if (dp > 0) { - let f; - let cDist; - if (dp > 1) { - cDist = dist(p2, pos); - f = 1; - } else { - f = dp / d; // normalize - cDist = Math.sqrt(dlt.x * dlt.x + dlt.y * dlt.y - f * f); // pythag. - } - if (cDist < dst) { - dst = cDist; - minI = i; - minF = f; - } - } - } - p1 = p2; - } - - let traversed = minF * lengths[minI]; // last partial segment - for (let i = 0; i < minI; i++) { - traversed += lengths[i]; - } - traversed /= totalLength; - const { dx, dy } = vSub(geom[minI + 1], geom[minI]); - const projected = { - x: geom[minI].x + minF * dx, - y: geom[minI].y + minF * dy, - }; - - return { projected, distance: dst, traversed }; -} From d4873afe557d9b32a767d2f168584cfa815d74b5 Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 12 Dec 2024 15:10:27 +0200 Subject: [PATCH 8/9] feat: consider transfer alert severity change --- app/component/itinerary/navigator/NaviUtils.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/component/itinerary/navigator/NaviUtils.js b/app/component/itinerary/navigator/NaviUtils.js index e0a3a54e90..a8d970074f 100644 --- a/app/component/itinerary/navigator/NaviUtils.js +++ b/app/component/itinerary/navigator/NaviUtils.js @@ -371,7 +371,8 @@ export const getItineraryAlerts = ( [prob] = transferProblems; } const transferId = `transfer-${prob.fromLeg.legId}-${prob.toLeg.legId}}`; - if (!messages.get(transferId)) { + const alert = messages.get(transferId); + if (!alert || alert.severity !== prob.severity) { content = withShowRoutesBtn(
, ); alerts.push({ - severity: 'ALERT', + severity: prob.severity, content, id: transferId, - hideClose: true, + hideClose: prob.severity === 'ALERT', }); } } From b7a670c60955b6057878bd5acc4b211dab00131b Mon Sep 17 00:00:00 2001 From: Vesa Meskanen Date: Thu, 12 Dec 2024 15:30:13 +0200 Subject: [PATCH 9/9] fix: replace old message with updated version, not the other way around --- .../itinerary/navigator/NaviCardContainer.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/app/component/itinerary/navigator/NaviCardContainer.js b/app/component/itinerary/navigator/NaviCardContainer.js index a835a4c226..b680a0c72c 100644 --- a/app/component/itinerary/navigator/NaviCardContainer.js +++ b/app/component/itinerary/navigator/NaviCardContainer.js @@ -116,16 +116,11 @@ function NaviCardContainer( : activeMessages; // handle messages that are updated. - const updatedMessages = previousValidMessages.map(msg => { - const incoming = incomingMessages.get(msg.id); - if (incoming) { - incomingMessages.delete(msg.id); - return incoming; - } - return msg; - }); + const keptMessages = previousValidMessages.filter( + msg => !!incomingMessages.get(msg.id), + ); const newMessages = Array.from(incomingMessages.values()); - setActiveMessages([...updatedMessages, ...newMessages]); + setActiveMessages([...keptMessages, ...newMessages]); setMessages(new Map([...messages, ...incomingMessages])); }