Skip to content

Commit

Permalink
fix: improve segmentsOnRoute
Browse files Browse the repository at this point in the history
  • Loading branch information
andipaetzold committed Dec 10, 2021
1 parent 0f60949 commit 096aa7e
Show file tree
Hide file tree
Showing 9 changed files with 689 additions and 495 deletions.
9 changes: 5 additions & 4 deletions data/routes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ export const routes = [
{
id: 3114603308,
slug: "2018-uci-worlds-course-short-lap",
segments: ["innsbruck-kom", "innsbruck-sprint"],
segments: ["innsbruck-kom", "innsbruck-sprint", "innsbruck-uci-lap"],
experience: 470,
stravaSegmentId: 18397725,
zwiftInsiderUrl:
Expand Down Expand Up @@ -987,7 +987,7 @@ export const routes = [
{
id: 2875658892,
slug: "bell-lap",
segments: [],
segments: ["crit-city-rev", "prime-rev"],
experience: null,
stravaSegmentId: 22419554,
zwiftInsiderUrl: "https://zwiftinsider.com/route/bell-lap",
Expand Down Expand Up @@ -1027,12 +1027,13 @@ export const routes = [
{
id: 947394567,
slug: "downtown-dolphin",
segments: ["crit-city", "prime"],
segments: ["crit-city-rev", "prime-rev"],
experience: null,
stravaSegmentId: 22445564,
zwiftInsiderUrl: "https://zwiftinsider.com/route/downtown-dolphin",
whatsOnZwiftUrl:
"https://whatsonzwift.com/world/crit-city/route/downtown-dolphin",
invalidSegments: ["crit-city"],
},
{
id: 3811569265,
Expand Down Expand Up @@ -1127,7 +1128,7 @@ export const routes = [
{
id: 107363867,
slug: "hilly-route-rev",
segments: ["hilly-loop", "watopia-sprint-rev", "zwift-kom-rev"],
segments: ["hilly-loop-rev", "watopia-sprint-rev", "zwift-kom-rev"],
experience: null,
stravaSegmentId: 12128037,
zwiftInsiderUrl: "https://zwiftinsider.com/route/hilly-route-reverse",
Expand Down
8 changes: 4 additions & 4 deletions data/segments.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,8 @@ export const segments = [
distance: 7.8,
whatsOnZwiftUrl:
"https://whatsonzwift.com/world/watopia/segment/jungle-loop/forward",
stravaSegmentUrl: "https://www.strava.com/segments/16359363",
stravaSegmentId: 16359363,
stravaSegmentId: 16359371,
stravaSegmentUrl: "https://www.strava.com/segments/16359371",
},
{
name: "Jungle Loop Rev.",
Expand All @@ -318,8 +318,8 @@ export const segments = [
distance: 7.8,
whatsOnZwiftUrl:
"https://whatsonzwift.com/world/watopia/segment/jungle-loop/reverse",
stravaSegmentId: 16359371,
stravaSegmentUrl: "https://www.strava.com/segments/16359371",
stravaSegmentId: 16359363,
stravaSegmentUrl: "https://www.strava.com/segments/16359363",
},
{
name: "Titans Grove KOM",
Expand Down
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@
"@tsconfig/recommended": "1.0.1",
"@turf/turf": "6.5.0",
"@types/jest": "26.0.23",
"@types/lodash": "4.14.178",
"@types/node": "15.6.1",
"jest": "27.0.3",
"lodash": "4.17.21",
"node-fetch": "3.1.0",
"rimraf": "3.0.2",
"semantic-release": "17.4.3",
Expand Down
3 changes: 2 additions & 1 deletion scripts/helpers/fetch-segments.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ export async function fetchSegments() {
.map(async (segment) => {
console.log(`Loading segment ${segment.name}`);

const url = `https://www.strava.com/stream/segments/${segment.stravaSegmentId}?streams%5B%5D=latlng`;
const url = `https://www.strava.com/stream/segments/${segment.stravaSegmentId}?streams%5B%5D=latlng&streams%5B%5D=distance`;
const response = await fetch(url);
const data = await response.json();
return {
...segment,
latlng: data.latlng,
distanceStream: data.distance,
};
})
);
Expand Down
168 changes: 100 additions & 68 deletions scripts/helpers/find-segments-on-route.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as turf from "@turf/turf";
import fetch from "node-fetch";
import range from "lodash/range.js";

const TOLERANCE = 15;
const TOLERANCE = 5;
const OPTIONS = { units: "meters" };

export async function findSegmentsOnRoute(route, segments) {
Expand All @@ -15,7 +16,10 @@ export async function findSegmentsOnRoute(route, segments) {

const segmentsOnRoute = segments
.filter((s) => !(route.invalidSegments ?? []).includes(s.slug))
.flatMap((segment) => findSegmentOnRoute(routeLatLng, segment));
.flatMap((segment) =>
findSegmentOnRoute(routeLatLng, routeDistance, segment)
);

return segmentsOnRoute
.sort((a, b) => a.from - b.from)
.map(({ segment, from, to }) => ({
Expand All @@ -25,88 +29,116 @@ export async function findSegmentsOnRoute(route, segments) {
}));
}

function findSegmentOnRoute(routeLatLng, segment) {
const segmentLatLng = segment.latlng;
function findSegmentOnRoute(routeLatLng, routeDistanceStream, segment) {
const segmentDistanceInMeters = segment.distance * 1_000;

const overlap = [];
for (
let startPointIndex = 0;
startPointIndex < routeLatLng.length - 3;
++startPointIndex
) {
if (
turf.pointToLineDistance(
turf.point(segmentLatLng[0]),
turf.lineString(
routeLatLng.slice(startPointIndex, startPointIndex + 2)
),
OPTIONS
lineDistance(
segment.latlng.slice(0, 2),
routeLatLng.slice(startPointIndex, startPointIndex + 2)
) > TOLERANCE
) {
continue;
}

const overlapLength = doesRouteStartWithSegment(
routeLatLng.slice(startPointIndex),
segmentLatLng
);
if (overlapLength !== undefined) {
overlap.push({
from: startPointIndex,
to: startPointIndex + overlapLength,
segment: segment.slug,
});
startPointIndex += overlapLength;
const routeDistanceStart = routeDistanceStream[startPointIndex];

const newOverlaps = range(startPointIndex + 1, routeLatLng.length - 2)
.filter(
// check end
(endPointIndex) =>
lineDistance(
segment.latlng.slice(
segment.latlng.length - 3,
segment.latlng.length - 1
),
routeLatLng.slice(endPointIndex, endPointIndex + 2)
) <= TOLERANCE
)
.filter(
// check distance
(endPointIndex) =>
Math.abs(
segmentDistanceInMeters -
(routeDistanceStream[endPointIndex] - routeDistanceStart)
) <=
0.1 * segmentDistanceInMeters
)
.filter(
// check 20%
(endPointIndex) => {
if (startPointIndex === 384) {
return true;
}
const middlePointIndex =
startPointIndex +
Math.ceil((endPointIndex - startPointIndex) * 0.2);

for (
let segmentIndex = 0;
segmentIndex < segment.latlng.length - 1;
++segmentIndex
) {
if (
lineDistance(
segment.latlng.slice(segmentIndex, segmentIndex + 2),
routeLatLng.slice(middlePointIndex, middlePointIndex + 2)
) > TOLERANCE
) {
continue;
}

if (
Math.abs(
routeDistanceStream[middlePointIndex] -
routeDistanceStart -
segment.distanceStream[segmentIndex]
) >
0.1 * segmentDistanceInMeters
) {
continue;
}

return true;
}

return false;
}
);

if (newOverlaps.length === 0) {
continue;
}
}

return overlap;
}

function doesRouteStartWithSegment(route, segment) {
let routeIndex = 0;
const routeBack = () => route[routeIndex];
const routeFront = () => route[routeIndex + 1];
const routeLine = () => turf.lineString([routeBack(), routeFront()]);

let segmentIndex = 0;
const segmentBack = () => segment[segmentIndex];
const segmentFront = () => segment[segmentIndex + 1];
const segmentLine = () => turf.lineString([segmentBack(), segmentFront()]);

const {
properties: { dist: startDistanceToRoute },
} = turf.nearestPointOnLine(routeLine(), segmentBack(), OPTIONS);
const {
properties: { dist: startDistanceToSegment },
} = turf.nearestPointOnLine(segmentLine(), routeBack(), OPTIONS);

if (Math.min(startDistanceToRoute, startDistanceToSegment) > TOLERANCE) {
return undefined;
}

while (routeIndex < route.length - 1 && segmentIndex < segment.length - 1) {
const {
properties: { dist: distanceToRoute },
} = turf.nearestPointOnLine(routeLine(), segmentFront(), OPTIONS);

const {
properties: { dist: distanceToSegment },
} = turf.nearestPointOnLine(segmentLine(), routeFront(), OPTIONS);

if (Math.min(distanceToRoute, distanceToSegment) > TOLERANCE) {
return undefined;
}
overlap.push({
from: startPointIndex,
to: newOverlaps[0],
segment: segment.slug,
});

if (distanceToRoute < distanceToSegment) {
++segmentIndex;
} else {
++routeIndex;
}
startPointIndex += Math.ceil((newOverlaps[0] - startPointIndex) / 2);
}

if (routeIndex >= route.length - 1) {
return undefined;
}
return overlap;
}

return routeIndex;
function lineDistance(lineA, lineB) {
const distanceA = turf.pointToLineDistance(
turf.point(lineA[0]),
turf.lineString(lineB),
OPTIONS
);
const distanceB = turf.pointToLineDistance(
turf.point(lineB[0]),
turf.lineString(lineA),
OPTIONS
);

return Math.min(distanceA, distanceB);
}
11 changes: 11 additions & 0 deletions src/routes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ describe.each(routes)("$name", (route) => {

expect(segment.world).toBe(route.world);
});

// it("should be in segmentsOnRoute", () => {
// const segment = segments.find((s) => s.slug === segmentSlug)!;
// if (!segment.stravaSegmentId) {
// return;
// }

// expect(
// route.segmentsOnRoute.find((sor) => sor.segment === segment.slug)
// ).toBeDefined();
// });
});
});

Expand Down
Loading

0 comments on commit 096aa7e

Please sign in to comment.