Export KML instead of GPX (since this seems to give better control of colour)
This commit is contained in:
parent
c61a76a6e4
commit
fb0247841b
@ -31,7 +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>
|
<button id="export-route-button">Export KML 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>
|
||||||
|
|||||||
@ -2,7 +2,7 @@ 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, PathSegment, Polygon } from './interfaces';
|
import type { Message, PathSegment, Polygon } from './interfaces';
|
||||||
import { downloadGpxFile } from './modules/gpx/exporter';
|
import { downloadKmlFile } from './modules/kml/exporter';
|
||||||
|
|
||||||
interface WindowState {
|
interface WindowState {
|
||||||
state: 'DataNotLoaded' | 'DataLoading' | 'Ready' | 'Searching'
|
state: 'DataNotLoaded' | 'DataLoading' | 'Ready' | 'Searching'
|
||||||
@ -378,7 +378,7 @@ searchButton?.addEventListener('click', _ => {
|
|||||||
|
|
||||||
exportButton?.addEventListener('click', _ => {
|
exportButton?.addEventListener('click', _ => {
|
||||||
if (lastRoute != null) {
|
if (lastRoute != null) {
|
||||||
downloadGpxFile(lastRoute);
|
downloadKmlFile(lastRoute);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -1,122 +0,0 @@
|
|||||||
import type { Coordinate, PathSegment } from "../../interfaces";
|
|
||||||
|
|
||||||
interface HighspeedSegment {
|
|
||||||
startingSegmentNumber: number,
|
|
||||||
segmentLength: number,
|
|
||||||
highestSpeed: number,
|
|
||||||
highestSpeedSegmentNumber: number
|
|
||||||
}
|
|
||||||
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
|
|
||||||
function createWaypoint(document: XMLDocument, parent: HTMLElement, name: string, coordinate: Coordinate, color: string): void {
|
|
||||||
let waypoint = createAndAppendChild(document, parent, 'wpt');
|
|
||||||
waypoint.setAttribute('lat', coordinate.latitude.toFixed(6));
|
|
||||||
waypoint.setAttribute('lon', coordinate.longitude.toFixed(6));
|
|
||||||
createAndAppendChild(document, waypoint, 'name', name);
|
|
||||||
let extensions = createAndAppendChild(document, waypoint, 'extensions');
|
|
||||||
let colorTag = document.createElementNS('https://osmand.net/docs/technical/osmand-file-formats/osmand-gpx', 'color');
|
|
||||||
extensions.appendChild(colorTag);
|
|
||||||
colorTag.appendChild(document.createTextNode(color));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function downloadGpxFile(path: PathSegment[]): void {
|
|
||||||
let gpxDocument = document.implementation.createDocument('http://www.topografix.com/GPX/1/1', 'gpx');
|
|
||||||
gpxDocument.documentElement.setAttribute('version', '1.1');
|
|
||||||
gpxDocument.documentElement.setAttribute('creator', 'https://freewheeling.martinserver.no');
|
|
||||||
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
// Add remaining route points
|
|
||||||
path.forEach(segment => {
|
|
||||||
createRoutePoint(gpxDocument, routeElement, segment.end, segment.requiredSpeed);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add waypoints where you need to start accelerating, where you reach top speed, and where you can start rolling normally again
|
|
||||||
var minimumSpeed: number = Math.min(...path.map(segment => segment.requiredSpeed));
|
|
||||||
var highspeedSegments: HighspeedSegment[] = [];
|
|
||||||
var currentSegment: HighspeedSegment | undefined = undefined;
|
|
||||||
|
|
||||||
for (var i = 0; i < path.length; i++) {
|
|
||||||
var segment = path[i];
|
|
||||||
if (segment.requiredSpeed - minimumSpeed > 0.01) {
|
|
||||||
if (currentSegment == null) {
|
|
||||||
currentSegment = {
|
|
||||||
startingSegmentNumber: i,
|
|
||||||
segmentLength: 1,
|
|
||||||
highestSpeed: -1,
|
|
||||||
highestSpeedSegmentNumber: -1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
currentSegment.segmentLength += 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (currentSegment != null) {
|
|
||||||
highspeedSegments.push(currentSegment);
|
|
||||||
currentSegment = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentSegment != null) {
|
|
||||||
highspeedSegments.push(currentSegment);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the highest speed inside each high speed segment
|
|
||||||
highspeedSegments.forEach(highspeedSegment => {
|
|
||||||
var highestSpeed = 0;
|
|
||||||
var highestSpeedSegmentNumber = 0;
|
|
||||||
for (var i = highspeedSegment.startingSegmentNumber; i < highspeedSegment.startingSegmentNumber + highspeedSegment.segmentLength; i++) {
|
|
||||||
if (path[i].requiredSpeed > highestSpeed) {
|
|
||||||
highestSpeed = path[i].requiredSpeed;
|
|
||||||
highestSpeedSegmentNumber = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
highspeedSegment.highestSpeed = highestSpeed * 3.6;
|
|
||||||
highspeedSegment.highestSpeedSegmentNumber = highestSpeedSegmentNumber;
|
|
||||||
});
|
|
||||||
|
|
||||||
// We probably want to drop the segments where we're not at least doubling the minimum speed, and we want them to be at least three or four long
|
|
||||||
highspeedSegments = highspeedSegments.filter(segment => segment.highestSpeed >= 2.0 * minimumSpeed && segment.segmentLength > 4);
|
|
||||||
|
|
||||||
// Add these segments to the gpx
|
|
||||||
highspeedSegments.forEach(highspeedSegment => {
|
|
||||||
let firstSegment = path[highspeedSegment.startingSegmentNumber];
|
|
||||||
let lastSegment = path[highspeedSegment.startingSegmentNumber + highspeedSegment.segmentLength - 1];
|
|
||||||
let fastestSegment = path[highspeedSegment.highestSpeedSegmentNumber];
|
|
||||||
|
|
||||||
createWaypoint(gpxDocument, gpxDocument.documentElement, 'Accelerate to ' + highspeedSegment.highestSpeed.toFixed(1) + ' km/h', firstSegment.start, '#00FF00');
|
|
||||||
createWaypoint(gpxDocument, gpxDocument.documentElement, 'Reach ' + highspeedSegment.highestSpeed.toFixed(1) + ' km/h', fastestSegment.start, '#FFFF00');
|
|
||||||
createWaypoint(gpxDocument, gpxDocument.documentElement, 'High speed ends', lastSegment.start, '#FF0000');
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
114
src/modules/kml/exporter.ts
Normal file
114
src/modules/kml/exporter.ts
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import type { PathSegment } from "../../interfaces";
|
||||||
|
|
||||||
|
interface HighspeedSegment {
|
||||||
|
startingSegmentNumber: number,
|
||||||
|
segmentLength: number,
|
||||||
|
highestSpeed: number,
|
||||||
|
highestSpeedSegmentNumber: number
|
||||||
|
}
|
||||||
|
|
||||||
|
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 createPlacemark(document: XMLDocument, parent: HTMLElement, style: string, path: PathSegment[], firstIndex: number, lastIndex: number, name: string) {
|
||||||
|
let placemark = createAndAppendChild(document, parent, 'Placemark');
|
||||||
|
createAndAppendChild(document, placemark, 'Name', name);
|
||||||
|
createAndAppendChild(document, placemark, 'styleUrl', style);
|
||||||
|
let lineString = createAndAppendChild(document, placemark, 'LineString');
|
||||||
|
var coordinatesString = '\n';
|
||||||
|
coordinatesString += path[firstIndex].start.longitude.toFixed(6) + ',' + path[firstIndex].start.latitude.toFixed(6) + ',0\n';
|
||||||
|
for (var i = firstIndex; i < lastIndex; i++) {
|
||||||
|
coordinatesString += path[i].end.longitude.toFixed(6) + ',' + path[i].end.latitude.toFixed(6) + ',0\n';
|
||||||
|
}
|
||||||
|
createAndAppendChild(document, lineString, 'coordinates', coordinatesString);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function downloadKmlFile(path: PathSegment[]): void {
|
||||||
|
let kmlDocument = document.implementation.createDocument('http://www.opengis.net/kml/2.2', 'kml');
|
||||||
|
let kmlRoot = createAndAppendChild(kmlDocument, kmlDocument.documentElement, 'Document');
|
||||||
|
createAndAppendChild(kmlDocument, kmlRoot, 'Name', 'Freewheeling route');
|
||||||
|
let slowStyle = createAndAppendChild(kmlDocument, kmlRoot, 'Style');
|
||||||
|
slowStyle.setAttribute('id', 'lowSpeed');
|
||||||
|
let slowLine = createAndAppendChild(kmlDocument, slowStyle, 'LineStyle');
|
||||||
|
createAndAppendChild(kmlDocument, slowLine, 'color', 'ff231be5');
|
||||||
|
|
||||||
|
let fastStyle = createAndAppendChild(kmlDocument, kmlRoot, 'Style');
|
||||||
|
fastStyle.setAttribute('id', 'highSpeed');
|
||||||
|
let fastLine = createAndAppendChild(kmlDocument, fastStyle, 'LineStyle');
|
||||||
|
createAndAppendChild(kmlDocument, fastLine, 'color', 'ff3c8c3c');
|
||||||
|
|
||||||
|
// Add waypoints where you need to start accelerating, where you reach top speed, and where you can start rolling normally again
|
||||||
|
var minimumSpeed: number = Math.min(...path.map(segment => segment.requiredSpeed));
|
||||||
|
var highspeedSegments: HighspeedSegment[] = [];
|
||||||
|
var currentSegment: HighspeedSegment | undefined = undefined;
|
||||||
|
|
||||||
|
for (var i = 0; i < path.length; i++) {
|
||||||
|
var segment = path[i];
|
||||||
|
if (segment.requiredSpeed - minimumSpeed > 0.01) {
|
||||||
|
if (currentSegment == null) {
|
||||||
|
currentSegment = {
|
||||||
|
startingSegmentNumber: i,
|
||||||
|
segmentLength: 1,
|
||||||
|
highestSpeed: -1,
|
||||||
|
highestSpeedSegmentNumber: -1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentSegment.segmentLength += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (currentSegment != null) {
|
||||||
|
highspeedSegments.push(currentSegment);
|
||||||
|
currentSegment = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentSegment != null) {
|
||||||
|
highspeedSegments.push(currentSegment);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the highest speed inside each high speed segment
|
||||||
|
highspeedSegments.forEach(highspeedSegment => {
|
||||||
|
var highestSpeed = 0;
|
||||||
|
var highestSpeedSegmentNumber = 0;
|
||||||
|
for (var i = highspeedSegment.startingSegmentNumber; i < highspeedSegment.startingSegmentNumber + highspeedSegment.segmentLength; i++) {
|
||||||
|
if (path[i].requiredSpeed > highestSpeed) {
|
||||||
|
highestSpeed = path[i].requiredSpeed;
|
||||||
|
highestSpeedSegmentNumber = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
highspeedSegment.highestSpeed = highestSpeed * 3.6;
|
||||||
|
highspeedSegment.highestSpeedSegmentNumber = highestSpeedSegmentNumber;
|
||||||
|
});
|
||||||
|
|
||||||
|
// We probably want to drop the segments where we're not at least doubling the minimum speed, and we want them to be at least three or four long
|
||||||
|
highspeedSegments = highspeedSegments.filter(segment => segment.highestSpeed >= 2.0 * minimumSpeed && segment.segmentLength > 4);
|
||||||
|
var pointCounter = 0;
|
||||||
|
highspeedSegments.forEach(highspeedSegment => {
|
||||||
|
if (pointCounter < highspeedSegment.startingSegmentNumber) {
|
||||||
|
createPlacemark(kmlDocument, kmlRoot, '#lowSpeed', path, pointCounter, highspeedSegment.startingSegmentNumber, 'Freeroll');
|
||||||
|
}
|
||||||
|
let lastNumber = highspeedSegment.startingSegmentNumber + highspeedSegment.segmentLength;
|
||||||
|
createPlacemark(kmlDocument, kmlRoot, '#highSpeed', path, highspeedSegment.startingSegmentNumber, lastNumber, highspeedSegment.highestSpeed.toFixed(1) + ' km/h');
|
||||||
|
pointCounter = lastNumber;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (pointCounter < path.length) {
|
||||||
|
createPlacemark(kmlDocument, kmlRoot, '#lowSpeed', path, pointCounter, path.length, 'Freeroll');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var element = document.createElement('a');
|
||||||
|
element.setAttribute('href', 'data:application/gpx+xml;base64,' + btoa('<?xml version="1.0" encoding="utf-8"?>' + new XMLSerializer().serializeToString(kmlDocument)));
|
||||||
|
element.setAttribute('download', 'freewheeling-route.kml');
|
||||||
|
element.style.display = 'none';
|
||||||
|
document.body.appendChild(element);
|
||||||
|
element.click();
|
||||||
|
document.body.removeChild(element);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user