@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
948 lines • 216 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ActivityUtilities = void 0;
const data_heart_rate_1 = require("../../data/data.heart-rate");
const data_max_hr_setting_1 = require("../../data/data.max-hr-setting");
const data_cadence_1 = require("../../data/data.cadence");
const data_speed_1 = require("../../data/data.speed");
const data_weight_1 = require("../../data/data.weight");
const data_gender_1 = require("../../data/data.gender");
const data_vertical_speed_1 = require("../../data/data.vertical-speed");
const data_temperature_1 = require("../../data/data.temperature");
const data_absolute_pressure_1 = require("../../data/data.absolute-pressure");
const data_evpe_1 = require("../../data/data.evpe");
const data_evpe_min_1 = require("../../data/data.evpe-min");
const data_evpe_max_1 = require("../../data/data.evpe-max");
const data_evpe_avg_1 = require("../../data/data.evpe-avg");
const data_ehpe_1 = require("../../data/data.ehpe");
const data_ehpe_min_1 = require("../../data/data.ehpe-min");
const data_ehpe_max_1 = require("../../data/data.ehpe-max");
const data_ehpe_avg_1 = require("../../data/data.ehpe-avg");
const data_satellite_5_best_snr_1 = require("../../data/data.satellite-5-best-snr");
const data_satellite_5_best_snr_min_1 = require("../../data/data.satellite-5-best-snr-min");
const data_satellite_5_best_snr_max_1 = require("../../data/data.satellite-5-best-snr-max");
const data_satellite_5_best_snr_avg_1 = require("../../data/data.satellite-5-best-snr-avg");
const data_number_of_satellites_1 = require("../../data/data.number-of-satellites");
const data_number_of_satellites_min_1 = require("../../data/data.number-of-satellites-min");
const data_number_of_satellites_max_1 = require("../../data/data.number-of-satellites-max");
const data_number_of_satellites_avg_1 = require("../../data/data.number-of-satellites-avg");
const data_altitude_1 = require("../../data/data.altitude");
const data_power_1 = require("../../data/data.power");
const data_altitude_max_1 = require("../../data/data.altitude-max");
const data_altitude_min_1 = require("../../data/data.altitude-min");
const data_altitude_avg_1 = require("../../data/data.altitude-avg");
const data_heart_rate_max_1 = require("../../data/data.heart-rate-max");
const data_heart_rate_min_1 = require("../../data/data.heart-rate-min");
const data_heart_rate_avg_1 = require("../../data/data.heart-rate-avg");
const data_cadence_max_1 = require("../../data/data.cadence-max");
const data_cadence_min_1 = require("../../data/data.cadence-min");
const data_cadence_avg_1 = require("../../data/data.cadence-avg");
const data_speed_max_1 = require("../../data/data.speed-max");
const data_speed_min_1 = require("../../data/data.speed-min");
const data_speed_avg_1 = require("../../data/data.speed-avg");
const data_vertical_speed_max_1 = require("../../data/data.vertical-speed-max");
const data_vertical_speed_min_1 = require("../../data/data.vertical-speed-min");
const data_vertical_speed_avg_1 = require("../../data/data.vertical-speed-avg");
const data_power_max_1 = require("../../data/data.power-max");
const data_power_min_1 = require("../../data/data.power-min");
const data_power_avg_1 = require("../../data/data.power-avg");
const data_temperature_max_1 = require("../../data/data.temperature-max");
const data_temperature_min_1 = require("../../data/data.temperature-min");
const data_temperature_avg_1 = require("../../data/data.temperature-avg");
const data_absolute_pressure_min_1 = require("../../data/data.absolute-pressure-min");
const data_absolute_pressure_max_1 = require("../../data/data.absolute-pressure-max");
const data_absolute_pressure_avg_1 = require("../../data/data.absolute-pressure-avg");
const data_distance_1 = require("../../data/data.distance");
const data_duration_1 = require("../../data/data.duration");
const data_pause_1 = require("../../data/data.pause");
const data_ascent_1 = require("../../data/data.ascent");
const data_descent_1 = require("../../data/data.descent");
const geolib_adapter_1 = require("../../geodesy/adapters/geolib.adapter");
const data_pace_max_1 = require("../../data/data.pace-max");
const data_pace_1 = require("../../data/data.pace");
const data_pace_min_1 = require("../../data/data.pace-min");
const data_pace_avg_1 = require("../../data/data.pace-avg");
const data_effort_pace_1 = require("../../data/data.effort-pace");
const data_effort_pace_avg_1 = require("../../data/data.effort-pace-avg");
const data_effort_pace_min_1 = require("../../data/data.effort-pace-min");
const data_effort_pace_max_1 = require("../../data/data.effort-pace-max");
const data_battery_charge_1 = require("../../data/data.battery-charge");
const data_battery_consumption_1 = require("../../data/data.battery-consumption");
const data_battery_life_estimation_1 = require("../../data/data.battery-life-estimation");
const data_latitude_degrees_1 = require("../../data/data.latitude-degrees");
const stream_1 = require("../../streams/stream");
const helpers_1 = require("./helpers");
const data_longitude_degrees_1 = require("../../data/data.longitude-degrees");
const stream_derivation_registry_1 = require("../../streams/stream.derivation.registry");
const data_energy_1 = require("../../data/data.energy");
const data_start_altitude_1 = require("../../data/data.start-altitude");
const data_end_altitude_1 = require("../../data/data.end-altitude");
const data_swim_pace_max_1 = require("../../data/data.swim-pace-max");
const data_swim_pace_1 = require("../../data/data.swim-pace");
const data_swim_pace_min_1 = require("../../data/data.swim-pace-min");
const data_swim_pace_avg_1 = require("../../data/data.swim-pace-avg");
const data_feeling_1 = require("../../data/data.feeling");
const data_power_watts_per_kg_1 = require("../../data/data.power-watts-per-kg");
const data_critical_power_1 = require("../../data/data.critical-power");
const data_w_prime_1 = require("../../data/data.w-prime");
const data_ftp_1 = require("../../data/data.ftp");
const data_power_left_1 = require("../../data/data.power-left");
const data_power_balance_left_1 = require("../../data/data.power-balance-left");
const data_power_balance_right_1 = require("../../data/data.power-balance-right");
const data_power_right_1 = require("../../data/data.power-right");
const data_air_power_min_1 = require("../../data/data.air-power-min");
const data_air_power_1 = require("../../data/data.air-power");
const data_air_power_max_1 = require("../../data/data.air-power-max");
const data_air_power_avg_1 = require("../../data/data.air-power-avg");
const data_leg_stiffness_1 = require("../../data/data.leg-stiffness");
const data_leg_stiffness_min_1 = require("../../data/data.leg-stiffness-min");
const data_leg_stiffness_max_1 = require("../../data/data.leg-stiffness-max");
const data_leg_stiffness_avg_1 = require("../../data/data.leg-stiffness-avg");
const data_rpe_1 = require("../../data/data.rpe");
const data_gnss_distance_1 = require("../../data/data.gnss-distance");
const data_heart_rate_zone_one_duration_1 = require("../../data/data.heart-rate-zone-one-duration");
const data_heart_rate_zone_two_duration_1 = require("../../data/data.heart-rate-zone-two-duration");
const data_heart_rate_zone_three_duration_1 = require("../../data/data.heart-rate-zone-three-duration");
const data_heart_rate_zone_four_duration_1 = require("../../data/data.heart-rate-zone-four-duration");
const data_heart_rate_zone_five_duration_1 = require("../../data/data.heart-rate-zone-five-duration");
const data_power_zone_one_duration_1 = require("../../data/data.power-zone-one-duration");
const data_power_zone_two_duration_1 = require("../../data/data.power-zone-two-duration");
const data_power_zone_three_duration_1 = require("../../data/data.power-zone-three-duration");
const data_power_zone_four_duration_1 = require("../../data/data.power-zone-four-duration");
const data_power_zone_five_duration_1 = require("../../data/data.power-zone-five-duration");
const data_power_zone_six_duration_1 = require("../../data/data.power-zone-six-duration");
const data_power_zone_seven_duration_1 = require("../../data/data.power-zone-seven-duration");
const data_speed_zone_one_duration_1 = require("../../data/data.speed-zone-one-duration");
const data_speed_zone_two_duration_1 = require("../../data/data.speed-zone-two-duration");
const data_speed_zone_three_duration_1 = require("../../data/data.speed-zone-three-duration");
const data_speed_zone_four_duration_1 = require("../../data/data.speed-zone-four-duration");
const data_speed_zone_five_duration_1 = require("../../data/data.speed-zone-five-duration");
const data_ground_contact_time_1 = require("../../data/data.ground-contact-time");
const data_ground_contact_time_avg_1 = require("../../data/data.ground-contact-time-avg");
const data_ground_contact_time_max_1 = require("../../data/data.ground-contact-time-max");
const data_ground_contact_time_min_1 = require("../../data/data.ground-contact-time-min");
const data_ground_contact_time_balance_left_1 = require("../../data/data-ground-contact-time-balance-left");
const data_ground_contact_time_balance_right_1 = require("../../data/data-ground-contact-time-balance-right");
const data_running_dynamics_balance_1 = require("../../data/data.running-dynamics-balance");
const data_vertical_oscillation_1 = require("../../data/data.vertical-oscillation");
const data_vertical_oscillation_avg_1 = require("../../data/data.vertical-oscillation-avg");
const data_vertical_oscillation_max_1 = require("../../data/data.vertical-oscillation-max");
const data_vertical_oscillation_min_1 = require("../../data/data.vertical-oscillation-min");
const data_vertical_ratio_1 = require("../../data/data.vertical-ratio");
const data_vertical_ratio_min_1 = require("../../data/data.vertical-ratio-min");
const data_vertical_ratio_max_1 = require("../../data/data.vertical-ratio-max");
const data_vertical_ratio_avg_1 = require("../../data/data.vertical-ratio-avg");
const data_stamina_1 = require("../../data/data.stamina");
const data_stance_time_balance_left_1 = require("../../data/data-stance-time-balance-left");
const data_stance_time_balance_right_1 = require("../../data/data-stance-time-balance-right");
const data_store_1 = require("../../data/data.store");
const data_start_position_1 = require("../../data/data.start-position");
const data_end_position_1 = require("../../data/data.end-position");
const data_grade_adjusted_speed_avg_1 = require("../../data/data.grade-adjusted-speed-avg");
const data_grade_adjusted_pace_avg_1 = require("../../data/data.grade-adjusted-pace-avg");
const data_grade_adjusted_speed_1 = require("../../data/data.grade-adjusted-speed");
const data_grade_adjusted_pace_1 = require("../../data/data.grade-adjusted-pace");
const data_grade_adjusted_speed_max_1 = require("../../data/data.grade-adjusted-speed-max");
const data_grade_adjusted_speed_min_1 = require("../../data/data.grade-adjusted-speed-min");
const data_grade_adjusted_pace_max_1 = require("../../data/data.grade-adjusted-pace-max");
const data_grade_adjusted_pace_min_1 = require("../../data/data.grade-adjusted-pace-min");
const data_grade_1 = require("../../data/data.grade");
const data_grade_min_1 = require("../../data/data.grade-min");
const data_grade_max_1 = require("../../data/data.grade-max");
const data_grade_avg_1 = require("../../data/data.grade-avg");
const activity_types_1 = require("../../activities/activity.types");
const data_moving_time_1 = require("../../data/data.moving-time");
const data_timer_time_1 = require("../../data/data.timer-time");
const data_elapsed_time_1 = require("../../data/data.elapsed-time");
const data_altitude_smooth_1 = require("../../data/data.altitude-smooth");
const data_grade_smooth_1 = require("../../data/data.grade-smooth");
const data_swolf_25m_1 = require("../../data/data.swolf-25m");
const data_swolf_50m_1 = require("../../data/data.swolf-50m");
const low_pass_filter_1 = require("./grade-calculator/low-pass-filter");
const data_power_intensity_factor_1 = require("../../data/data.power-intensity-factor");
const data_power_normalized_1 = require("../../data/data.power-normalized");
const data_training_stress_score_1 = require("../../data/data.training-stress-score");
const data_training_stress_score_method_1 = require("../../data/data.training-stress-score-method");
const data_power_work_1 = require("../../data/data.power-work");
const grade_calculator_1 = require("./grade-calculator/grade-calculator");
const data_vo2_max_1 = require("../../data/data.vo2-max");
const data_total_grit_1 = require("../../data/data.total-grit");
const data_total_flow_1 = require("../../data/data.total-flow");
const data_jump_count_1 = require("../../data/data.jump-count");
const data_jump_event_1 = require("../../data/data.jump-event");
const data_jump_stats_1 = require("../../data/data.jump-stats");
const data_power_curve_1 = require("../../data/data.power-curve");
const data_durability_evidence_1 = require("../../data/data.durability-evidence");
const tss_calculator_1 = require("./tss/tss-calculator");
const activity_durability_1 = require("./activity-durability");
// @ts-ignore
const kalmanjs_1 = __importDefault(require("kalmanjs"));
/* Configure filtering values */
// Altitude stream
const ALTITUDE_SPIKES_FILTER_WIN = 3;
// Fix abnormal streams
const SPEED_STREAM_STD_DEV_THRESHOLD_DEFAULT = 25 / 3.6; // Kph to mps
const SPEED_STREAM_STD_DEV_THRESHOLD_MAP = new Map([
[activity_types_1.ActivityTypeGroups.RunningGroup, 15 / 3.6], // kph to m/s
[activity_types_1.ActivityTypeGroups.CyclingGroup, 27 / 3.6], // kph to m/s
[activity_types_1.ActivityTypeGroups.MountainBikingGroup, 27 / 3.6], // kph to m/s
[activity_types_1.ActivityTypeGroups.SwimmingGroup, 5 / 3.6] // kph to m/s
]);
class ActivityUtilities {
/**
* Provide average from laps a given stat type
*/
static getDataTypeAvgFromLaps(activity, statType, filterOver) {
const data = activity
.getLaps()
.map(lap => { var _a; return (_a = lap.getStat(statType)) === null || _a === void 0 ? void 0 : _a.getValue(); })
.filter(d => Number.isFinite(d) && (Number.isFinite(filterOver) ? d > filterOver : true));
if (data.length > 0) {
return this.getAverage(data);
}
return null;
}
/**
* Provide average of a given stream type
*/
static getDataTypeAvg(activity, streamType, startDate, endDate, filterOver) {
const data = (activity
.getSquashedStreamData(streamType, startDate, endDate)
.filter(streamData => streamData !== Infinity &&
streamData !== -Infinity &&
(Number.isFinite(filterOver) ? streamData > filterOver : true)));
return this.getAverage(data);
}
static round(value, decimals = 0) {
const decimalsFactor = Math.pow(10, decimals);
return Math.round(value * decimalsFactor) / decimalsFactor;
}
static addLeftRightBalanceStatsFromStreams(activity, LeftDataClass, RightDataClass) {
const leftStat = activity.getStat(LeftDataClass.type);
const rightStat = activity.getStat(RightDataClass.type);
const leftStatValue = leftStat === null || leftStat === void 0 ? void 0 : leftStat.getValue();
const rightStatValue = rightStat === null || rightStat === void 0 ? void 0 : rightStat.getValue();
if (leftStat && rightStat) {
return;
}
if (leftStat && !rightStat && typeof leftStatValue === 'number') {
activity.addStat(new RightDataClass(this.round(100 - leftStatValue, 2)));
return;
}
if (rightStat && !leftStat && typeof rightStatValue === 'number') {
activity.addStat(new LeftDataClass(this.round(100 - rightStatValue, 2)));
return;
}
if (activity.hasStreamData(LeftDataClass.type)) {
const avgLeftBalance = this.round(this.getDataTypeAvg(activity, LeftDataClass.type), 2);
activity.addStat(new LeftDataClass(avgLeftBalance));
activity.addStat(new RightDataClass(this.round(100 - avgLeftBalance, 2)));
return;
}
if (activity.hasStreamData(RightDataClass.type)) {
const avgRightBalance = this.round(this.getDataTypeAvg(activity, RightDataClass.type), 2);
activity.addStat(new RightDataClass(avgRightBalance));
activity.addStat(new LeftDataClass(this.round(100 - avgRightBalance, 2)));
}
}
static getAverage(data) {
return this.getSum(data) / data.length;
}
static getSum(data) {
return data.reduce((sumbuff, value) => {
sumbuff += value;
return sumbuff;
}, 0);
}
static getDataTypeMax(activity, streamType, startDate, endDate) {
return this.getActivityDataTypeMinOrMax(activity, streamType, true, startDate, endDate);
}
static getDataTypeMin(activity, streamType, startDate, endDate, filterOver) {
return this.getActivityDataTypeMinOrMax(activity, streamType, false, startDate, endDate, filterOver);
}
static getDataTypeMinToMaxDifference(activity, streamType, startDate, endDate) {
return (this.getDataTypeMax(activity, streamType, startDate, endDate) -
this.getDataTypeMin(activity, streamType, startDate, endDate));
}
static getDataTypeFirst(activity, streamType, startDate, endDate) {
const data = activity.getSquashedStreamData(streamType, startDate, endDate);
return data[0];
}
static getDataTypeLast(activity, streamType, startDate, endDate) {
const data = activity.getSquashedStreamData(streamType, startDate, endDate);
return data[data.length - 1];
}
static cropDistance(startDistance, endDistance, activity) {
// Short to do the search just in case
let startDistanceDate; // Does not sound right
let endDistanceDate;
// debugger;
activity.getStreamData(data_distance_1.DataDistance.type).forEach((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
const distanceStream = activity.getAllStreams().find(s => data_distance_1.DataDistance.type === s.type);
if (distanceStream) {
activity.removeStream(distanceStream);
}
const gnssDistanceStream = activity.getAllStreams().find(s => 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
*/
static cropTime(activity, startDate, endDate) {
activity.getAllStreams().forEach(stream => {
// Get the data for the range specified
const 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;
}
static getStreamDataTypesBasedOnDataType(streamToBaseOn, streams) {
return streamToBaseOn.getData().reduce((accu, streamDataItem, index) => {
if (!(0, helpers_1.isNumberOrString)(streamDataItem)) {
return accu;
}
const dataItem = {
[streamToBaseOn.type]: streamDataItem
};
streams.forEach(stream => {
dataItem[stream.type] = stream.getData()[index];
});
accu.push(dataItem);
return accu;
}, []);
}
static getStreamDataTypesBasedOnTime(startDate, endDate, streams) {
const streamDataBasedOnTime = {};
for (let i = 0; i < this.getDataLength(startDate, endDate); i++) {
// Perhaps this can be optimized with a search function
streams.forEach((stream) => {
if ((0, 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];
}
});
}
return streamDataBasedOnTime;
}
static getDataLength(startDate, endDate) {
return Math.ceil((+endDate - +startDate) / 1000) + 1;
}
static createJumpAggregate() {
return {
min: Infinity,
max: -Infinity,
sum: 0,
count: 0
};
}
static getJumpEvents(activity) {
const maybeGetAllEvents = activity.getAllEvents;
if (typeof maybeGetAllEvents !== 'function') {
return [];
}
return maybeGetAllEvents
.call(activity)
.filter((event) => {
if (!event || typeof event.getType !== 'function') {
return false;
}
if (event instanceof data_jump_event_1.DataJumpEvent) {
return true;
}
const typedEvent = event;
return typedEvent.getType() === data_jump_event_1.DataJumpEvent.type && typedEvent.jumpData !== undefined;
})
.map(event => event);
}
static getFiniteStatValue(activity, statType) {
const stat = activity.getStat(statType);
if (!stat) {
return null;
}
const value = stat.getValue();
return typeof value === 'number' && Number.isFinite(value) ? value : null;
}
static getJumpAggregatesFromEvents(jumpEvents) {
const aggregates = new Map(this.jumpStatFamilies.map(family => [family.key, this.createJumpAggregate()]));
jumpEvents.forEach(jumpEvent => {
this.jumpStatFamilies.forEach(family => {
const value = family.getEventValue(jumpEvent);
if (typeof value === 'number' && Number.isFinite(value)) {
const aggregate = aggregates.get(family.key);
aggregate.min = Math.min(aggregate.min, value);
aggregate.max = Math.max(aggregate.max, value);
aggregate.sum += value;
aggregate.count += 1;
}
});
});
return aggregates;
}
static addMissingJumpStatsFromEvents(activity) {
const jumpEvents = this.getJumpEvents(activity);
if (!jumpEvents.length) {
return;
}
const aggregates = this.getJumpAggregatesFromEvents(jumpEvents);
this.jumpStatFamilies.forEach(family => {
const aggregate = aggregates.get(family.key);
if (aggregate.count <= 0) {
return;
}
if (!activity.getStat(family.minType)) {
activity.addStat(family.createMin(aggregate.min));
}
if (!activity.getStat(family.maxType)) {
activity.addStat(family.createMax(aggregate.max));
}
if (!activity.getStat(family.avgType)) {
activity.addStat(family.createAvg(aggregate.sum / aggregate.count));
}
});
}
static getSummaryJumpStatsForActivities(activities) {
const summaryAggregates = new Map(this.jumpStatFamilies.map(family => [family.key, this.createJumpAggregate()]));
activities.forEach(activity => {
const jumpEvents = this.getJumpEvents(activity);
if (jumpEvents.length > 0) {
const eventAggregates = this.getJumpAggregatesFromEvents(jumpEvents);
this.jumpStatFamilies.forEach(family => {
const eventAggregate = eventAggregates.get(family.key);
if (eventAggregate.count <= 0) {
return;
}
const summaryAggregate = summaryAggregates.get(family.key);
summaryAggregate.min = Math.min(summaryAggregate.min, eventAggregate.min);
summaryAggregate.max = Math.max(summaryAggregate.max, eventAggregate.max);
summaryAggregate.sum += eventAggregate.sum;
summaryAggregate.count += eventAggregate.count;
});
return;
}
const jumpCount = this.getFiniteStatValue(activity, data_jump_count_1.DataJumpCount.type);
this.jumpStatFamilies.forEach(family => {
const summaryAggregate = summaryAggregates.get(family.key);
const minValue = this.getFiniteStatValue(activity, family.minType);
if (minValue !== null) {
summaryAggregate.min = Math.min(summaryAggregate.min, minValue);
}
const maxValue = this.getFiniteStatValue(activity, family.maxType);
if (maxValue !== null) {
summaryAggregate.max = Math.max(summaryAggregate.max, maxValue);
}
const avgValue = this.getFiniteStatValue(activity, family.avgType);
if (avgValue !== null) {
const weight = jumpCount !== null ? Math.max(0, jumpCount) : 1;
summaryAggregate.sum += avgValue * weight;
summaryAggregate.count += weight;
}
});
});
const stats = [];
this.jumpStatFamilies.forEach(family => {
const aggregate = summaryAggregates.get(family.key);
if (aggregate.min !== Infinity) {
stats.push(family.createMin(aggregate.min));
}
if (aggregate.max !== -Infinity) {
stats.push(family.createMax(aggregate.max));
}
if (aggregate.count > 0) {
stats.push(family.createAvg(aggregate.sum / aggregate.count));
}
});
return stats;
}
static generateMissingStreamsAndStatsForActivity(activity) {
var _a;
this.generateMissingStreams(activity);
this.fixAbnormalStreamData(activity);
this.generateMissingStatsForActivity(activity);
this.generateMissingSpeedDerivedStatsForActivity(activity);
const existingDurability = (_a = activity.getStat(data_durability_evidence_1.DataDurabilityEvidence.type)) === null || _a === void 0 ? void 0 : _a.getValue();
const canonicalExistingDurability = (0, data_durability_evidence_1.normalizeDurabilityEvidenceValue)(existingDurability);
const hasSourceData = canonicalExistingDurability ? (0, activity_durability_1.hasActivityDurabilitySourceData)(activity) : false;
const canReuseExistingDurability = !!canonicalExistingDurability &&
(!hasSourceData ||
canonicalExistingDurability.sourceFingerprint === (0, activity_durability_1.calculateActivityDurabilitySourceFingerprint)(activity));
const existingIsCanonical = !!canonicalExistingDurability &&
JSON.stringify(existingDurability) === JSON.stringify(canonicalExistingDurability);
if (!canReuseExistingDurability) {
activity.removeStat(data_durability_evidence_1.DataDurabilityEvidence.type);
const durability = (0, activity_durability_1.analyzeActivityDurability)(activity, { includeTimeline: false }).summary;
if (durability) {
activity.addStat(new data_durability_evidence_1.DataDurabilityEvidence(durability));
}
}
else if (!existingIsCanonical) {
activity.removeStat(data_durability_evidence_1.DataDurabilityEvidence.type);
activity.addStat(new data_durability_evidence_1.DataDurabilityEvidence(canonicalExistingDurability));
}
this.generateMissingUnitStatsForActivity(activity); // Perhaps this needs to happen on user level so needs to go out of here
}
static fixAbnormalStreamData(activity) {
var _a, _b, _c;
// Check if fix abnormal speed option has been enable and if we have stream data and position data (e.g. do not fix for swim pool activities)
if (((_c = (_b = (_a = activity.parseOptions) === null || _a === void 0 ? void 0 : _a.streams) === null || _b === void 0 ? void 0 : _b.fixAbnormal) === null || _c === void 0 ? void 0 : _c.speed) &&
activity.hasStreamData(data_speed_1.DataSpeed.type) &&
activity.hasStreamData(data_latitude_degrees_1.DataLatitudeDegrees.type) &&
activity.hasStreamData(data_longitude_degrees_1.DataLongitudeDegrees.type)) {
// Check for speed data dispersion using standard deviation
const speedStdDev = (0, helpers_1.standardDeviation)(activity.getSquashedStreamData(data_speed_1.DataSpeed.type));
// Get speed standard deviation threshold at which we will attempt to fix the stream
const stdDevThreshold = SPEED_STREAM_STD_DEV_THRESHOLD_MAP.get(activity_types_1.ActivityTypesHelper.getActivityGroupForActivityType(activity.type)) ||
SPEED_STREAM_STD_DEV_THRESHOLD_DEFAULT;
if (speedStdDev > stdDevThreshold) {
// Fix/Predict speed stream through Kalman filtering
this.shapeStream(data_speed_1.DataSpeed.type, activity, squashedSpeedData => {
// Grade stream
const SPEED_KALMAN_SMOOTHING = {
R: 0.01, // Speed model calculation is something stable
Q: speedStdDev * 2 // We intend to get a measurement error which can be under and over std dev (explaining the double factor)
};
// Apply kalman filter
const kf = new kalmanjs_1.default(SPEED_KALMAN_SMOOTHING);
return squashedSpeedData.map(v => (v === null ? null : kf.filter(v)));
});
}
}
}
static generateMissingStreams(activity) {
// Compute missing streams
this.generateMissingStreamsForActivity(activity);
// Always include derived base streams (like Pace), but conditionally include unit variants
const includeUnitVariants = !activity.parseOptions || activity.parseOptions.generateUnitStreams;
activity.addStreams(this.createUnitStreamsFromStreams(activity.getAllStreams(), activity.type, undefined, {
includeDerivedTypes: true, // Always include derived base types (Pace etc)
includeUnitVariants
}));
}
static getSummaryStatsForActivities(activities) {
const stats = [];
// If only one
if (activities.length === 1) {
return activities[0].getStatsAsArray().filter(stat => stat.getType() !== data_durability_evidence_1.DataDurabilityEvidence.type);
}
let duration = 0;
let ascent = 0;
let descent = 0;
let energy = 0;
let distance = 0;
let pauseTime = 0;
let averageHeartRate = 0;
let averagePower = 0;
let averagePowerWattsPerKg = 0;
let hasAveragePowerWattsPerKg = false;
let averagePowerNormalized = 0;
let hasAveragePowerNormalized = false;
let averageCadence = 0;
let averageSpeed = 0;
let averageGradeAdjustedSpeed = 0;
let averagePace = 0;
let averageEffortPace = 0;
let averageGradeAdjustedPace = 0;
let averageSwimPace = 0;
let averageTemperature = 0;
let averageAbsolutePressure = 0;
let averageEVPE = 0;
let hasAverageEVPE = false;
let averageEHPE = 0;
let hasAverageEHPE = false;
let averageSatellite5BestSNR = 0;
let hasAverageSatellite5BestSNR = false;
let averageNumberOfSatellites = 0;
let hasAverageNumberOfSatellites = false;
let averageGrade = 0;
let hasAverageGrade = false;
let averageAirPower = 0;
let averageVerticalSpeed = 0;
let averageAltitude = 0;
let averageLegStiffness = 0;
let hasAverageLegStiffness = false;
let averageVerticalRatio = 0;
let hasAverageVerticalRatio = false;
let averageVerticalOscillation = 0;
let hasAverageVerticalOscillation = false;
let averageFeeling = 0;
let averageRPE = 0;
// Sum Duration
activities.forEach(activity => {
duration += activity.getDuration().getValue();
});
stats.push(new data_duration_1.DataDuration(duration));
// Sum pause time
activities.forEach(activity => {
pauseTime += activity.getPause().getValue();
});
stats.push(new data_pause_1.DataPause(pauseTime));
// Sum Distance
activities.forEach(activity => {
distance += activity.getDistance().getValue();
});
stats.push(new data_distance_1.DataDistance(distance));
// Sum ascent
activities.forEach(activity => {
const 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(activity => {
const 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(activity => {
const 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(activity => {
const 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(activity => {
const 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(activity => {
const 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 Power-to-Weight ratio (W/kg)
activities.forEach(activity => {
const activityPowerWattsPerKg = this.resolvePowerWattsPerKg(activity);
if (activityPowerWattsPerKg !== null) {
averagePowerWattsPerKg = hasAveragePowerWattsPerKg
? (averagePowerWattsPerKg + activityPowerWattsPerKg) / 2
: activityPowerWattsPerKg;
hasAveragePowerWattsPerKg = true;
}
});
if (hasAveragePowerWattsPerKg) {
stats.push(new data_power_watts_per_kg_1.DataPowerWattsPerKg(averagePowerWattsPerKg));
}
// Avg Normalized Power
activities.forEach(activity => {
const activityPowerNormalized = activity.getStat(data_power_normalized_1.DataPowerNormalized.type);
if (activityPowerNormalized) {
averagePowerNormalized = hasAveragePowerNormalized
? (averagePowerNormalized + activityPowerNormalized.getValue()) / 2
: activityPowerNormalized.getValue();
hasAveragePowerNormalized = true;
}
});
if (hasAveragePowerNormalized) {
stats.push(new data_power_normalized_1.DataPowerNormalized(averagePowerNormalized));
}
// Avg Avg Cadence
activities.forEach(activity => {
const 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(activity => {
const 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(activity => {
const 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(activity => {
const 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 Effort Pace
activities.forEach(activity => {
const activityAvgEffortPace = activity.getStat(data_effort_pace_avg_1.DataEffortPaceAvg.type);
if (activityAvgEffortPace) {
// The below will fallback for 0
averageEffortPace = averageEffortPace
? (averageEffortPace + activityAvgEffortPace.getValue()) / 2
: activityAvgEffortPace.getValue();
}
});
if (averageEffortPace) {
stats.push(new data_effort_pace_avg_1.DataEffortPaceAvg(averageEffortPace));
}
// Avg Avg GAP Pace
activities.forEach(activity => {
const 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(activity => {
const 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(activity => {
const 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 Avg Absolute Pressure
activities.forEach(activity => {
const activityAvgAbsolutePressure = activity.getStat(data_absolute_pressure_avg_1.DataAbsolutePressureAvg.type);
if (activityAvgAbsolutePressure) {
averageAbsolutePressure = averageAbsolutePressure
? (averageAbsolutePressure + activityAvgAbsolutePressure.getValue()) / 2
: activityAvgAbsolutePressure.getValue();
}
});
if (averageAbsolutePressure) {
stats.push(new data_absolute_pressure_avg_1.DataAbsolutePressureAvg(averageAbsolutePressure));
}
// Avg Avg EVPE
activities.forEach(activity => {
const activityAvgEVPE = activity.getStat(data_evpe_avg_1.DataEVPEAvg.type);
if (activityAvgEVPE) {
averageEVPE = hasAverageEVPE
? (averageEVPE + activityAvgEVPE.getValue()) / 2
: activityAvgEVPE.getValue();
hasAverageEVPE = true;
}
});
if (hasAverageEVPE) {
stats.push(new data_evpe_avg_1.DataEVPEAvg(averageEVPE));
}
// Avg Avg EHPE
activities.forEach(activity => {
const activityAvgEHPE = activity.getStat(data_ehpe_avg_1.DataEHPEAvg.type);
if (activityAvgEHPE) {
averageEHPE = hasAverageEHPE
? (averageEHPE + activityAvgEHPE.getValue()) / 2
: activityAvgEHPE.getValue();
hasAverageEHPE = true;
}
});
if (hasAverageEHPE) {
stats.push(new data_ehpe_avg_1.DataEHPEAvg(averageEHPE));
}
// Avg Avg Satellite 5 Best SNR
activities.forEach(activity => {
const activityAvgSatellite5BestSNR = activity.getStat(data_satellite_5_best_snr_avg_1.DataSatellite5BestSNRAvg.type);
if (activityAvgSatellite5BestSNR) {
averageSatellite5BestSNR = hasAverageSatellite5BestSNR
? (averageSatellite5BestSNR + activityAvgSatellite5BestSNR.getValue()) / 2
: activityAvgSatellite5BestSNR.getValue();
hasAverageSatellite5BestSNR = true;
}
});
if (hasAverageSatellite5BestSNR) {
stats.push(new data_satellite_5_best_snr_avg_1.DataSatellite5BestSNRAvg(averageSatellite5BestSNR));
}
// Avg Avg Number of Satellites
activities.forEach(activity => {
const activityAvgNumberOfSatellites = activity.getStat(data_number_of_satellites_avg_1.DataNumberOfSatellitesAvg.type);
if (activityAvgNumberOfSatellites) {
averageNumberOfSatellites = hasAverageNumberOfSatellites
? (averageNumberOfSatellites + activityAvgNumberOfSatellites.getValue()) / 2
: activityAvgNumberOfSatellites.getValue();
hasAverageNumberOfSatellites = true;
}
});
if (hasAverageNumberOfSatellites) {
stats.push(new data_number_of_satellites_avg_1.DataNumberOfSatellitesAvg(averageNumberOfSatellites));
}
// Avg Grade
activities.forEach(activity => {
const activityAvgGrade = activity.getStat(data_grade_avg_1.DataGradeAvg.type);
if (activityAvgGrade) {
averageGrade = hasAverageGrade
? (averageGrade + activityAvgGrade.getValue()) / 2
: activityAvgGrade.getValue();
hasAverageGrade = true;
}
});
if (hasAverageGrade) {
stats.push(new data_grade_avg_1.DataGradeAvg(averageGrade));
}
// Avg Avg Air Power
activities.forEach(activity => {
const activityAvgAirPower = activity.getStat(data_air_power_avg_1.DataAirPowerAvg.type);
if (activityAvgAirPower) {
averageAirPower = averageAirPower
? (averageAirPower + activityAvgAirPower.getValue()) / 2
: activityAvgAirPower.getValue();
}
});
if (averageAirPower) {
stats.push(new data_air_power_avg_1.DataAirPowerAvg(averageAirPower));
}
// Avg Avg Vertical Speed
activities.forEach(activity => {
const activityAvgVerticalSpeed = activity.getStat(data_vertical_speed_avg_1.DataVerticalSpeedAvg.type);
if (activityAvgVerticalSpeed) {
averageVerticalSpeed = averageVerticalSpeed
? (averageVerticalSpeed + activityAvgVerticalSpeed.getValue()) / 2
: activityAvgVerticalSpeed.getValue();
}
});
if (averageVerticalSpeed) {
stats.push(new data_vertical_speed_avg_1.DataVerticalSpeedAvg(averageVerticalSpeed));
}
// Avg Avg Altitude
activities.forEach(activity => {
const activityAvgAltitude = activity.getStat(data_altitude_avg_1.DataAltitudeAvg.type);
if (activityAvgAltitude) {
averageAltitude = averageAltitude
? (averageAltitude + activityAvgAltitude.getValue()) / 2
: activityAvgAltitude.getValue();
}
});
if (averageAltitude) {
stats.push(new data_altitude_avg_1.DataAltitudeAvg(averageAltitude));
}
// Avg Avg Leg Stiffness
activities.forEach(activity => {
const activityAvgLegStiffness = activity.getStat(data_leg_stiffness_avg_1.DataLegStiffnessAvg.type);
if (activityAvgLegStiffness) {
averageLegStiffness = hasAverageLegStiffness
? (averageLegStiffness + activityAvgLegStiffness.getValue()) / 2
: activityAvgLegStiffness.getValue();
hasAverageLegStiffness = true;
}
});
if (hasAverageLegStiffness) {
stats.push(new data_leg_stiffness_avg_1.DataLegStiffnessAvg(averageLegStiffness));
}
// Avg Avg Vertical Ratio
activities.forEach(activity => {
const activityAvgVerticalRatio = activity.getStat(data_vertical_ratio_avg_1.DataVerticalRatioAvg.type);
if (activityAvgVerticalRatio) {
averageVerticalRatio = hasAverageVerticalRatio
? (averageVerticalRatio + activityAvgVerticalRatio.getValue()) / 2
: activityAvgVerticalRatio.getValue();
hasAverageVerticalRatio = true;
}
});
if (hasAverageVerticalRatio) {
stats.push(new data_vertical_ratio_avg_1.DataVerticalRatioAvg(averageVerticalRatio));
}
// Avg Feeling
activities.forEach(activity => {
const activityAvgFeeling = activity.getStat(data_fe