Unit Conversion
The units used by most devices are a mix between Système international, imperial and some obscure units designed for storage in memory (for example, semicircles instead of degrees). This library contains tools to transform the data, the most useful of which is Unit Conversion.
To apply a unit conversion to the data messages, you must first define some rules to lay out exactly what you want to happen - for example, what units to convert to which. This library calls these rules a Conversion Policy
There are two different types of rules in the conversion policy:
- Unit Conversion
- Field Conversion
A unit conversion converts every field with one unit to a different unit. This allows you to, for example, convert all fields with the unit degrees celsius to degrees fahrenheit.
A field conversion allows you to convert every field with a specific name to another type. For example, you could convert all altitudes to feet and at the same time leave distance in metres. The field conversion policy always has precedence over a unit conversion policy.
The policies are stored in HashMaps as demonstrated below.
HashMap<String, String> unitPolicy = [
semicircle : "degree",
metre : "mile"
]
HashMap<String, String> fieldPolicy = [
altitude : "feet"
]
This creates a unit policy which will convert all semicircle units to degrees and metres to miles. It also specifies a field policy that converts fields named ‘altitude’ to feet (the converter will automatically detect the original unit of the altitude field).
The following code demonstrates the conversion in action.
def converter = new MessageConverter(new ConversionPolicy(fieldPolicy, unitPolicy))
new FitParser().parse(new File("fit/fit.fit")).each { message ->
println message.getType()
DataMessage converted = converter.convertMessage(message)
converted.fields.each{ field ->
println "\t ${field.getKey()}: ${field.getValue()} (${converted.unitSymbols[field.getKey()]})"
}
}
- The policies defined in the previous code listing are used to create a new
MessageConverterobject - the type responsible for transforming the data. - A new message type is created and given the name
converted. It is identical tomessageother than the fields that have been transformed. - The fields are printed out. Note that the
unitSymbolsHashMap has been updated with the new units as appropriate.
Note that any fields that have not been converted remain exactly the same as before. Also, the convertMessage method does not mutate the original message in any way. If you want to, you can print out both the original and transformed DataMessgae objects to compare them.
The above code has the following output.
record
timestamp: 831901185 (s)
position_lat: <REDACTED> (deg)
position_long: <REDACTED> (deg)
distance: 8.1151854055 (miles)
altitude: 221.7847840000001 (feet)
speed: 8.007 (m/s)
cadence: 89 (rpm)
temperature: 15 (C)
Notice that the field conversion policy had precedence over the unit conversion policy, as altitude has the unit feet instead of miles.
Adding more conversions
The library contains some conversions by default (see the loadConversions method in converter.Converter.groovy, however you may run into situations where there is no conversion available, leading to a ConvertException being thrown.
You can add a new conversion by using the Converter.addConversion method, which takes a Conversion object. As an example, suppose that the library did not support conversion between celsius and fahrenheit (which in reality, it does) and you wanted to add the conversion yourself. To gain the temperature in fahrenheit, from celsius, the conversion is
fahrenheit = 1.8 * celsius + 32
The conversion is linear (in two dimentions as there are two units involved), so it has two contants: the multiplication factor (the gradient of the line) and the addition factor (the interception between the line and the y axis). This conversion can be added using the following code.
Converter.addConversion(new Conversion().from("celsius").to("fahrenheit").constants(1.8, 32))
The library will add the conversion and calculate the inverse. As a result, it can now convert celsius to fahrenheit and from fahrenheit to celsius. There is no need to define the conversion in both directions.
Adding more units
To add a new unit, you need to provide two pieces of information. Firstly, the unit name. This is a string used to identify the unit in conversion policies and when creating new conversions. Secondly, the unit symbol is needed. This is the symbol as it appears in the FIT profile (you can find this out by printing out the fields and units..
Converter.addUnit("celsius", "C")
Converter.addUnit("mile", "mile")