Skip to content

Commit

Permalink
Merge pull request #5182 from HSLdevcom/DT-6558
Browse files Browse the repository at this point in the history
DT-6558 update non-transit leg progress
  • Loading branch information
vesameskanen authored Dec 3, 2024
2 parents e39cfd3 + 3654e21 commit 4344ace
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 44 deletions.
13 changes: 13 additions & 0 deletions app/component/itinerary/navigator/NaviCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export default function NaviCard({
cardExpanded,
startTime,
time,
position,
origin,
}) {
if (legType === LEGTYPE.PENDING) {
return (
Expand Down Expand Up @@ -69,6 +71,8 @@ export default function NaviCard({
instructions={instructions}
legType={legType}
time={time}
position={position}
origin={origin}
/>
</div>
<div type="button" className="navitop-arrow">
Expand Down Expand Up @@ -97,10 +101,19 @@ NaviCard.propTypes = {
cardExpanded: PropTypes.bool,
startTime: PropTypes.string,
time: PropTypes.number.isRequired,
position: PropTypes.shape({
lat: PropTypes.number,
lon: PropTypes.number,
}),
origin: PropTypes.shape({
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
}).isRequired,
};
NaviCard.defaultProps = {
cardExpanded: false,
leg: undefined,
nextLeg: undefined,
startTime: '',
position: undefined,
};
9 changes: 7 additions & 2 deletions app/component/itinerary/navigator/NaviCardContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function addMessages(incominMessages, newMessages) {
});
}
function NaviCardContainer(
{ focusToLeg, time, legs, position },
{ focusToLeg, time, legs, position, origin },
{ intl, config, match, router },
) {
const [currentLeg, setCurrentLeg] = useState(null);
Expand Down Expand Up @@ -186,6 +186,8 @@ function NaviCardContainer(
legType={legType}
startTime={legTimeStr(first.start)}
time={time}
position={position}
origin={origin}
/>
</div>
</button>
Expand All @@ -208,7 +210,10 @@ NaviCardContainer.propTypes = {
lat: PropTypes.number,
lon: PropTypes.number,
}),

origin: PropTypes.shape({
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
}).isRequired,
/*
focusToPoint: PropTypes.func.isRequired,
*/
Expand Down
46 changes: 25 additions & 21 deletions app/component/itinerary/navigator/NaviContainer.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import PropTypes from 'prop-types';
import React from 'react';
import React, { useEffect, useState } from 'react';
import polyUtil from 'polyline-encoded';
import { legTime } from '../../../util/legUtils';
import { GeodeticToEcef, GeodeticToEnu } from '../../../util/geo-utils';
import { itineraryShape, relayShape } from '../../../util/shapes';
import NaviBottom from './NaviBottom';
import NaviCardContainer from './NaviCardContainer';
Expand All @@ -10,34 +12,35 @@ function NaviContainer(
{ itinerary, focusToLeg, relayEnvironment, setNavigation, mapRef },
{ getStore },
) {
const { legs } = itinerary;
const [planarLegs, setPlanarLegs] = useState([]);
const [origin, setOrigin] = useState();

const position = getStore('PositionStore').getLocationState();

useEffect(() => {
const { lat, lon } = itinerary.legs[0].from;
const orig = GeodeticToEcef(lat, lon);
const legs = itinerary.legs.map(leg => {
const geometry = polyUtil.decode(leg.legGeometry.points);
return {
...leg,
geometry: geometry.map(p => GeodeticToEnu(p[0], p[1], orig)),
};
});
setPlanarLegs(legs);
setOrigin(orig);
}, [itinerary]);

const { realTimeLegs, time, isPositioningAllowed } = useRealtimeLegs(
legs,
planarLegs,
mapRef,
relayEnvironment,
);

// recompute estimated arrival
let lastTransitLeg;
let arrivalChange = 0;

legs.forEach(leg => {
if (leg.transitLeg) {
lastTransitLeg = leg;
}
});

if (lastTransitLeg) {
const rtLeg = realTimeLegs.find(leg => {
return leg.legId === lastTransitLeg.legId;
});
arrivalChange = legTime(rtLeg.end) - legTime(lastTransitLeg.end);
if (!realTimeLegs.length) {
return null;
}

const arrivalTime = legTime(legs[legs.length - 1].end) + arrivalChange;

return (
<>
<NaviCardContainer
Expand All @@ -48,10 +51,11 @@ function NaviContainer(
}
time={time}
position={position}
origin={origin}
/>
<NaviBottom
setNavigation={setNavigation}
arrival={arrivalTime}
arrival={legTime(realTimeLegs[realTimeLegs.length - 1].end)}
time={time}
/>
</>
Expand Down
53 changes: 32 additions & 21 deletions app/component/itinerary/navigator/NaviInstructions.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,49 @@
import React, { useEffect, useState } from 'react';
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 { legShape, configShape } from '../../../util/shapes';
import { legDestination, legTimeStr, legTime } from '../../../util/legUtils';
import RouteNumber from '../../RouteNumber';
import { LEGTYPE, getLocalizedMode } from './NaviUtils';
import { displayDistance } from '../../../util/geo-utils';
import { LEGTYPE, getLocalizedMode, pathProgress } from './NaviUtils';
import { durationToString } from '../../../util/timeUtils';

export default function NaviInstructions(
{ leg, nextLeg, instructions, legType, time },
{ leg, nextLeg, instructions, legType, time, position, origin },
{ intl, config },
) {
const [fadeOut, setFadeOut] = useState(false);
const withRealTime = (rt, children) => (
<span className={cx('bold', { realtime: rt })}>{children}</span>
);
useEffect(() => {
const timer = setTimeout(() => {
setFadeOut(true);
}, 10000);
return () => {
setFadeOut(false);
clearTimeout(timer);
};
}, [leg]);

if (legType === LEGTYPE.MOVE) {
const { distance, duration } = leg;
let remainingTraversal;

if (position?.lat && position?.lon) {
// 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;
} else {
// estimate from elapsed time
remainingTraversal = (legTime(leg.end) - time) / (leg.duration * 1000);
}
const duration = leg.duration * remainingTraversal;
const distance = leg.distance * remainingTraversal;

return (
<>
<div className="destination-header">
<FormattedMessage id={instructions} defaultMessage="Go to" />
&nbsp;
{legDestination(intl, leg, null, nextLeg)}
</div>
{distance && duration && (
<div className={cx('duration', fadeOut && 'fade-out')}>
{displayDistance(distance, config, intl.formatNumber)} (
{durationToString(duration * 1000)})
</div>
)}

<div className={cx('duration')}>
{displayDistance(distance, config, intl.formatNumber)} (
{durationToString(duration * 1000)})
</div>
</>
);
}
Expand Down Expand Up @@ -134,12 +136,21 @@ NaviInstructions.propTypes = {
instructions: PropTypes.string.isRequired,
legType: PropTypes.string,
time: PropTypes.number.isRequired,
position: PropTypes.shape({
lat: PropTypes.number,
lon: PropTypes.number,
}),
origin: PropTypes.shape({
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
}).isRequired,
};

NaviInstructions.defaultProps = {
legType: '',
leg: undefined,
nextLeg: undefined,
position: undefined,
};
NaviInstructions.contextTypes = {
intl: intlShape.isRequired,
Expand Down
70 changes: 70 additions & 0 deletions app/component/itinerary/navigator/NaviUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -343,3 +343,73 @@ 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 distance = 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 < distance) {
distance = 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, traversed };
}
51 changes: 51 additions & 0 deletions app/util/geo-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -419,3 +419,54 @@ export function getClosestPoint(a, b, c) {
}
return distA < distB ? a : b;
}

// WGS84 to local planar coordinates
// Usage: origin = GeodeticToEcef(lat0, lon0);
// { x, y } = GeodeticToEnu(lat, lon, origin);

const a = 6378137.0; // WGS-84 Earth semimajor axis (m)
const b = 6356752.314245; // Derived Earth semiminor axis (m)
const f = (a - b) / a; // Ellipsoid Flatness
const e_sq = f * (2 - f); // Square of Eccentricity
const inv_e_sq = 1 - e_sq;

// Converts WGS-84 Geodetic point (lat, lon) at ground level 0 to
// Earth-Centered Earth-Fixed (ECEF) coordinates (x, y, z)
export function GeodeticToEcef(lat, lon) {
const lambda = toRad(lat);
const phi = toRad(lon);
const sin_lambda = Math.sin(lambda);
const cos_lambda = Math.cos(lambda);
const cos_phi = Math.cos(phi);
const sin_phi = Math.sin(phi);
const N = a / Math.sqrt(1 - e_sq * sin_lambda * sin_lambda);

const x = N * cos_lambda * cos_phi;
const y = N * cos_lambda * sin_phi;
const z = N * inv_e_sq * sin_lambda;

return { x, y, z, sin_lambda, cos_lambda, cos_phi, sin_phi };
}

// Converts the earth-centered earth-fixed (ECEF) coordinates (x, y, z) to
// east-north coordinates in a local tangent plane defined by an ECEF point origin
function EcefToEnu(e, origin) {
const xd = e.x - origin.x;
const yd = e.y - origin.y;
const zd = e.z - origin.z;

const xEast = -origin.sin_phi * xd + origin.cos_phi * yd;
const yNorth =
-origin.cos_phi * origin.sin_lambda * xd -
origin.sin_lambda * origin.sin_phi * yd +
origin.cos_lambda * zd;

return { x: xEast, y: yNorth };
}

// Converts the geodetic WGS-84 coordinated (lat, lon) at ground level to
// east-north coordinates in a local tangent plane defined by an ECEF point origin
export function GeodeticToEnu(lat, lon, origin) {
const ecef = GeodeticToEcef(lat, lon);
return EcefToEnu(ecef, origin);
}

0 comments on commit 4344ace

Please sign in to comment.