2024-04-16 13:02:22 +02:00
import { XMLParser , XMLBuilder } from "fast-xml-parser" ;
2024-04-16 13:54:48 +02:00
import { GPXFileType } from "./types" ;
import { GPXFile } from "./gpx" ;
2024-04-15 14:26:34 +02:00
export function parseGPX ( gpxData : string ) : GPXFile {
const parser = new XMLParser ( {
ignoreAttributes : false ,
attributeNamePrefix : "" ,
2024-04-16 13:02:22 +02:00
attributesGroupName : 'attributes' ,
2024-04-16 11:48:42 +02:00
isArray : ( name : string ) = > {
return name === 'trk' || name === 'trkseg' || name === 'trkpt' || name === 'wpt' ;
} ,
attributeValueProcessor ( attrName , attrValue , jPath ) {
if ( attrName === 'lat' || attrName === 'lon' ) {
return parseFloat ( attrValue ) ;
}
return attrValue ;
} ,
transformTagName ( tagName : string ) {
if ( tagName === 'power' ) {
// Transform the simple <power> tag to the more complex <gpxpx:PowerExtension> tag, the nested <gpxpx:PowerInWatts> tag is then handled by the tagValueProcessor
2024-04-16 13:02:22 +02:00
return 'gpxpx:PowerExtension' ;
2024-04-16 11:48:42 +02:00
}
return tagName ;
} ,
tagValueProcessor ( tagName , tagValue , jPath , hasAttributes , isLeafNode ) {
if ( isLeafNode ) {
if ( tagName === 'ele' ) {
return parseFloat ( tagValue ) ;
}
if ( tagName === 'time' ) {
return new Date ( tagValue ) ;
}
2024-04-16 13:02:22 +02:00
if ( tagName === 'gpxtpx:hr' || tagName === 'gpxtpx:cad' || tagName === 'gpxtpx:atemp' || tagName === 'gpxpx:PowerInWatts' || tagName === 'opacity' || tagName === 'weight' ) {
2024-04-16 11:48:42 +02:00
return parseFloat ( tagValue ) ;
}
2024-04-16 13:02:22 +02:00
if ( tagName === 'gpxpx:PowerExtension' ) {
2024-04-16 11:48:42 +02:00
// Finish the transformation of the simple <power> tag to the more complex <gpxpx:PowerExtension> tag
// Note that this only targets the transformed <power> tag, since it must be a leaf node
return {
2024-04-16 13:02:22 +02:00
'gpxpx:PowerInWatts' : parseFloat ( tagValue )
2024-04-16 11:48:42 +02:00
} ;
}
2024-04-16 09:54:41 +02:00
}
2024-04-16 11:48:42 +02:00
return tagValue ;
} ,
} ) ;
2024-04-16 09:54:41 +02:00
2024-04-16 13:54:48 +02:00
const parsed : GPXFileType = parser . parse ( gpxData ) . gpx ;
2024-04-16 09:54:41 +02:00
2024-04-16 13:54:48 +02:00
return new GPXFile ( parsed ) ;
2024-04-16 11:48:42 +02:00
}
2024-04-16 13:02:22 +02:00
2024-04-19 12:17:31 +02:00
export function buildGPX ( file : GPXFile ) : string {
const gpx = file . toGPXFileType ( ) ;
2024-04-16 13:02:22 +02:00
const builder = new XMLBuilder ( {
format : true ,
ignoreAttributes : false ,
attributeNamePrefix : "" ,
attributesGroupName : 'attributes' ,
suppressEmptyNode : true ,
tagValueProcessor : ( tagName : string , tagValue : unknown ) : string = > {
if ( tagValue instanceof Date ) {
return tagValue . toISOString ( ) ;
}
return tagValue . toString ( ) ;
} ,
} ) ;
gpx . attributes . creator = 'https://gpx.studio' ;
gpx . attributes [ 'version' ] = '1.1' ;
gpx . attributes [ 'xmlns' ] = 'http://www.topografix.com/GPX/1/1' ;
gpx . attributes [ 'xmlns:xsi' ] = 'http://www.w3.org/2001/XMLSchema-instance' ;
gpx . attributes [ 'xsi:schemaLocation' ] = 'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/PowerExtension/v1 http://www.garmin.com/xmlschemas/PowerExtensionv1.xsd http://www.topografix.com/GPX/gpx_style/0/2 http://www.topografix.com/GPX/gpx_style/0/2/gpx_style.xsd' ;
gpx . attributes [ 'xmlns:gpxtpx' ] = 'http://www.garmin.com/xmlschemas/TrackPointExtension/v1' ;
gpx . attributes [ 'xmlns:gpxx' ] = 'http://www.garmin.com/xmlschemas/GpxExtensions/v3' ;
gpx . attributes [ 'xmlns:gpxpx' ] = 'http://www.garmin.com/xmlschemas/PowerExtension/v1' ;
gpx . attributes [ 'xmlns:gpx_style' ] = 'http://www.topografix.com/GPX/gpx_style/0/2' ;
gpx . metadata . author = {
name : 'gpx.studio' ,
link : {
attributes : {
href : 'https://gpx.studio' ,
}
}
} ;
return builder . build ( {
"?xml" : {
attributes : {
version : "1.0" ,
encoding : "UTF-8" ,
}
} ,
gpx
} ) ;
}