fabulator/gpx-builder

Little issue with types and extensions

Closed this issue · 8 comments

Hi and wow what a great lib:

I am writing an exporter for https://github.com/sports-alliance/sports-lib and we have this issue:

 Type 'number' is not assignable to type '{ [key: string]: string | number; }'.

50                 cadence: <number>data[DataCadence.type],

The code to create a point:

new Point(
            <number>data[DataLatitudeDegrees.type],
            <number>data[DataLongitudeDegrees.type],
            {
              ele: data[DataAltitude.type] || undefined,
              time: new Date(activity.startDate.getTime() + <number>data[DataTime.type] * 1000),
              hr: data[DataHeartRate.type],
              extensions: {
                power: <number>data[DataPower.type],
                speed: <number>data[DataSpeed.type],
                temperature: <number>data[DataTemperature.type],
                cadence: <number>data[DataCadence.type],
              }
            }
          )

Looks like some type issue?

Hi,

it's problem of valid XML file. To use extensions you must define them in XML header. You can check how I add Garmin extension definitions in repo https://github.com/fabulator/gpx-builder/blob/master/src/builder/GarminBuilder/GarminBuilder.ts

There is also the name of extension that needs to be used in file https://github.com/fabulator/gpx-builder/blob/master/src/builder/GarminBuilder/models/GarminPoint.ts. Generated point looks like this:

 <trkpt lat="51.12832496166229" lon="15.615156626701355">
        <ele>314.715</ele>
        <time>2018-06-10T17:39:35.000Z</time>
        <extensions>
          <gpxtpx:TrackPointExtension>
            <gpxtpx:hr>121</gpxtpx:hr>
          </gpxtpx:TrackPointExtension>
        </extensions>
      </trkpt>

so you would need to use something like this:

extensions: {
'gpxtpx:TrackPointExtension': {
                power: <number>data[DataPower.type],
                speed: <number>data[DataSpeed.type],
                temperature: <number>data[DataTemperature.type],
                cadence: <number>data[DataCadence.type],
}
              }

The problem here is that there is no definition of power in gpx file. If you want to generate non valid XML file (most services don't validate schema anyway) you can use type-assertion to avoid typescript error.

import {Extensions} from 'gpx-builder/dist/types'
extensions: {
                power: <number>data[DataPower.type],
                speed: <number>data[DataSpeed.type],
                temperature: <number>data[DataTemperature.type],
                cadence: <number>data[DataCadence.type],
              } as Extensions

Hey I found the issue to be the import way. Following the readme it was ok.

That said:

pointsArray.push(new Point(
              <number>data[DataLatitudeDegrees.type],
              <number>data[DataLongitudeDegrees.type],
              {
                ele: data[DataAltitude.type] || undefined,
                time: new Date(activity.startDate.getTime() + <number>data[DataTime.type] * 1000),
                hr: data[DataHeartRate.type],
                power: data[DataPower.type] || undefined,
                speed: data[DataSpeed.type] || undefined,
                atemp: data[DataTemperature.type] || undefined,
                cad: data[DataCadence.type] || undefined,
                extensions: {
                  power: data[DataPower.type] || undefined,
                  distance: data[DataDistance.type] || undefined,
                }
              }
            ))
            return pointsArray;

I will revert back to typescript types import and use:

import {Extensions} from 'gpx-builder/dist/types'
extensions: {
                power: <number>data[DataPower.type],
                speed: <number>data[DataSpeed.type],
                temperature: <number>data[DataTemperature.type],
                cadence: <number>data[DataCadence.type],
              } as Extensions

I do understand the problem well here, I was not doubting about the extensions and it's great that you know that.
FYI Strava reads the distance extension for example.

I ll get back to this

Ok,

Doing an import as:

const {buildGPX, GarminBuilder} = require('gpx-builder');
const {Point, Metadata, Person, Copyright, Link, Track, Segment} = GarminBuilder.MODELS;

Works when using eg


const tracks: typeof Track = []
      event.getActivities().forEach((activity) => {
        const timeStream = event.getFirstActivity().generateTimeStream([DataLatitudeDegrees.type]);
        activity.addStream(timeStream);
        const segment = new Segment(
          event.getFirstActivity().getStreamDataTypesBasedOnDataType(DataLatitudeDegrees.type, [
            DataLongitudeDegrees.type,
            DataTime.type,
            DataDistance.type,
            DataHeartRate.type,
            DataCadence.type,
            DataTemperature.type,
            DataPower.type,
            DataAltitude.type,
            DataSpeed.type
          ]).reduce((pointsArray: typeof Point[], data, index, array) => {
            pointsArray.push(new Point(
              <number>data[DataLatitudeDegrees.type],
              <number>data[DataLongitudeDegrees.type],
              {
                ele: data[DataAltitude.type] || undefined,
                time: new Date(activity.startDate.getTime() + <number>data[DataTime.type] * 1000),
                hr: data[DataHeartRate.type],
                power: data[DataPower.type] || undefined,
                speed: data[DataSpeed.type] || undefined,
                atemp: data[DataTemperature.type] || undefined,
                cad: data[DataCadence.type] || undefined,
                extensions: {
                  power: data[DataPower.type] || undefined,
                  distance: data[DataDistance.type] || undefined,
                }
              }
            ))
            return pointsArray;
          }, []))
        tracks.push(new Track([segment]));
        activity.removeStream(timeStream);
      })

However why the issue when importing the ts type files ? That I dont get? Sorry for my ignorance

extensions: {
                  power: data[DataPower.type] || undefined,
                  distance: data[DataDistance.type] || undefined,
                }

This is not valid. XML schema expect you will name your extension and put some prefix to data, so it would looks like this:

extensions: {
'gpxtpx:TrackPointExtension': {
                  'gpxtpx:power': data[DataPower.type] || undefined,
                  'gpxtpx:distance': data[DataDistance.type] || undefined,
                }
}

The extensions has type

interface Extensions {
    [key: string]: {[key: string]: string | number},
}

And yes, Strava read some extension directly:

Strava also detects general tags placed in the <extensions> tag of each tag. Strava extracts:

cadence as cadence
distance as distance
heartrate as heartrate
power as power

even that they are not valid by schema. I think I will add some StravaBuilder that would support them.

No worries got it :-D Thanks so much again. I am closing this as it works as expected

FYI if you are into sports I am going to use this lib to support this opensource project https://quantified-self.io/ for exporting to GPX.

great, it looks interesting