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