Export routes to gpx
This commit is contained in:
parent
00db764fd0
commit
06fa1be52b
@ -31,6 +31,7 @@
|
||||
<div id="map"></div>
|
||||
<br />
|
||||
<button id="search-button">Start search</button>
|
||||
<button id="export-route-button">Export GPX route</button>
|
||||
<p id="search-status-paragraph"></p>
|
||||
<table id="search-result-table" style="display: none;">
|
||||
<thead>
|
||||
|
||||
@ -489,7 +489,7 @@ float calculateRequiredSpeed(float endSpeed, float horizontalDistance, float hei
|
||||
return requiredSpeed;
|
||||
}
|
||||
|
||||
std::vector<JSNodeInfo> getPathJS(uint32_t startingNode, uint32_t endNode, float dragCoefficient) {
|
||||
std::vector<JSNodeInfo> getPathJS(uint32_t startingNode, uint32_t endNode, float minimumSpeed, float dragCoefficient) {
|
||||
std::vector<JSNodeInfo> path;
|
||||
|
||||
if (lastSearchResult.startingNode != startingNode) {
|
||||
@ -529,8 +529,8 @@ std::vector<JSNodeInfo> 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];
|
||||
|
||||
@ -62,6 +62,7 @@ interface FoundPathsFromNode {
|
||||
interface GetFullPath {
|
||||
startNodeId: number,
|
||||
endNodeId: number,
|
||||
minimumSpeed: number,
|
||||
dragCoefficient: number
|
||||
}
|
||||
|
||||
|
||||
27
src/main.ts
27
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') {
|
||||
|
||||
42
src/modules/gpx/exporter.ts
Normal file
42
src/modules/gpx/exporter.ts
Normal file
@ -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('<?xml version="1.0" encoding="utf-8"?>' + new XMLSerializer().serializeToString(gpxDocument)));
|
||||
element.setAttribute('download', 'freewheeling-route.gpx');
|
||||
element.style.display = 'none';
|
||||
document.body.appendChild(element);
|
||||
element.click();
|
||||
document.body.removeChild(element);
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user