diff --git a/index.html b/index.html
index e5955ef..a6c0870 100644
--- a/index.html
+++ b/index.html
@@ -31,7 +31,7 @@
-
+
diff --git a/src/main.ts b/src/main.ts
index 7ccc21c..6fd91ba 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -2,7 +2,7 @@ import './style.css';
import './modules/maphandler/maphandler'
import MapHandler from './modules/maphandler/maphandler';
import type { Message, PathSegment, Polygon } from './interfaces';
-import { downloadGpxFile } from './modules/gpx/exporter';
+import { downloadKmlFile } from './modules/kml/exporter';
interface WindowState {
state: 'DataNotLoaded' | 'DataLoading' | 'Ready' | 'Searching'
@@ -378,7 +378,7 @@ searchButton?.addEventListener('click', _ => {
exportButton?.addEventListener('click', _ => {
if (lastRoute != null) {
- downloadGpxFile(lastRoute);
+ downloadKmlFile(lastRoute);
}
})
diff --git a/src/modules/gpx/exporter.ts b/src/modules/gpx/exporter.ts
deleted file mode 100644
index 3de77cb..0000000
--- a/src/modules/gpx/exporter.ts
+++ /dev/null
@@ -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('' + 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/kml/exporter.ts b/src/modules/kml/exporter.ts
new file mode 100644
index 0000000..8b7bf40
--- /dev/null
+++ b/src/modules/kml/exporter.ts
@@ -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('' + new XMLSerializer().serializeToString(kmlDocument)));
+ element.setAttribute('download', 'freewheeling-route.kml');
+ element.style.display = 'none';
+ document.body.appendChild(element);
+ element.click();
+ document.body.removeChild(element);
+}
\ No newline at end of file