fabulator / gpx-builder

Builder of GPX files
Apache License 2.0
38 stars 7 forks source link

Little issue with types and extensions #47

Closed jimmykane closed 4 years ago

jimmykane commented 4 years ago

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?

fabulator commented 4 years ago

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
jimmykane commented 4 years ago

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

jimmykane commented 4 years ago

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

fabulator commented 4 years ago
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},
}
fabulator commented 4 years ago

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.

jimmykane commented 4 years ago

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

jimmykane commented 4 years ago

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.

fabulator commented 4 years ago

great, it looks interesting