diff --git a/index.html b/index.html
index ef6dadc..db3e6fa 100644
--- a/index.html
+++ b/index.html
@@ -31,6 +31,7 @@
+
diff --git a/native/route_search.cpp b/native/route_search.cpp
index 155c1b2..75780f5 100644
--- a/native/route_search.cpp
+++ b/native/route_search.cpp
@@ -489,7 +489,7 @@ float calculateRequiredSpeed(float endSpeed, float horizontalDistance, float hei
return requiredSpeed;
}
-std::vector getPathJS(uint32_t startingNode, uint32_t endNode, float dragCoefficient) {
+std::vector getPathJS(uint32_t startingNode, uint32_t endNode, float minimumSpeed, float dragCoefficient) {
std::vector path;
if (lastSearchResult.startingNode != startingNode) {
@@ -529,8 +529,8 @@ std::vector getPathJS(uint32_t startingNode, uint32_t endNode, float
float currentRequiredSpeed = -1.0;
for (auto it = path.rbegin(); it != path.rend(); it++) {
if (currentRequiredSpeed <= -1.0) {
- it->requiredSpeed = 1.0;
- currentRequiredSpeed = 1.0;
+ it->requiredSpeed = minimumSpeed;
+ currentRequiredSpeed = minimumSpeed;
} else {
uint32_t currentNodeId = it->nodeId;
RoadNode currentNode = set.roadNodes[currentNodeId];
diff --git a/src/interfaces.ts b/src/interfaces.ts
index 103341d..4c6f8eb 100644
--- a/src/interfaces.ts
+++ b/src/interfaces.ts
@@ -62,6 +62,7 @@ interface FoundPathsFromNode {
interface GetFullPath {
startNodeId: number,
endNodeId: number,
+ minimumSpeed: number,
dragCoefficient: number
}
diff --git a/src/main.ts b/src/main.ts
index 43f5ebf..fd0733c 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,7 +1,8 @@
import './style.css';
import './modules/maphandler/maphandler'
import MapHandler from './modules/maphandler/maphandler';
-import type { Message, Polygon } from './interfaces';
+import type { Message, PathSegment, Polygon } from './interfaces';
+import { downloadGpxFile } from './modules/gpx/exporter';
interface WindowState {
state: 'DataNotLoaded' | 'DataLoading' | 'Ready' | 'Searching'
@@ -34,6 +35,7 @@ var mapHandler: MapHandler | null = null;
var currentSearchArea: Polygon[] = [];
var currentState: WindowState = {state: 'DataNotLoaded'};
+var lastRoute: PathSegment[] | undefined;
var lastSearchUpdate = Date.now();
@@ -42,6 +44,7 @@ let notLoadedContainer = document.getElementById('notloadedcontainer');
let loadingContainer = document.getElementById('loadingcontainer');
let mapContainer = document.getElementById('mapcontainer');
let searchButton = document.getElementById('search-button');
+let exportButton = document.getElementById('export-route-button');
let searchStatusParagraph = document.getElementById('search-status-paragraph');
let searchResultsTable = document.getElementById('search-result-table');
let searchResultTableBody = document.getElementById('search-result-table-body');
@@ -84,6 +87,7 @@ routeWorker.onmessage = e => {
} else if (message.returnFullPath != null) {
let settings = getSettings();
mapHandler?.drawPath(message.returnFullPath.pathSegments, settings.minimumSpeed, settings.maximumSpeed);
+ setRoute(message.returnFullPath.pathSegments);
} else if (message.searchAreaResult != null) {
searchStatusParagraph?.setHTMLUnsafe('Searching. ' + message.searchAreaResult.remainingNodes + ' possible starting points remain.');
let currentTime = Date.now();
@@ -158,10 +162,11 @@ function setUpMapHandler() {
mapHandler.addClickedMapListener((latitude, longitude) => {
let message: Message = {findClosestNode: {latitude: latitude, longitude: longitude}};
routeWorker.postMessage(message);
+ setRoute(undefined);
});
mapHandler.addClickedEndpointListener((startNodeId, endNodeId) => {
let settings = getSettings();
- let message: Message = {getFullPath: {startNodeId: startNodeId, endNodeId: endNodeId, dragCoefficient: settings.dragCoefficient}};
+ let message: Message = {getFullPath: {startNodeId: startNodeId, endNodeId: endNodeId, minimumSpeed: settings.minimumSpeed, dragCoefficient: settings.dragCoefficient}};
routeWorker.postMessage(message);
});
mapHandler.addExclusionAreaListener(polygons => {
@@ -232,6 +237,18 @@ function setState(state: WindowState) {
};
setState({state: 'DataNotLoaded'});
+function setRoute(route?: PathSegment[]) {
+ lastRoute = route;
+ if (exportButton && exportButton instanceof HTMLButtonElement) {
+ if (lastRoute == null) {
+ exportButton.disabled = true;
+ } else {
+ exportButton.disabled = false;
+ }
+ }
+};
+setRoute(undefined);
+
// Set up the settings values
function setUpNumberInput(element: HTMLElement | null, localStorageKey: string, defaultValue: number): void {
if (element != null && element instanceof HTMLInputElement) {
@@ -345,6 +362,12 @@ searchButton?.addEventListener('click', _ => {
}
});
+exportButton?.addEventListener('click', _ => {
+ if (lastRoute != null) {
+ downloadGpxFile(lastRoute);
+ }
+})
+
settingsButton?.addEventListener('click', _ => {
let settingsDisplay = settingsDiv?.style.getPropertyValue('display');
if (settingsDisplay === 'none') {
diff --git a/src/modules/gpx/exporter.ts b/src/modules/gpx/exporter.ts
new file mode 100644
index 0000000..e6b059b
--- /dev/null
+++ b/src/modules/gpx/exporter.ts
@@ -0,0 +1,42 @@
+import type { Coordinate, PathSegment } from "../../interfaces";
+
+function createAndAppendChild(document: XMLDocument, parent: HTMLElement, nameOfChild: string, contents?: string): HTMLElement {
+ let childElement = document.createElement(nameOfChild);
+ parent.appendChild(childElement);
+ if (contents) {
+ childElement.appendChild(document.createTextNode(contents));
+ }
+ return childElement;
+}
+
+function createRoutePoint(document: XMLDocument, parent: HTMLElement, coordinate: Coordinate, requiredSpeed: number) {
+ let routePoint = createAndAppendChild(document, parent, 'rtept');
+ routePoint.setAttribute('lat', coordinate.latitude.toFixed(6));
+ routePoint.setAttribute('lon', coordinate.longitude.toFixed(6));
+ createAndAppendChild(document, routePoint, 'desc', 'Required speed: ' + (requiredSpeed * 3.6).toFixed(1) + ' km/h');
+}
+
+export function downloadGpxFile(path: PathSegment[]): void {
+ let gpxDocument = document.implementation.createDocument('http://www.topografix.com/GPX/1/1', 'gpx');
+
+ let routeElement = createAndAppendChild(gpxDocument, gpxDocument.documentElement, 'rte');
+ createAndAppendChild(gpxDocument, routeElement, 'name', 'Freewheeling route');
+ createAndAppendChild(gpxDocument, routeElement, 'desc', 'Route to be biked without pedalling');
+ createAndAppendChild(gpxDocument, routeElement, 'src', 'https://freewheeling.martinserver.no');
+
+ // Add first route point
+ if (path.length > 0) {
+ createRoutePoint(gpxDocument, routeElement, path.at(0)!.start, path.at(0)!.requiredSpeed);
+ }
+ path.forEach(segment => {
+ createRoutePoint(gpxDocument, routeElement, segment.end, segment.requiredSpeed);
+ });
+
+ var element = document.createElement('a');
+ element.setAttribute('href', 'data:application/gpx+xml;base64,' + btoa('' + new XMLSerializer().serializeToString(gpxDocument)));
+ element.setAttribute('download', 'freewheeling-route.gpx');
+ element.style.display = 'none';
+ document.body.appendChild(element);
+ element.click();
+ document.body.removeChild(element);
+}
\ No newline at end of file
diff --git a/src/modules/maphandler/maphandler.ts b/src/modules/maphandler/maphandler.ts
index 51c7a00..d28461e 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: 'viridis',
+ map: 'autumn',
steps: 100
});
@@ -314,7 +314,7 @@ class MapHandler {
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 color = viridisPalette.format('cssHex')[intensity];
let polyLine = L.polyline(leafletCoordinates, {color: color}).addTo(this.path);
let requiredSpeed = Math.max(minimumSpeed, pathSegment.requiredSpeed);
diff --git a/src/modules/worker/worker.ts b/src/modules/worker/worker.ts
index 8712240..c2e9fdd 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, message.getFullPath.dragCoefficient);
+ let path = module.getPath(message.getFullPath.startNodeId, message.getFullPath.endNodeId, message.getFullPath.minimumSpeed, message.getFullPath.dragCoefficient);
if (!path) {
sendErrorMessage('Could not get path');
return;