Export routes to gpx
This commit is contained in:
parent
00db764fd0
commit
06fa1be52b
@ -31,6 +31,7 @@
|
|||||||
<div id="map"></div>
|
<div id="map"></div>
|
||||||
<br />
|
<br />
|
||||||
<button id="search-button">Start search</button>
|
<button id="search-button">Start search</button>
|
||||||
|
<button id="export-route-button">Export GPX route</button>
|
||||||
<p id="search-status-paragraph"></p>
|
<p id="search-status-paragraph"></p>
|
||||||
<table id="search-result-table" style="display: none;">
|
<table id="search-result-table" style="display: none;">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@ -489,7 +489,7 @@ float calculateRequiredSpeed(float endSpeed, float horizontalDistance, float hei
|
|||||||
return requiredSpeed;
|
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;
|
std::vector<JSNodeInfo> path;
|
||||||
|
|
||||||
if (lastSearchResult.startingNode != startingNode) {
|
if (lastSearchResult.startingNode != startingNode) {
|
||||||
@ -529,8 +529,8 @@ std::vector<JSNodeInfo> getPathJS(uint32_t startingNode, uint32_t endNode, float
|
|||||||
float currentRequiredSpeed = -1.0;
|
float currentRequiredSpeed = -1.0;
|
||||||
for (auto it = path.rbegin(); it != path.rend(); it++) {
|
for (auto it = path.rbegin(); it != path.rend(); it++) {
|
||||||
if (currentRequiredSpeed <= -1.0) {
|
if (currentRequiredSpeed <= -1.0) {
|
||||||
it->requiredSpeed = 1.0;
|
it->requiredSpeed = minimumSpeed;
|
||||||
currentRequiredSpeed = 1.0;
|
currentRequiredSpeed = minimumSpeed;
|
||||||
} else {
|
} else {
|
||||||
uint32_t currentNodeId = it->nodeId;
|
uint32_t currentNodeId = it->nodeId;
|
||||||
RoadNode currentNode = set.roadNodes[currentNodeId];
|
RoadNode currentNode = set.roadNodes[currentNodeId];
|
||||||
|
|||||||
@ -62,6 +62,7 @@ interface FoundPathsFromNode {
|
|||||||
interface GetFullPath {
|
interface GetFullPath {
|
||||||
startNodeId: number,
|
startNodeId: number,
|
||||||
endNodeId: number,
|
endNodeId: number,
|
||||||
|
minimumSpeed: number,
|
||||||
dragCoefficient: number
|
dragCoefficient: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
27
src/main.ts
27
src/main.ts
@ -1,7 +1,8 @@
|
|||||||
import './style.css';
|
import './style.css';
|
||||||
import './modules/maphandler/maphandler'
|
import './modules/maphandler/maphandler'
|
||||||
import MapHandler from './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 {
|
interface WindowState {
|
||||||
state: 'DataNotLoaded' | 'DataLoading' | 'Ready' | 'Searching'
|
state: 'DataNotLoaded' | 'DataLoading' | 'Ready' | 'Searching'
|
||||||
@ -34,6 +35,7 @@ var mapHandler: MapHandler | null = null;
|
|||||||
var currentSearchArea: Polygon[] = [];
|
var currentSearchArea: Polygon[] = [];
|
||||||
|
|
||||||
var currentState: WindowState = {state: 'DataNotLoaded'};
|
var currentState: WindowState = {state: 'DataNotLoaded'};
|
||||||
|
var lastRoute: PathSegment[] | undefined;
|
||||||
|
|
||||||
var lastSearchUpdate = Date.now();
|
var lastSearchUpdate = Date.now();
|
||||||
|
|
||||||
@ -42,6 +44,7 @@ let notLoadedContainer = document.getElementById('notloadedcontainer');
|
|||||||
let loadingContainer = document.getElementById('loadingcontainer');
|
let loadingContainer = document.getElementById('loadingcontainer');
|
||||||
let mapContainer = document.getElementById('mapcontainer');
|
let mapContainer = document.getElementById('mapcontainer');
|
||||||
let searchButton = document.getElementById('search-button');
|
let searchButton = document.getElementById('search-button');
|
||||||
|
let exportButton = document.getElementById('export-route-button');
|
||||||
let searchStatusParagraph = document.getElementById('search-status-paragraph');
|
let searchStatusParagraph = document.getElementById('search-status-paragraph');
|
||||||
let searchResultsTable = document.getElementById('search-result-table');
|
let searchResultsTable = document.getElementById('search-result-table');
|
||||||
let searchResultTableBody = document.getElementById('search-result-table-body');
|
let searchResultTableBody = document.getElementById('search-result-table-body');
|
||||||
@ -84,6 +87,7 @@ routeWorker.onmessage = e => {
|
|||||||
} else if (message.returnFullPath != null) {
|
} else if (message.returnFullPath != null) {
|
||||||
let settings = getSettings();
|
let settings = getSettings();
|
||||||
mapHandler?.drawPath(message.returnFullPath.pathSegments, settings.minimumSpeed, settings.maximumSpeed);
|
mapHandler?.drawPath(message.returnFullPath.pathSegments, settings.minimumSpeed, settings.maximumSpeed);
|
||||||
|
setRoute(message.returnFullPath.pathSegments);
|
||||||
} else if (message.searchAreaResult != null) {
|
} else if (message.searchAreaResult != null) {
|
||||||
searchStatusParagraph?.setHTMLUnsafe('Searching. ' + message.searchAreaResult.remainingNodes + ' possible starting points remain.');
|
searchStatusParagraph?.setHTMLUnsafe('Searching. ' + message.searchAreaResult.remainingNodes + ' possible starting points remain.');
|
||||||
let currentTime = Date.now();
|
let currentTime = Date.now();
|
||||||
@ -158,10 +162,11 @@ function setUpMapHandler() {
|
|||||||
mapHandler.addClickedMapListener((latitude, longitude) => {
|
mapHandler.addClickedMapListener((latitude, longitude) => {
|
||||||
let message: Message = {findClosestNode: {latitude: latitude, longitude: longitude}};
|
let message: Message = {findClosestNode: {latitude: latitude, longitude: longitude}};
|
||||||
routeWorker.postMessage(message);
|
routeWorker.postMessage(message);
|
||||||
|
setRoute(undefined);
|
||||||
});
|
});
|
||||||
mapHandler.addClickedEndpointListener((startNodeId, endNodeId) => {
|
mapHandler.addClickedEndpointListener((startNodeId, endNodeId) => {
|
||||||
let settings = getSettings();
|
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);
|
routeWorker.postMessage(message);
|
||||||
});
|
});
|
||||||
mapHandler.addExclusionAreaListener(polygons => {
|
mapHandler.addExclusionAreaListener(polygons => {
|
||||||
@ -232,6 +237,18 @@ function setState(state: WindowState) {
|
|||||||
};
|
};
|
||||||
setState({state: 'DataNotLoaded'});
|
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
|
// Set up the settings values
|
||||||
function setUpNumberInput(element: HTMLElement | null, localStorageKey: string, defaultValue: number): void {
|
function setUpNumberInput(element: HTMLElement | null, localStorageKey: string, defaultValue: number): void {
|
||||||
if (element != null && element instanceof HTMLInputElement) {
|
if (element != null && element instanceof HTMLInputElement) {
|
||||||
@ -345,6 +362,12 @@ searchButton?.addEventListener('click', _ => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
exportButton?.addEventListener('click', _ => {
|
||||||
|
if (lastRoute != null) {
|
||||||
|
downloadGpxFile(lastRoute);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
settingsButton?.addEventListener('click', _ => {
|
settingsButton?.addEventListener('click', _ => {
|
||||||
let settingsDisplay = settingsDiv?.style.getPropertyValue('display');
|
let settingsDisplay = settingsDiv?.style.getPropertyValue('display');
|
||||||
if (settingsDisplay === 'none') {
|
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';
|
import { createPalette } from 'hue-map';
|
||||||
|
|
||||||
const viridisPalette = createPalette({
|
const viridisPalette = createPalette({
|
||||||
map: 'viridis',
|
map: 'autumn',
|
||||||
steps: 100
|
steps: 100
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -314,7 +314,7 @@ class MapHandler {
|
|||||||
|
|
||||||
let intensity = Math.round((pathSegment.requiredSpeed - 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')[intensity];
|
||||||
|
|
||||||
let polyLine = L.polyline(leafletCoordinates, {color: color}).addTo(this.path);
|
let polyLine = L.polyline(leafletCoordinates, {color: color}).addTo(this.path);
|
||||||
let requiredSpeed = Math.max(minimumSpeed, pathSegment.requiredSpeed);
|
let requiredSpeed = Math.max(minimumSpeed, pathSegment.requiredSpeed);
|
||||||
|
|||||||
@ -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, message.getFullPath.dragCoefficient);
|
let path = module.getPath(message.getFullPath.startNodeId, message.getFullPath.endNodeId, message.getFullPath.minimumSpeed, message.getFullPath.dragCoefficient);
|
||||||
if (!path) {
|
if (!path) {
|
||||||
sendErrorMessage('Could not get path');
|
sendErrorMessage('Could not get path');
|
||||||
return;
|
return;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user