@sports-alliance/sports-lib
Version:
A Library to for importing / exporting and processing GPX, TCX, FIT and JSON files from services such as Strava, Movescount, Garmin, Polar etc
906 lines • 91.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ActivityUtilities = void 0;
var data_heart_rate_1 = require("../../data/data.heart-rate");
var data_cadence_1 = require("../../data/data.cadence");
var data_speed_1 = require("../../data/data.speed");
var data_vertical_speed_1 = require("../../data/data.vertical-speed");
var data_temperature_1 = require("../../data/data.temperature");
var data_altitude_1 = require("../../data/data.altitude");
var data_power_1 = require("../../data/data.power");
var data_altitude_max_1 = require("../../data/data.altitude-max");
var data_altitude_min_1 = require("../../data/data.altitude-min");
var data_altitude_avg_1 = require("../../data/data.altitude-avg");
var data_heart_rate_max_1 = require("../../data/data.heart-rate-max");
var data_heart_rate_min_1 = require("../../data/data.heart-rate-min");
var data_heart_rate_avg_1 = require("../../data/data.heart-rate-avg");
var data_cadence_max_1 = require("../../data/data.cadence-max");
var data_cadence_min_1 = require("../../data/data.cadence-min");
var data_cadence_avg_1 = require("../../data/data.cadence-avg");
var data_speed_max_1 = require("../../data/data.speed-max");
var data_speed_min_1 = require("../../data/data.speed-min");
var data_speed_avg_1 = require("../../data/data.speed-avg");
var data_vertical_speed_max_1 = require("../../data/data.vertical-speed-max");
var data_vertical_speed_min_1 = require("../../data/data.vertical-speed-min");
var data_vertical_speed_avg_1 = require("../../data/data.vertical-speed-avg");
var data_power_max_1 = require("../../data/data.power-max");
var data_power_min_1 = require("../../data/data.power-min");
var data_power_avg_1 = require("../../data/data.power-avg");
var data_temperature_max_1 = require("../../data/data.temperature-max");
var data_temperature_min_1 = require("../../data/data.temperature-min");
var data_temperature_avg_1 = require("../../data/data.temperature-avg");
var data_distance_1 = require("../../data/data.distance");
var data_duration_1 = require("../../data/data.duration");
var data_pause_1 = require("../../data/data.pause");
var data_ascent_1 = require("../../data/data.ascent");
var data_descent_1 = require("../../data/data.descent");
var geolib_adapter_1 = require("../../geodesy/adapters/geolib.adapter");
var data_pace_max_1 = require("../../data/data.pace-max");
var data_pace_1 = require("../../data/data.pace");
var data_pace_min_1 = require("../../data/data.pace-min");
var data_pace_avg_1 = require("../../data/data.pace-avg");
var data_battery_charge_1 = require("../../data/data.battery-charge");
var data_battery_consumption_1 = require("../../data/data.battery-consumption");
var data_battery_life_estimation_1 = require("../../data/data.battery-life-estimation");
var data_latitude_degrees_1 = require("../../data/data.latitude-degrees");
var stream_1 = require("../../streams/stream");
var helpers_1 = require("./helpers");
var data_longitude_degrees_1 = require("../../data/data.longitude-degrees");
var data_energy_1 = require("../../data/data.energy");
var data_start_altitude_1 = require("../../data/data.start-altitude");
var data_end_altitude_1 = require("../../data/data.end-altitude");
var data_swim_pace_max_1 = require("../../data/data.swim-pace-max");
var data_swim_pace_1 = require("../../data/data.swim-pace");
var data_swim_pace_min_1 = require("../../data/data.swim-pace-min");
var data_swim_pace_avg_1 = require("../../data/data.swim-pace-avg");
var data_feeling_1 = require("../../data/data.feeling");
var data_power_left_1 = require("../../data/data.power-left");
var data_right_balance_1 = require("../../data/data.right-balance");
var data_left_balance_1 = require("../../data/data.left-balance");
var data_power_right_1 = require("../../data/data.power-right");
var data_air_power_min_1 = require("../../data/data.air-power-min");
var data_air_power_1 = require("../../data/data.air-power");
var data_air_power_max_1 = require("../../data/data.air-power-max");
var data_air_power_avg_1 = require("../../data/data.air-power-avg");
var data_rpe_1 = require("../../data/data.rpe");
var data_gnss_distance_1 = require("../../data/data.gnss-distance");
var data_heart_rate_zone_one_duration_1 = require("../../data/data.heart-rate-zone-one-duration");
var data_heart_rate_zone_two_duration_1 = require("../../data/data.heart-rate-zone-two-duration");
var data_heart_rate_zone_three_duration_1 = require("../../data/data.heart-rate-zone-three-duration");
var data_heart_rate_zone_four_duration_1 = require("../../data/data.heart-rate-zone-four-duration");
var data_heart_rate_zone_five_duration_1 = require("../../data/data.heart-rate-zone-five-duration");
var data_power_zone_one_duration_1 = require("../../data/data.power-zone-one-duration");
var data_power_zone_two_duration_1 = require("../../data/data.power-zone-two-duration");
var data_power_zone_three_duration_1 = require("../../data/data.power-zone-three-duration");
var data_power_zone_four_duration_1 = require("../../data/data.power-zone-four-duration");
var data_power_zone_five_duration_1 = require("../../data/data.power-zone-five-duration");
var data_speed_zone_one_duration_1 = require("../../data/data.speed-zone-one-duration");
var data_speed_zone_two_duration_1 = require("../../data/data.speed-zone-two-duration");
var data_speed_zone_three_duration_1 = require("../../data/data.speed-zone-three-duration");
var data_speed_zone_four_duration_1 = require("../../data/data.speed-zone-four-duration");
var data_speed_zone_five_duration_1 = require("../../data/data.speed-zone-five-duration");
var data_store_1 = require("../../data/data.store");
var data_start_position_1 = require("../../data/data.start-position");
var data_end_position_1 = require("../../data/data.end-position");
var data_grade_adjusted_speed_avg_1 = require("../../data/data.grade-adjusted-speed-avg");
var data_grade_adjusted_pace_avg_1 = require("../../data/data.grade-adjusted-pace-avg");
var data_grade_adjusted_speed_1 = require("../../data/data.grade-adjusted-speed");
var data_grade_adjusted_pace_1 = require("../../data/data.grade-adjusted-pace");
var data_grade_adjusted_speed_max_1 = require("../../data/data.grade-adjusted-speed-max");
var data_grade_adjusted_speed_min_1 = require("../../data/data.grade-adjusted-speed-min");
var data_grade_adjusted_pace_max_1 = require("../../data/data.grade-adjusted-pace-max");
var data_grade_adjusted_pace_min_1 = require("../../data/data.grade-adjusted-pace-min");
var data_grade_1 = require("../../data/data.grade");
var grade_calculator_1 = require("./grade-calculator/grade-calculator");
var activity_types_1 = require("../../activities/activity.types");
var data_moving_time_1 = require("../../data/data.moving-time");
var ActivityUtilities = /** @class */ (function () {
function ActivityUtilities() {
}
ActivityUtilities.getDataTypeAvg = function (activity, streamType, startDate, endDate) {
var data = (activity
.getSquashedStreamData(streamType, startDate, endDate)
.filter(function (streamData) { return streamData !== Infinity && streamData !== -Infinity; }));
return this.getAverage(data);
};
ActivityUtilities.getAverage = function (data) {
var sum = data.reduce(function (sumbuff, value) {
sumbuff += value;
return sumbuff;
}, 0);
return sum / data.length;
};
ActivityUtilities.getDataTypeMax = function (activity, streamType, startDate, endDate) {
return this.getActivityDataTypeMinOrMax(activity, streamType, true, startDate, endDate);
};
ActivityUtilities.getDataTypeMin = function (activity, streamType, startDate, endDate) {
return this.getActivityDataTypeMinOrMax(activity, streamType, false, startDate, endDate);
};
ActivityUtilities.getDataTypeMinToMaxDifference = function (activity, streamType, startDate, endDate) {
return (this.getDataTypeMax(activity, streamType, startDate, endDate) -
this.getDataTypeMin(activity, streamType, startDate, endDate));
};
ActivityUtilities.getDataTypeFirst = function (activity, streamType, startDate, endDate) {
var data = activity.getSquashedStreamData(streamType, startDate, endDate);
return data[0];
};
ActivityUtilities.getDataTypeLast = function (activity, streamType, startDate, endDate) {
var data = activity.getSquashedStreamData(streamType, startDate, endDate);
return data[data.length - 1];
};
ActivityUtilities.cropDistance = function (startDistance, endDistance, activity) {
// Short to do the search just in case
var startDistanceDate; // Does not sound right
var endDistanceDate;
// debugger;
activity.getStreamData(data_distance_1.DataDistance.type).forEach(function (distanceFromData, index) {
// Find the index with greater dinstnce and convert it to time
if (startDistance && !startDistanceDate && distanceFromData && distanceFromData >= startDistance) {
startDistanceDate = new Date(activity.startDate.getTime() + index * 1000);
return;
}
// Same for end
if (endDistance && !endDistanceDate && distanceFromData && distanceFromData >= endDistance) {
endDistanceDate = new Date(activity.startDate.getTime() + index * 1000);
return;
}
});
if (!startDistanceDate && !endDistanceDate) {
return activity;
}
activity = this.cropTime(activity, startDistanceDate, endDistanceDate);
// Remove because it is invalid, you cannot just offset the distance as a stream I think
var distanceStream = activity.getAllStreams().find(function (s) { return data_distance_1.DataDistance.type === s.type; });
if (distanceStream) {
activity.removeStream(distanceStream);
}
var gnssDistanceStream = activity.getAllStreams().find(function (s) { return data_gnss_distance_1.DataGNSSDistance.type === s.type; });
if (gnssDistanceStream) {
activity.removeStream(gnssDistanceStream);
}
return activity;
};
/**
* Crops left,right on time.
* Start and end date need to be relative to the activity start / end time
* @param activity
* @param startDate
* @param endDate
*/
ActivityUtilities.cropTime = function (activity, startDate, endDate) {
activity.getAllStreams().forEach(function (stream) {
// Get the data for the range specified
var trimmedStreamData = activity.getStreamData(stream.type, startDate, endDate);
activity.removeStream(stream);
activity.addStream(new stream_1.Stream(stream.type, trimmedStreamData));
});
activity.startDate = startDate || activity.startDate;
activity.endDate = endDate || activity.endDate;
// debugger
return activity;
};
ActivityUtilities.getStreamDataTypesBasedOnDataType = function (streamToBaseOn, streams) {
return streamToBaseOn.getData().reduce(function (accu, streamDataItem, index) {
var _a;
if (!helpers_1.isNumberOrString(streamDataItem)) {
return accu;
}
var dataItem = (_a = {},
_a[streamToBaseOn.type] = streamDataItem,
_a);
streams.forEach(function (stream) {
dataItem[stream.type] = stream.getData()[index];
});
accu.push(dataItem);
return accu;
}, []);
};
ActivityUtilities.getStreamDataTypesBasedOnTime = function (startDate, endDate, streams) {
var streamDataBasedOnTime = {};
var _loop_1 = function (i) {
// Perhaps this can be optimized with a search function
streams.forEach(function (stream) {
if (helpers_1.isNumber(stream.getData()[i])) {
streamDataBasedOnTime[startDate.getTime() + i * 1000] =
streamDataBasedOnTime[startDate.getTime() + i * 1000] || {};
streamDataBasedOnTime[startDate.getTime() + i * 1000][stream.type] = stream.getData()[i];
}
});
};
for (var i = 0; i < this.getDataLength(startDate, endDate); i++) {
_loop_1(i);
}
return streamDataBasedOnTime;
};
ActivityUtilities.getDataLength = function (startDate, endDate) {
return Math.ceil((+endDate - +startDate) / 1000) + 1;
};
ActivityUtilities.generateMissingStreamsAndStatsForActivity = function (activity) {
this.generateMissingStreamsForActivity(activity);
activity.addStreams(this.createUnitStreamsFromStreams(activity.getAllStreams(), activity.type));
this.generateMissingStatsForActivity(activity);
this.generateMissingSpeedDerivedStatsForActivity(activity);
this.generateMissingUnitStatsForActivity(activity); // Perhaps this needs to happen on user level so needs to go out of here
};
ActivityUtilities.getSummaryStatsForActivities = function (activities) {
var stats = [];
// If only one
if (activities.length === 1) {
return activities[0].getStatsAsArray();
}
var duration = 0;
var ascent = 0;
var descent = 0;
var energy = 0;
var distance = 0;
var pauseTime = 0;
var averageHeartRate = 0;
var averagePower = 0;
var averageCadence = 0;
var averageSpeed = 0;
var averageGradeAdjustedSpeed = 0;
var averagePace = 0;
var averageGradeAdjustedPace = 0;
var averageSwimPace = 0;
var averageTemperature = 0;
var averageFeeling = 0;
var averageRPE = 0;
// Sum Duration
activities.forEach(function (activity) {
duration += activity.getDuration().getValue();
});
stats.push(new data_duration_1.DataDuration(duration));
// Sum pause time
activities.forEach(function (activity) {
pauseTime += activity.getPause().getValue();
});
stats.push(new data_pause_1.DataPause(pauseTime));
// Sum Distance
activities.forEach(function (activity) {
distance += activity.getDistance().getValue();
});
stats.push(new data_distance_1.DataDistance(distance));
// Sum ascent
activities.forEach(function (activity) {
var activityAscent = activity.getStat(data_ascent_1.DataAscent.type);
if (activityAscent) {
ascent += activityAscent.getValue();
}
});
stats.push(new data_ascent_1.DataAscent(ascent));
// Sum descent
activities.forEach(function (activity) {
var activityDescent = activity.getStat(data_descent_1.DataDescent.type);
if (activityDescent) {
descent += activityDescent.getValue();
}
});
stats.push(new data_descent_1.DataDescent(descent));
// Sum energy
activities.forEach(function (activity) {
var activityEnergy = activity.getStat(data_energy_1.DataEnergy.type);
if (activityEnergy) {
energy += activityEnergy.getValue();
}
});
stats.push(new data_energy_1.DataEnergy(energy));
// Avg Avg HR
activities.forEach(function (activity) {
var activityAvgHeartRate = activity.getStat(data_heart_rate_avg_1.DataHeartRateAvg.type);
if (activityAvgHeartRate) {
// The below will fallback for 0
averageHeartRate = averageHeartRate
? (averageHeartRate + activityAvgHeartRate.getValue()) / 2
: activityAvgHeartRate.getValue();
}
});
if (averageHeartRate) {
stats.push(new data_heart_rate_avg_1.DataHeartRateAvg(averageHeartRate));
}
// Avg Avg HR
activities.forEach(function (activity) {
var activityAvgHeartRate = activity.getStat(data_heart_rate_avg_1.DataHeartRateAvg.type);
if (activityAvgHeartRate) {
// The below will fallback for 0
averageHeartRate = averageHeartRate
? (averageHeartRate + activityAvgHeartRate.getValue()) / 2
: activityAvgHeartRate.getValue();
}
});
if (averageHeartRate) {
stats.push(new data_heart_rate_avg_1.DataHeartRateAvg(averageHeartRate));
}
// Avg Avg Power
activities.forEach(function (activity) {
var activityAvgPower = activity.getStat(data_power_avg_1.DataPowerAvg.type);
if (activityAvgPower) {
// The below will fallback for 0
averagePower = averagePower
? (averagePower + activityAvgPower.getValue()) / 2
: activityAvgPower.getValue();
}
});
if (averagePower) {
stats.push(new data_power_avg_1.DataPowerAvg(averagePower));
}
// Avg Avg Cadence
activities.forEach(function (activity) {
var activityAvgCadence = activity.getStat(data_cadence_avg_1.DataCadenceAvg.type);
if (activityAvgCadence) {
// The below will fallback for 0
averageCadence = averageCadence
? (averageCadence + activityAvgCadence.getValue()) / 2
: activityAvgCadence.getValue();
}
});
if (averageCadence) {
stats.push(new data_cadence_avg_1.DataCadenceAvg(averageCadence));
}
// Avg Avg Speed
activities.forEach(function (activity) {
var activityAvgSpeed = activity.getStat(data_speed_avg_1.DataSpeedAvg.type);
if (activityAvgSpeed) {
// The below will fallback for 0
averageSpeed = averageSpeed
? (averageSpeed + activityAvgSpeed.getValue()) / 2
: activityAvgSpeed.getValue();
}
});
if (averageSpeed) {
stats.push(new data_speed_avg_1.DataSpeedAvg(averageSpeed));
}
// Avg Avg Gap Speed
activities.forEach(function (activity) {
var activityAvgGradeAdjustedSpeed = activity.getStat(data_grade_adjusted_speed_avg_1.DataGradeAdjustedSpeedAvg.type);
if (activityAvgGradeAdjustedSpeed) {
// The below will fallback for 0
averageGradeAdjustedSpeed = averageGradeAdjustedSpeed
? (averageGradeAdjustedSpeed + activityAvgGradeAdjustedSpeed.getValue()) / 2
: activityAvgGradeAdjustedSpeed.getValue();
}
});
if (averageGradeAdjustedSpeed) {
stats.push(new data_grade_adjusted_speed_avg_1.DataGradeAdjustedSpeedAvg(averageGradeAdjustedSpeed));
}
// Avg Avg Pace
activities.forEach(function (activity) {
var activityAvgPace = activity.getStat(data_pace_avg_1.DataPaceAvg.type);
if (activityAvgPace) {
// The below will fallback for 0
averagePace = averagePace
? (averagePace + activityAvgPace.getValue()) / 2
: activityAvgPace.getValue();
}
});
if (averagePace) {
stats.push(new data_pace_avg_1.DataPaceAvg(averagePace));
}
// Avg Avg GAP Pace
activities.forEach(function (activity) {
var activityAvgGradeAdjustedPace = activity.getStat(data_grade_adjusted_pace_avg_1.DataGradeAdjustedPaceAvg.type);
if (activityAvgGradeAdjustedPace) {
// The below will fallback for 0
averageGradeAdjustedPace = averageGradeAdjustedPace
? (averageGradeAdjustedPace + activityAvgGradeAdjustedPace.getValue()) / 2
: activityAvgGradeAdjustedPace.getValue();
}
});
if (averageGradeAdjustedPace) {
stats.push(new data_grade_adjusted_pace_avg_1.DataGradeAdjustedPaceAvg(averageGradeAdjustedPace));
}
// Avg Avg SwimPace
activities.forEach(function (activity) {
var activityAvgSwimPace = activity.getStat(data_swim_pace_avg_1.DataSwimPaceAvg.type);
if (activityAvgSwimPace) {
// The below will fallback for 0
averageSwimPace = averageSwimPace
? (averageSwimPace + activityAvgSwimPace.getValue()) / 2
: activityAvgSwimPace.getValue();
}
});
if (averageSwimPace) {
stats.push(new data_swim_pace_avg_1.DataSwimPaceAvg(averageSwimPace));
}
// Avg Avg Temperature
activities.forEach(function (activity) {
var activityAvgTemperature = activity.getStat(data_temperature_avg_1.DataTemperatureAvg.type);
if (activityAvgTemperature) {
// The below will fallback for 0
averageTemperature = averageTemperature
? (averageTemperature + activityAvgTemperature.getValue()) / 2
: activityAvgTemperature.getValue();
}
});
if (averageTemperature) {
stats.push(new data_temperature_avg_1.DataTemperatureAvg(averageTemperature));
}
// Avg Feeling
activities.forEach(function (activity) {
var activityAvgFeeling = activity.getStat(data_feeling_1.DataFeeling.type);
if (activityAvgFeeling) {
// The below will fallback for 0
averageFeeling = averageFeeling
? Math.ceil((averageFeeling + activityAvgFeeling.getValue()) / 2)
: activityAvgFeeling.getValue();
}
});
if (averageFeeling) {
stats.push(new data_feeling_1.DataFeeling(averageFeeling));
}
// Avg RPE
activities.forEach(function (activity) {
var activityAvgRPE = activity.getStat(data_feeling_1.DataFeeling.type);
if (activityAvgRPE) {
// The below will fallback for 0
averageRPE = averageRPE
? Math.ceil((averageRPE + activityAvgRPE.getValue()) / 2)
: activityAvgRPE.getValue();
}
});
if (averageRPE) {
stats.push(new data_rpe_1.DataRPE(averageRPE));
}
stats.push.apply(stats, this.getIntensityZonesStatsAggregated(activities));
// Add start and end position
// This expects the to be sorted
var activitiesWithStartPosition = activities.filter(function (activity) { return activity.getStat(data_start_position_1.DataStartPosition.type); });
var activitiesWithEndPosition = activities.filter(function (activity) { return activity.getStat(data_end_position_1.DataEndPosition.type); });
if (activitiesWithStartPosition && activitiesWithStartPosition.length) {
var startPositionStat = activitiesWithStartPosition[0].getStat(data_start_position_1.DataStartPosition.type);
stats.push(new data_start_position_1.DataStartPosition(startPositionStat.getValue()));
}
if (activitiesWithEndPosition && activitiesWithEndPosition.length) {
var endPositionStat = (activitiesWithEndPosition[activitiesWithEndPosition.length - 1].getStat(data_end_position_1.DataEndPosition.type));
stats.push(new data_end_position_1.DataEndPosition(endPositionStat.getValue()));
}
// debugger;
return stats;
};
ActivityUtilities.getIntensityZonesStatsAggregated = function (statClassInstances) {
return [
data_heart_rate_zone_one_duration_1.DataHeartRateZoneOneDuration.type,
data_heart_rate_zone_two_duration_1.DataHeartRateZoneTwoDuration.type,
data_heart_rate_zone_three_duration_1.DataHeartRateZoneThreeDuration.type,
data_heart_rate_zone_four_duration_1.DataHeartRateZoneFourDuration.type,
data_heart_rate_zone_five_duration_1.DataHeartRateZoneFiveDuration.type,
data_power_zone_one_duration_1.DataPowerZoneOneDuration.type,
data_power_zone_two_duration_1.DataPowerZoneTwoDuration.type,
data_power_zone_three_duration_1.DataPowerZoneThreeDuration.type,
data_power_zone_four_duration_1.DataPowerZoneFourDuration.type,
data_power_zone_five_duration_1.DataPowerZoneFiveDuration.type,
data_speed_zone_one_duration_1.DataSpeedZoneOneDuration.type,
data_speed_zone_two_duration_1.DataSpeedZoneTwoDuration.type,
data_speed_zone_three_duration_1.DataSpeedZoneThreeDuration.type,
data_speed_zone_four_duration_1.DataSpeedZoneFourDuration.type,
data_speed_zone_five_duration_1.DataSpeedZoneFiveDuration.type
].reduce(function (statsArray, zone) {
var zoneDuration = statClassInstances.reduce(function (duration, statClassInstance) {
var durationStat = statClassInstance.getStat(zone);
if (durationStat) {
duration = duration || 0;
duration += durationStat.getValue();
}
return duration;
}, null);
if (helpers_1.isNumber(zoneDuration)) {
statsArray.push(data_store_1.DynamicDataLoader.getDataInstanceFromDataType(zone, zoneDuration));
}
return statsArray;
}, []);
};
ActivityUtilities.getActivityDataTypeGain = function (activity, streamType, starDate, endDate, minDiff) {
return this.getActivityDataTypeGainOrLoss(activity, streamType, true, starDate, endDate, minDiff);
};
ActivityUtilities.getActivityDataTypeLoss = function (activity, streamType, starDate, endDate, minDiff) {
return this.getActivityDataTypeGainOrLoss(activity, streamType, false, starDate, endDate, minDiff);
};
ActivityUtilities.getGainOrLoss = function (data, gain, minDiff) {
if (minDiff === void 0) { minDiff = 3; }
var gainOrLoss = 0;
data.reduce(function (previousValue, nextValue) {
// For gain
if (gain) {
// Increase the gain if eligible first check to be greater plus diff [200, 300, 400, 100, 101, 102]
if (previousValue + minDiff <= nextValue) {
gainOrLoss += nextValue - previousValue;
return nextValue;
}
// if not eligible check if smaller without the diff and if yes do not register it and send it back as the last to check against
if (previousValue < nextValue) {
return previousValue;
}
return nextValue;
}
// For Loss
if (previousValue - minDiff >= nextValue) {
gainOrLoss += previousValue - nextValue;
return nextValue;
}
// if not eligible check if smaller without the diff and if yes do not register it and send it back as the last to check against
if (previousValue > nextValue) {
return previousValue;
}
return nextValue;
});
return gainOrLoss;
};
ActivityUtilities.getMax = function (data) {
return data.reduce(function (previousValue, currentValue) {
return Math.max(previousValue, currentValue);
}, -Infinity);
};
ActivityUtilities.getMin = function (data) {
return data.reduce(function (previousValue, currentValue) {
return Math.min(previousValue, currentValue);
}, Infinity);
};
ActivityUtilities.calculateTotalDistanceForActivity = function (activity, startDate, endDate) {
return this.geoLibAdapter.getDistance(activity.getPositionData(startDate, endDate).filter(function (position) { return position !== null; }));
};
/**
* Returns streams that derive from speed based on the activity type
* @param speedStream
* @param activityType
*/
ActivityUtilities.createByActivityTypeSpeedBasedStreams = function (speedStream, activityType) {
return activity_types_1.ActivityTypesHelper.speedDerivedDataTypesToUseForActivityType(activityType).reduce(function (array, dataType) {
switch (dataType) {
case data_pace_1.DataPace.type:
return array.concat([
new stream_1.Stream(data_pace_1.DataPace.type, speedStream.getData().map(function (dataValue) {
if (!helpers_1.isNumber(dataValue)) {
return null;
}
return helpers_1.convertSpeedToPace(dataValue);
}))
]);
case data_swim_pace_1.DataSwimPace.type:
return array.concat([
new stream_1.Stream(data_swim_pace_1.DataSwimPace.type, speedStream.getData().map(function (dataValue) {
if (!helpers_1.isNumber(dataValue)) {
return null;
}
return helpers_1.convertSpeedToSwimPace(dataValue);
}))
]);
case data_speed_1.DataSpeed.type:
return array.concat(speedStream);
default:
return array;
}
}, []);
};
/**
* Returns streams that derive from grade adjusted speed based on the activity type
* @param gradeAdjustedSpeedStream
* @param activityType
*/
ActivityUtilities.createByActivityTypeAltiDistanceSpeedBasedStreams = function (gradeAdjustedSpeedStream, activityType) {
return activity_types_1.ActivityTypesHelper.altiDistanceSpeedDerivedDataTypesToUseForActivityType(activityType).reduce(function (array, dataType) {
switch (dataType) {
case data_grade_adjusted_pace_1.DataGradeAdjustedPace.type:
return array.concat([
new stream_1.Stream(data_grade_adjusted_pace_1.DataGradeAdjustedPace.type, gradeAdjustedSpeedStream.getData().map(function (dataValue) {
if (!helpers_1.isNumber(dataValue)) {
return null;
}
return helpers_1.convertSpeedToPace(dataValue);
}))
]);
case data_grade_adjusted_speed_1.DataGradeAdjustedSpeed.type:
return array.concat(gradeAdjustedSpeedStream);
default:
return array;
}
}, []);
};
/**
* @todo unit test (get the pun?)
* This creates streams that are deriving as unit based streams
* For example it will create pace from speed, swim pace from speed but also speed in km/h as a unitstream
* @param streams
* @param activityType
* @param unitStreamTypes DynamicDataLoader.allUnitDerivedDataTypes this acts like a whitelist for the unit derived units ONLY!
*/
ActivityUtilities.createUnitStreamsFromStreams = function (streams, activityType, unitStreamTypes) {
// @todo perhaps check input to be unitStreamTypesStrictly
var unitStreamTypesToCreate = unitStreamTypes || data_store_1.DynamicDataLoader.allUnitDerivedDataTypes;
var baseUnitStreams = [];
var speedStream = streams.find(function (stream) { return stream.type === data_speed_1.DataSpeed.type; });
if (speedStream) {
baseUnitStreams = baseUnitStreams.concat(this.createByActivityTypeSpeedBasedStreams(speedStream, activityType));
}
var gradeAdjustedSpeedStream = streams.find(function (stream) { return stream.type === data_grade_adjusted_speed_1.DataGradeAdjustedSpeed.type; });
if (gradeAdjustedSpeedStream) {
baseUnitStreams = baseUnitStreams.concat(this.createByActivityTypeAltiDistanceSpeedBasedStreams(gradeAdjustedSpeedStream, activityType));
}
var verticalSpeedStream = streams.find(function (stream) { return stream.type === data_vertical_speed_1.DataVerticalSpeed.type; });
if (verticalSpeedStream) {
// For vertical speed (yet) we dont need a seperate function so just add the base that is the "derived" one
baseUnitStreams = activity_types_1.ActivityTypesHelper.verticalSpeedDerivedDataTypesToUseForActivityType(activityType).length
? baseUnitStreams.concat(verticalSpeedStream)
: baseUnitStreams;
}
// @todo add distance ?
var startWith = baseUnitStreams.filter(function (baseUnitStream) {
return unitStreamTypesToCreate.indexOf(baseUnitStream.type) !== -1 && streams.indexOf(baseUnitStream) === -1;
});
return Object.keys(data_store_1.DynamicDataLoader.dataTypeUnitGroups).reduce(function (array, baseDataType) {
var baseStream = baseUnitStreams.find(function (stream) { return stream.type === baseDataType; });
if (!baseStream) {
return array;
}
var unitStreams = Object.keys(data_store_1.DynamicDataLoader.dataTypeUnitGroups[baseDataType])
.filter(function (unitBasedDataType) { return unitStreamTypesToCreate.indexOf(unitBasedDataType) !== -1; }) // @todo perhaps dont filter
.map(function (unitBasedDataType) {
return new stream_1.Stream(unitBasedDataType, baseStream.getData().map(function (dataValue) {
if (!helpers_1.isNumber(dataValue)) {
return null;
}
return data_store_1.DynamicDataLoader.dataTypeUnitGroups[baseDataType][unitBasedDataType](dataValue);
}));
});
return array.concat(unitStreams);
}, startWith);
};
/**
* Generates missing streams for an activity such as distance etc if they are missing
* This will always create a steam even if the distance is 0
* @param activity
*/
ActivityUtilities.generateMissingStreamsForActivity = function (activity) {
var _this = this;
// First add any missing data to the streams via interpolating and extrapolating
this.addMissingDataToStreams(activity);
if (activity.hasStreamData(data_latitude_degrees_1.DataLatitudeDegrees.type) &&
activity.hasStreamData(data_latitude_degrees_1.DataLatitudeDegrees.type) &&
(!activity.hasStreamData(data_distance_1.DataDistance.type) || !activity.hasStreamData(data_gnss_distance_1.DataGNSSDistance.type))) {
var streamData_1 = activity.createStream(data_distance_1.DataDistance.type).getData(); // Creating does not add it to activity just presets the resolution to 1s
var distance_1 = 0;
streamData_1[0] = distance_1; // Force first distance sample to be equal to 0 instead of null
activity
.getPositionData()
.reduce(function (prevPosition, position, index, array) {
if (!position) {
return prevPosition;
}
if (prevPosition && position) {
distance_1 += Number(_this.geoLibAdapter.getDistance([prevPosition, position]).toFixed(1));
}
streamData_1[index] = distance_1;
return position;
});
if (!activity.hasStreamData(data_distance_1.DataDistance.type)) {
activity.addStream(new stream_1.Stream(data_distance_1.DataDistance.type, streamData_1));
}
if (!activity.hasStreamData(data_gnss_distance_1.DataGNSSDistance.type)) {
activity.addStream(new stream_1.Stream(data_gnss_distance_1.DataGNSSDistance.type, streamData_1));
}
if (!activity.hasStreamData(data_speed_1.DataSpeed.type)) {
var speedStreamData_1 = activity.createStream(data_speed_1.DataSpeed.type).getData();
activity.getStreamDataByDuration(data_distance_1.DataDistance.type).forEach(function (distanceData, index) {
if (distanceData.value === 0) {
speedStreamData_1[index] = 0;
return;
}
if (distanceData.value !== null && isFinite(distanceData.time) && distanceData.time > 0) {
speedStreamData_1[index] = Math.round((distanceData.value / (distanceData.time / 1000)) * 100) / 100;
return;
}
speedStreamData_1[index] = null;
});
activity.addStream(new stream_1.Stream(data_speed_1.DataSpeed.type, speedStreamData_1));
}
}
// Check if we can get a grade stream
if (!activity.hasStreamData(data_grade_1.DataGrade.type) &&
activity.hasStreamData(data_distance_1.DataDistance.type) &&
activity.hasStreamData(data_altitude_1.DataAltitude.type)) {
var distanceData = activity.getStreamData(data_distance_1.DataDistance.type);
var altitudeData = activity.getStreamData(data_altitude_1.DataAltitude.type);
var gradeStreamData = grade_calculator_1.GradeCalculator.computeGradeStream(distanceData, altitudeData);
activity.addStream(new stream_1.Stream(data_grade_1.DataGrade.type, gradeStreamData));
}
// Get a grade adjusted speed (the model applies to running only)
if ((activity_types_1.ActivityTypesHelper.getActivityGroupForActivityType(activity.type) === activity_types_1.ActivityTypeGroups.Running ||
activity_types_1.ActivityTypesHelper.getActivityGroupForActivityType(activity.type) === activity_types_1.ActivityTypeGroups.TrailRunning) &&
!activity.hasStreamData(data_grade_adjusted_speed_1.DataGradeAdjustedSpeed.type) &&
activity.hasStreamData(data_grade_1.DataGrade.type) &&
activity.hasStreamData(data_speed_1.DataSpeed.type)) {
var speedStreamData = activity.getStreamData(data_speed_1.DataSpeed.type);
var gradeStreamData_1 = activity.getStreamData(data_grade_1.DataGrade.type);
var gradeAdjustedSpeedData = speedStreamData.map(function (value, index) {
return value === null
? null
: Math.round(grade_calculator_1.GradeCalculator.estimateAdjustedSpeed(value, gradeStreamData_1[index] || 0) * 100) / 100;
});
activity.addStream(new stream_1.Stream(data_grade_adjusted_speed_1.DataGradeAdjustedSpeed.type, gradeAdjustedSpeedData));
}
if (activity.hasStreamData(data_power_1.DataPower.type) &&
activity.hasStreamData(data_right_balance_1.DataRightBalance.type) &&
!activity.hasStreamData(data_power_right_1.DataPowerRight.type)) {
var rightPowerStream = activity.createStream(data_power_right_1.DataPowerRight.type);
var powerStreamData_1 = activity.getStreamData(data_power_1.DataPower.type);
var rightBalanceStreamData = activity.getStreamData(data_right_balance_1.DataRightBalance.type);
rightPowerStream.setData(rightBalanceStreamData.reduce(function (accu, streamData, index) {
var powerStreamDataItem = powerStreamData_1[index];
if (streamData === null || !powerStreamData_1 || powerStreamDataItem === null) {
return accu;
}
accu[index] = (streamData / 100) * powerStreamDataItem;
return accu;
}, []));
activity.addStream(rightPowerStream);
}
if (activity.hasStreamData(data_power_1.DataPower.type) &&
activity.hasStreamData(data_left_balance_1.DataLeftBalance.type) &&
!activity.hasStreamData(data_power_left_1.DataPowerLeft.type)) {
var leftPowerStream = activity.createStream(data_power_left_1.DataPowerLeft.type);
var powerStreamData_2 = activity.getStreamData(data_power_1.DataPower.type);
var leftBalanceStreamData = activity.getStreamData(data_left_balance_1.DataLeftBalance.type);
leftPowerStream.setData(leftBalanceStreamData.reduce(function (accu, streamData, index) {
var powerStreamDataItem = powerStreamData_2[index];
if (streamData === null || !powerStreamData_2 || powerStreamDataItem === null) {
return accu;
}
accu[index] = (streamData / 100) * powerStreamDataItem;
return accu;
}, []));
activity.addStream(leftPowerStream);
}
return activity;
};
/**
* Back and forth fills an activity's stream data so they can be more "tree" like
* It does this for:
*
* [DataAltitude.type,
* DataHeartRate.type,
* DataCadence.type,
* DataDistance.type]
*
* Example
*
* Distance[0, 10, 30, 40, 50,null,60] #null here is legit eg missing record
* Altitude[100, 101, null, 103, null, null, 106]
* Should be
* Altitude[100,101,101,103,103,103,106]
*
* @param activity
*/
ActivityUtilities.addMissingDataToStreams = function (activity) {
/**
* This tries to align data with Strava.
* Strava fills HR alti cadence with the last value.
* For Power and temperature it doesn't but keeps nulls.
* However, if you keep nulls for paused portions then strava doens't give back null
* that typically indicates a sensor disconnect I suppose.
*/
var streamTypesToBackAndForthFill = [
data_altitude_1.DataAltitude.type,
data_heart_rate_1.DataHeartRate.type,
data_cadence_1.DataCadence.type,
data_distance_1.DataDistance.type
// DataSpeed.type, @todo should we be backfilling speed?
];
// First generate the time stream
var timeStream = activity.generateTimeStream();
/**
* We do a second pass here and we add missing data on crossing time indexes
* for example:
* Time[0,1,2,3,4,5,7]
* Distance[0, 10, 30, 40, 50,null,60] #null here is legit eg missing record
* Altitude[100, 101, null, 103, null, null, 106]
* Should be
* Altitude[100,101,101,103,103,103,106]
*/
activity
.getAllStreams()
.filter(function (stream) { return streamTypesToBackAndForthFill.indexOf(stream.type) !== -1; })
.forEach(function (stream) {
// Find the first sample value
var currentValue = stream.getData(true, true)[0];
// The time stream will always have more length than each stream when not back/forthfilled
var timeStreamData = timeStream.getData();
stream.setData(timeStreamData.reduce(function (data, time, timeIndex) {
// If there is no timeslot put whatever was
if (!helpers_1.isNumber(time)) {
data.push(stream.getData()[timeIndex]);
return data;
}
// We have a time slot here on ... (for the first run, old is the very first next)
// If it's a number set the current , else leave it to old to forth fill
if (helpers_1.isNumber(stream.getData()[time])) {
currentValue = stream.getData()[time];
}
// Fill the current or old...
data.push(currentValue);
return data;
}, []));
});
/**
* @todo
* Linear fill distance where:
* a) There is not distance but it's not paused
* b) There is no corespoding lat/long but there is distace (aka distance = not trusted)
* About B I am not sure. That is because if there is for example an internal accelerometer
* that reports better this can help with pace and other things. Even for GAP
*/
};
ActivityUtilities.getActivityDataTypeGainOrLoss = function (activity, streamType, gain, startDate, endDate, minDiff) {
return this.getGainOrLoss(activity.getSquashedStreamData(streamType, startDate, endDate), gain, minDiff);
};
ActivityUtilities.getActivityDataTypeMinOrMax = function (activity, streamType, max, startDate, endDate) {
var data = activity
.getSquashedStreamData(streamType, startDate, endDate)
.filter(function (streamData) { return streamData !== Infinity && streamData !== -Infinity; });
if (max) {
return this.getMax(data);
}
return this.getMin(data);
};
/**
* Generates the stats for an activity
* @todo move to factory with next version
* @param activity
*/
ActivityUtilities.generateMissingStatsForActivity = function (activity) {
// If there is no distance or distance for some reason is 0
var activityDistanceStat = activity.getStat(data_distance_1.DataDistance.type);
if (!activityDistanceStat || activityDistanceStat.getValue() === 0) {
var distance = 0;
if (activity.hasStreamData(data_distance_1.DataDistance.type)) {
var distanceData = activity.getSquashedStreamData(data_distance_1.DataDistance.type);
distance = distanceData[distanceData.length - 1] - distanceData[0] || 0;
}
else if (activity.hasStreamData(data_longitude_degrees_1.DataLongitudeDegrees.type) &&
activity.hasStreamData(data_latitude_degrees_1.DataLatitudeDegrees.type)) {
distance = this.calculateTotalDistanceForActivity(activity, activity.startDate, activity.endDate);
}
activity.addStat(new data_distance_1.DataDistance(distance));
}
if (!activity.getStat(data_gnss_distance_1.DataGNSSDistance.type) && activity.hasStreamData(data_gnss_distance_1.DataGNSSDistance.type)) {
activity.addStat(new data_gnss_distance_1.DataGNSSDistance(activity.getSquashedStreamData(data_gnss_distance_1.DataGNSSDistance.type)[activity.getSquashedStreamData(data_gnss_distance_1.DataGNSSDistance.type).length - 1]));
}
// Ascent (altitude gain)
if (!activity.getStat(data_ascent_1.DataAscent.type) && activity.hasStreamData(data_altitude_1.DataAltitude.type)) {
activity.addStat(new data_ascent_1.DataAscent(this.getActivityDataTypeGain(activity, data_altitude_1.DataAltitude.type)));
}
// Descent (altitude loss)
if (!activity.getStat(data_descent_1.DataDescent.type) && activity.hasStreamData(data_altitude_1.DataAltitude.type)) {
activity.addStat(new data_descent_1.DataDescent(this.getActivityDataTypeLoss(activity, data_altitude_1.DataAltitude.type)));
}
// Altitude Max
if (!activity.getStat(data_altitude_max_1.DataAltitudeMax.type) && activity.hasStreamData(data_altitude_1.DataAltitude.type)) {
activity.addStat(new data_altitude_max_1.DataAltitudeMax(this.getDataTypeMax(activity, data_altitude_1.DataAltitude.type)));
}
// Altitude Min
if (!activity.getStat(data_altitude_min_1.DataAltitudeMin.type) && activity.hasStreamData(data_altitude_1.DataAltitude.type)) {
activity.addStat(new data_altitude_min_1.DataAltitudeMin(this.getDataTypeMin(activity, data_altitude_1.DataAltitude.type)));
}
// Altitude Avg
if (!activity.getStat(data_altitude_avg_1.DataAltitudeAvg.type) && activity.hasStreamData(data_altitude_1.DataAltitude.type)) {
activity.addStat(new data_altitude_avg_1.DataAltitudeAvg(this.getDataTypeAvg(activity, data_altitude_1.DataAltitude.type)));
}
// Altitude start
if (!activity.getStat(data_start_altitude_1.DataStartAltitude.type) && activity.hasStreamData(data_altitude_1.DataAltitude.type)) {
activity.addStat(new data_start_altitude_1.DataStartAltitude(this.getDataTypeFirst(activity, data_altitude_1.DataAltitude.type)));
}
// Altitude end
if (!activity.getStat(data_end_altitude_1.DataEndAltitude.type) && activity.hasStreamData(data_altitude_1.DataAltitude.type)) {
activity.addStat(new data_end_altitude_1.DataEndAltitude(this.getDataTypeLast(activity, data_altitude_1.DataAltitude.type)));
}
// Heart Rate Max
if (!activity.getStat(data_heart_rate_max_1.DataHeartRateMax.type) && activity.hasStreamData(data_heart_rate_1.DataHeartRate.type)) {
activity.addStat(new data_heart_rate_max_1.DataHeartRateMax(this.getDataTypeMax(activity, data_heart_rate_1.DataHeartRate.type)));
}
// Heart Rate Min
if (!activity.getStat(data_heart_rate_min_1.DataHeartRateMin.type) && activity.hasStreamData(data_heart_rate_1.DataHeartRate.type)) {
activity.addStat(new data_heart_rate_min_1.DataHeartRateMin(this.getDataTypeMin(activity, data_heart_rate_1.DataHeartRate.type)));
}
// Heart Rate Avg
if (!activity.getStat(data_heart_rate_avg_1.DataHeartRateAvg.type) && activity.hasStreamData(data_heart_rate_1.DataHeartRate.type)) {
activity.addStat(new data_heart_rate_avg_1.DataHeartRateAvg(this.getDataTypeAvg(activity, data_heart_rate_1.DataHeartRate.type)));
}
// Cadence Max
if (!activ