From 7c0dd3ee4ea2d0c8261534c6a128d0ddc038938f Mon Sep 17 00:00:00 2001 From: Martin Asprusten Date: Wed, 2 Jul 2025 14:16:58 +0200 Subject: [PATCH] Break out of infinite loops when calculating required speed, add waypoints to gpx that show when you have to start accelerating --- native/route_search.cpp | 18 +++++++-- src/modules/gpx/exporter.ts | 80 +++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 3 deletions(-) diff --git a/native/route_search.cpp b/native/route_search.cpp index 75780f5..d37ac92 100644 --- a/native/route_search.cpp +++ b/native/route_search.cpp @@ -473,6 +473,11 @@ float calculateRequiredSpeed(float endSpeed, float horizontalDistance, float hei float numerator = 0; float denominator = 0.5; float foundEndSpeed = -100.0; + int loopCounter = 0; + + float closestFoundSpeed = 1e99; + float bestNumerator = 1; + float bestDenominator = 1; do { numerator *= 2; denominator *= 2; @@ -482,9 +487,16 @@ float calculateRequiredSpeed(float endSpeed, float horizontalDistance, float hei numerator += 1; } foundEndSpeed = calculate_speed(maximumSpeed * numerator / denominator, horizontalDistance, heightDifference, 0.01, 100.0, dragCoefficient); - } while (fabs(foundEndSpeed - endSpeed) > 0.013); + loopCounter++; - float requiredSpeed = maximumSpeed * numerator / denominator; + if (fabs(foundEndSpeed - endSpeed) < closestFoundSpeed) { + closestFoundSpeed = foundEndSpeed; + bestNumerator = numerator; + bestDenominator = denominator; + } + } while (fabs(foundEndSpeed - endSpeed) > 0.013 && loopCounter < 32); + + float requiredSpeed = maximumSpeed * bestNumerator / bestDenominator; return requiredSpeed; } @@ -546,7 +558,7 @@ std::vector getPathJS(uint32_t startingNode, uint32_t endNode, float Connection neighbour = neighbours[i]; if (neighbour.connected_point_number == nextNodeId) { float horizontalDistance = neighbour.distance; - currentRequiredSpeed = calculateRequiredSpeed(currentRequiredSpeed, horizontalDistance, heightDifference, dragCoefficient); + currentRequiredSpeed = fmax(calculateRequiredSpeed(currentRequiredSpeed, horizontalDistance, heightDifference, dragCoefficient), minimumSpeed); it->requiredSpeed = currentRequiredSpeed; break; } diff --git a/src/modules/gpx/exporter.ts b/src/modules/gpx/exporter.ts index e6b059b..3de77cb 100644 --- a/src/modules/gpx/exporter.ts +++ b/src/modules/gpx/exporter.ts @@ -1,5 +1,12 @@ import type { Coordinate, PathSegment } from "../../interfaces"; +interface HighspeedSegment { + startingSegmentNumber: number, + segmentLength: number, + highestSpeed: number, + highestSpeedSegmentNumber: number +} + function createAndAppendChild(document: XMLDocument, parent: HTMLElement, nameOfChild: string, contents?: string): HTMLElement { let childElement = document.createElement(nameOfChild); parent.appendChild(childElement); @@ -16,8 +23,22 @@ function createRoutePoint(document: XMLDocument, parent: HTMLElement, coordinate createAndAppendChild(document, routePoint, 'desc', 'Required speed: ' + (requiredSpeed * 3.6).toFixed(1) + ' km/h'); } +function createWaypoint(document: XMLDocument, parent: HTMLElement, name: string, coordinate: Coordinate, color: string): void { + let waypoint = createAndAppendChild(document, parent, 'wpt'); + waypoint.setAttribute('lat', coordinate.latitude.toFixed(6)); + waypoint.setAttribute('lon', coordinate.longitude.toFixed(6)); + createAndAppendChild(document, waypoint, 'name', name); + let extensions = createAndAppendChild(document, waypoint, 'extensions'); + let colorTag = document.createElementNS('https://osmand.net/docs/technical/osmand-file-formats/osmand-gpx', 'color'); + extensions.appendChild(colorTag); + colorTag.appendChild(document.createTextNode(color)); +} + export function downloadGpxFile(path: PathSegment[]): void { let gpxDocument = document.implementation.createDocument('http://www.topografix.com/GPX/1/1', 'gpx'); + gpxDocument.documentElement.setAttribute('version', '1.1'); + gpxDocument.documentElement.setAttribute('creator', 'https://freewheeling.martinserver.no'); + let routeElement = createAndAppendChild(gpxDocument, gpxDocument.documentElement, 'rte'); createAndAppendChild(gpxDocument, routeElement, 'name', 'Freewheeling route'); @@ -28,10 +49,69 @@ export function downloadGpxFile(path: PathSegment[]): void { if (path.length > 0) { createRoutePoint(gpxDocument, routeElement, path.at(0)!.start, path.at(0)!.requiredSpeed); } + // Add remaining route points path.forEach(segment => { createRoutePoint(gpxDocument, routeElement, segment.end, segment.requiredSpeed); }); + // Add waypoints where you need to start accelerating, where you reach top speed, and where you can start rolling normally again + var minimumSpeed: number = Math.min(...path.map(segment => segment.requiredSpeed)); + var highspeedSegments: HighspeedSegment[] = []; + var currentSegment: HighspeedSegment | undefined = undefined; + + for (var i = 0; i < path.length; i++) { + var segment = path[i]; + if (segment.requiredSpeed - minimumSpeed > 0.01) { + if (currentSegment == null) { + currentSegment = { + startingSegmentNumber: i, + segmentLength: 1, + highestSpeed: -1, + highestSpeedSegmentNumber: -1 + } + } else { + currentSegment.segmentLength += 1; + } + } else { + if (currentSegment != null) { + highspeedSegments.push(currentSegment); + currentSegment = undefined; + } + } + } + + if (currentSegment != null) { + highspeedSegments.push(currentSegment); + } + + // Find the highest speed inside each high speed segment + highspeedSegments.forEach(highspeedSegment => { + var highestSpeed = 0; + var highestSpeedSegmentNumber = 0; + for (var i = highspeedSegment.startingSegmentNumber; i < highspeedSegment.startingSegmentNumber + highspeedSegment.segmentLength; i++) { + if (path[i].requiredSpeed > highestSpeed) { + highestSpeed = path[i].requiredSpeed; + highestSpeedSegmentNumber = i; + } + } + highspeedSegment.highestSpeed = highestSpeed * 3.6; + highspeedSegment.highestSpeedSegmentNumber = highestSpeedSegmentNumber; + }); + + // We probably want to drop the segments where we're not at least doubling the minimum speed, and we want them to be at least three or four long + highspeedSegments = highspeedSegments.filter(segment => segment.highestSpeed >= 2.0 * minimumSpeed && segment.segmentLength > 4); + + // Add these segments to the gpx + highspeedSegments.forEach(highspeedSegment => { + let firstSegment = path[highspeedSegment.startingSegmentNumber]; + let lastSegment = path[highspeedSegment.startingSegmentNumber + highspeedSegment.segmentLength - 1]; + let fastestSegment = path[highspeedSegment.highestSpeedSegmentNumber]; + + createWaypoint(gpxDocument, gpxDocument.documentElement, 'Accelerate to ' + highspeedSegment.highestSpeed.toFixed(1) + ' km/h', firstSegment.start, '#00FF00'); + createWaypoint(gpxDocument, gpxDocument.documentElement, 'Reach ' + highspeedSegment.highestSpeed.toFixed(1) + ' km/h', fastestSegment.start, '#FFFF00'); + createWaypoint(gpxDocument, gpxDocument.documentElement, 'High speed ends', lastSegment.start, '#FF0000'); + }); + var element = document.createElement('a'); element.setAttribute('href', 'data:application/gpx+xml;base64,' + btoa('' + new XMLSerializer().serializeToString(gpxDocument))); element.setAttribute('download', 'freewheeling-route.gpx');