Calculate and show the required speed at all points of the route

This commit is contained in:
Martin Asprusten 2025-07-01 14:15:47 +02:00
parent f0b4479664
commit 00db764fd0
5 changed files with 76 additions and 10 deletions

View File

@ -54,6 +54,7 @@ struct JSNodeInfo {
float positionZ; float positionZ;
float distanceFromStart; float distanceFromStart;
float currentSpeed; float currentSpeed;
float requiredSpeed;
}; };
struct JSSearchResult { struct JSSearchResult {
@ -459,7 +460,36 @@ JSSearchResult findAllPathsFromPointJS(int startingNode, float minimumSpeed, flo
return searchResult; return searchResult;
} }
std::vector<JSNodeInfo> getPathJS(uint32_t startingNode, uint32_t endNode) { float calculateRequiredSpeed(float endSpeed, float horizontalDistance, float heightDifference, float dragCoefficient) {
// The bike will never reach faster speeds than terminal velocity in free fall
float maximumSpeed = sqrt(GRAVITY_ACCELERATION / dragCoefficient);
// First, check if we'll reach the required speed no matter what
if (calculate_speed(0.00001, horizontalDistance, heightDifference, 0.0, maximumSpeed, dragCoefficient) >= endSpeed) {
return 0.0;
}
// Divide and conquer
float numerator = 0;
float denominator = 0.5;
float foundEndSpeed = -100.0;
do {
numerator *= 2;
denominator *= 2;
if (foundEndSpeed > endSpeed) {
numerator -= 1;
} else {
numerator += 1;
}
foundEndSpeed = calculate_speed(maximumSpeed * numerator / denominator, horizontalDistance, heightDifference, 0.01, 100.0, dragCoefficient);
} while (fabs(foundEndSpeed - endSpeed) > 0.013);
float requiredSpeed = maximumSpeed * numerator / denominator;
return requiredSpeed;
}
std::vector<JSNodeInfo> getPathJS(uint32_t startingNode, uint32_t endNode, float dragCoefficient) {
std::vector<JSNodeInfo> path; std::vector<JSNodeInfo> path;
if (lastSearchResult.startingNode != startingNode) { if (lastSearchResult.startingNode != startingNode) {
@ -496,6 +526,34 @@ std::vector<JSNodeInfo> getPathJS(uint32_t startingNode, uint32_t endNode) {
path.push_back(nodeInfo); path.push_back(nodeInfo);
} }
float currentRequiredSpeed = -1.0;
for (auto it = path.rbegin(); it != path.rend(); it++) {
if (currentRequiredSpeed <= -1.0) {
it->requiredSpeed = 1.0;
currentRequiredSpeed = 1.0;
} else {
uint32_t currentNodeId = it->nodeId;
RoadNode currentNode = set.roadNodes[currentNodeId];
uint32_t nextNodeId = (it - 1)->nodeId;
RoadNode nextNode = set.roadNodes[nextNodeId];
float heightDifference = nextNode.position_z - currentNode.position_z;
Connection neighbours[10];
int numberOfNeighbours = 0;
getNeighbourConnections(currentNode, neighbours, numberOfNeighbours);
for (int i = 0; i < numberOfNeighbours; i++) {
Connection neighbour = neighbours[i];
if (neighbour.connected_point_number == nextNodeId) {
float horizontalDistance = neighbour.distance;
currentRequiredSpeed = calculateRequiredSpeed(currentRequiredSpeed, horizontalDistance, heightDifference, dragCoefficient);
it->requiredSpeed = currentRequiredSpeed;
break;
}
}
}
}
return path; return path;
} }
@ -761,6 +819,7 @@ EMSCRIPTEN_BINDINGS(my_module) {
.property("positionY", &JSNodeInfo::positionY) .property("positionY", &JSNodeInfo::positionY)
.property("positionZ", &JSNodeInfo::positionZ) .property("positionZ", &JSNodeInfo::positionZ)
.property("currentSpeed", &JSNodeInfo::currentSpeed) .property("currentSpeed", &JSNodeInfo::currentSpeed)
.property("requiredSpeed", &JSNodeInfo::requiredSpeed)
.property("distanceFromStart", &JSNodeInfo::distanceFromStart); .property("distanceFromStart", &JSNodeInfo::distanceFromStart);
emscripten::class_<JSSearchResult>("SearchResult") emscripten::class_<JSSearchResult>("SearchResult")

View File

@ -61,7 +61,8 @@ interface FoundPathsFromNode {
interface GetFullPath { interface GetFullPath {
startNodeId: number, startNodeId: number,
endNodeId: number endNodeId: number,
dragCoefficient: number
} }
export interface Coordinate { export interface Coordinate {
@ -72,7 +73,8 @@ export interface Coordinate {
export interface PathSegment { export interface PathSegment {
start: Coordinate, start: Coordinate,
end: Coordinate, end: Coordinate,
speed: number calculatedSpeed: number,
requiredSpeed: number
} }
interface ReturnFullPath { interface ReturnFullPath {

View File

@ -160,7 +160,8 @@ function setUpMapHandler() {
routeWorker.postMessage(message); routeWorker.postMessage(message);
}); });
mapHandler.addClickedEndpointListener((startNodeId, endNodeId) => { mapHandler.addClickedEndpointListener((startNodeId, endNodeId) => {
let message: Message = {getFullPath: {startNodeId: startNodeId, endNodeId: endNodeId}}; let settings = getSettings();
let message: Message = {getFullPath: {startNodeId: startNodeId, endNodeId: endNodeId, dragCoefficient: settings.dragCoefficient}};
routeWorker.postMessage(message); routeWorker.postMessage(message);
}); });
mapHandler.addExclusionAreaListener(polygons => { mapHandler.addExclusionAreaListener(polygons => {

View File

@ -11,7 +11,7 @@ import type { Position } from 'geojson';
import { createPalette } from 'hue-map'; import { createPalette } from 'hue-map';
const viridisPalette = createPalette({ const viridisPalette = createPalette({
map: 'inferno', map: 'viridis',
steps: 100 steps: 100
}); });
@ -312,13 +312,15 @@ class MapHandler {
let endLeafletCoordinate: LatLngTuple = [pathSegment.end.latitude, pathSegment.end.longitude]; let endLeafletCoordinate: LatLngTuple = [pathSegment.end.latitude, pathSegment.end.longitude];
let leafletCoordinates = [startLeafletCoordinate, endLeafletCoordinate]; let leafletCoordinates = [startLeafletCoordinate, endLeafletCoordinate];
let intensity = Math.round((pathSegment.speed - minimumSpeed) * 99.0 / (maximumSpeed - minimumSpeed)); let intensity = Math.round((pathSegment.requiredSpeed - minimumSpeed) * 99.0 / (maximumSpeed - minimumSpeed));
intensity = Math.min(Math.max(intensity, 0), 99); intensity = Math.min(Math.max(intensity, 0), 99);
let color = viridisPalette.format('cssHex')[99-intensity]; let color = viridisPalette.format('cssHex')[99-intensity];
let polyLine = L.polyline(leafletCoordinates, {color: color}).addTo(this.path); let polyLine = L.polyline(leafletCoordinates, {color: color}).addTo(this.path);
let speedKmh = pathSegment.speed * 3.6; let requiredSpeed = Math.max(minimumSpeed, pathSegment.requiredSpeed);
polyLine.bindTooltip(Math.round(speedKmh) + '.' + (Math.round(speedKmh * 10.0) % 10) + ' km/h'); let requiredSpeedKmh = requiredSpeed * 3.6;
let requiredSpeedKmhRounded = Math.round(requiredSpeedKmh * 10.0);
polyLine.bindTooltip('Required speed: ' + Math.floor(requiredSpeedKmhRounded / 10.0) + '.' + (requiredSpeedKmhRounded % 10) + ' km/h');
}); });
} }

View File

@ -128,7 +128,7 @@ onmessage = async (e) => {
} }
if (message.getFullPath != null) { if (message.getFullPath != null) {
let path = module.getPath(message.getFullPath.startNodeId, message.getFullPath.endNodeId); let path = module.getPath(message.getFullPath.startNodeId, message.getFullPath.endNodeId, message.getFullPath.dragCoefficient);
if (!path) { if (!path) {
sendErrorMessage('Could not get path'); sendErrorMessage('Could not get path');
return; return;
@ -150,6 +150,7 @@ onmessage = async (e) => {
let currentLngLat = proj4('EPSG:32633', 'EPSG:4326', currentUtm); let currentLngLat = proj4('EPSG:32633', 'EPSG:4326', currentUtm);
let speed = Math.max(previousPoint.currentSpeed, currentPoint.currentSpeed); let speed = Math.max(previousPoint.currentSpeed, currentPoint.currentSpeed);
let requiredSpeed = Math.max(previousPoint.requiredSpeed, currentPoint.requiredSpeed);
let segment: PathSegment = { let segment: PathSegment = {
start: { start: {
latitude: previousLngLat[1], latitude: previousLngLat[1],
@ -159,7 +160,8 @@ onmessage = async (e) => {
latitude: currentLngLat[1], latitude: currentLngLat[1],
longitude: currentLngLat[0] longitude: currentLngLat[0]
}, },
speed: speed calculatedSpeed: speed,
requiredSpeed: requiredSpeed
} }
pathSegments.push(segment); pathSegments.push(segment);