diff --git a/native/route_search.cpp b/native/route_search.cpp index e0f9c0f..155c1b2 100644 --- a/native/route_search.cpp +++ b/native/route_search.cpp @@ -54,6 +54,7 @@ struct JSNodeInfo { float positionZ; float distanceFromStart; float currentSpeed; + float requiredSpeed; }; struct JSSearchResult { @@ -459,7 +460,36 @@ JSSearchResult findAllPathsFromPointJS(int startingNode, float minimumSpeed, flo return searchResult; } -std::vector 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 getPathJS(uint32_t startingNode, uint32_t endNode, float dragCoefficient) { std::vector path; if (lastSearchResult.startingNode != startingNode) { @@ -496,6 +526,34 @@ std::vector getPathJS(uint32_t startingNode, uint32_t endNode) { 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; } @@ -761,6 +819,7 @@ EMSCRIPTEN_BINDINGS(my_module) { .property("positionY", &JSNodeInfo::positionY) .property("positionZ", &JSNodeInfo::positionZ) .property("currentSpeed", &JSNodeInfo::currentSpeed) + .property("requiredSpeed", &JSNodeInfo::requiredSpeed) .property("distanceFromStart", &JSNodeInfo::distanceFromStart); emscripten::class_("SearchResult") diff --git a/src/interfaces.ts b/src/interfaces.ts index 952b767..103341d 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -61,7 +61,8 @@ interface FoundPathsFromNode { interface GetFullPath { startNodeId: number, - endNodeId: number + endNodeId: number, + dragCoefficient: number } export interface Coordinate { @@ -72,7 +73,8 @@ export interface Coordinate { export interface PathSegment { start: Coordinate, end: Coordinate, - speed: number + calculatedSpeed: number, + requiredSpeed: number } interface ReturnFullPath { diff --git a/src/main.ts b/src/main.ts index cca530d..43f5ebf 100644 --- a/src/main.ts +++ b/src/main.ts @@ -160,7 +160,8 @@ function setUpMapHandler() { routeWorker.postMessage(message); }); 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); }); mapHandler.addExclusionAreaListener(polygons => { diff --git a/src/modules/maphandler/maphandler.ts b/src/modules/maphandler/maphandler.ts index c05b3b4..51c7a00 100644 --- a/src/modules/maphandler/maphandler.ts +++ b/src/modules/maphandler/maphandler.ts @@ -11,7 +11,7 @@ import type { Position } from 'geojson'; import { createPalette } from 'hue-map'; const viridisPalette = createPalette({ - map: 'inferno', + map: 'viridis', steps: 100 }); @@ -312,13 +312,15 @@ class MapHandler { let endLeafletCoordinate: LatLngTuple = [pathSegment.end.latitude, pathSegment.end.longitude]; 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); let color = viridisPalette.format('cssHex')[99-intensity]; let polyLine = L.polyline(leafletCoordinates, {color: color}).addTo(this.path); - let speedKmh = pathSegment.speed * 3.6; - polyLine.bindTooltip(Math.round(speedKmh) + '.' + (Math.round(speedKmh * 10.0) % 10) + ' km/h'); + let requiredSpeed = Math.max(minimumSpeed, pathSegment.requiredSpeed); + 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'); }); } diff --git a/src/modules/worker/worker.ts b/src/modules/worker/worker.ts index 6f3ba60..8712240 100644 --- a/src/modules/worker/worker.ts +++ b/src/modules/worker/worker.ts @@ -128,7 +128,7 @@ onmessage = async (e) => { } 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) { sendErrorMessage('Could not get path'); return; @@ -150,6 +150,7 @@ onmessage = async (e) => { let currentLngLat = proj4('EPSG:32633', 'EPSG:4326', currentUtm); let speed = Math.max(previousPoint.currentSpeed, currentPoint.currentSpeed); + let requiredSpeed = Math.max(previousPoint.requiredSpeed, currentPoint.requiredSpeed); let segment: PathSegment = { start: { latitude: previousLngLat[1], @@ -159,7 +160,8 @@ onmessage = async (e) => { latitude: currentLngLat[1], longitude: currentLngLat[0] }, - speed: speed + calculatedSpeed: speed, + requiredSpeed: requiredSpeed } pathSegments.push(segment);