This repository has been archived on 2024-03-27. You can view files and clone it, but cannot push or open issues/pull-requests.
encrateia/lib/models/record_list.dart

570 lines
17 KiB
Dart

import 'package:encrateia/models/event.dart';
import 'package:encrateia/models/power_zone.dart';
import 'package:encrateia/models/power_zone_schema.dart';
import 'package:encrateia/utils/list_utils.dart';
import 'package:encrateia/utils/map_utils.dart';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:encrateia/models/plot_point.dart';
import 'package:encrateia/utils/enums.dart';
import 'bar_zone.dart';
import 'heart_rate_zone.dart';
import 'heart_rate_zone_schema.dart';
class RecordList<E> extends DelegatingList<E> {
RecordList(List<E> records)
: _records = records as List<Event>,
super(records);
final List<Event> _records;
// AVERAGES:
// Power
double avgPower() {
final Iterable<int> powers = _records
.where((Event record) =>
record.power != null && record.power > 0 && record.power < 2000)
.map((Event record) => record.power);
return powers.isNotEmpty ? powers.mean() : -1;
}
double sdevPower() => _records
.where((Event record) =>
record.power != null && record.power > 0 && record.power < 2000)
.map((Event record) => record.power)
.sdev();
int movingTime() {
int movingTime = 0;
DateTime lastTimestamp;
double lastSpeed = 0;
for (final Event record in _records) {
if (record.event == 'record') {
if (record.speed != null && record.timeStamp != null) {
if (record.speed > 0) {
if (lastSpeed > 0)
movingTime +=
record.timeStamp.difference(lastTimestamp).inSeconds;
else
movingTime += 1;
}
lastTimestamp = record.timeStamp;
lastSpeed = record.speed;
}
} else if (record.event == 'timer' && record.eventType == 'start') {
lastTimestamp = record.timeStamp;
}
}
return movingTime;
}
int minPower() {
final List<int> powers =
_records.map((Event record) => record.power).nonZeros().cast<int>();
return powers.isNotEmpty ? powers.min() as int : 0;
}
int maxPower() {
final List<int> powers =
_records.map((Event record) => record.power).nonZeros().cast<int>();
return powers.isNotEmpty ? powers.max() as int : 0;
}
// Heart Rate
int avgHeartRate() {
final Iterable<int> heartRates = _records
.where((Event record) =>
record.heartRate != null &&
record.heartRate > 0 &&
record.heartRate < 2000)
.map((Event record) => record.heartRate);
return heartRates.isNotEmpty ? heartRates.mean().round() : -1;
}
double sdevHeartRate() => _records
.where((Event record) =>
record.heartRate != null &&
record.heartRate > 0 &&
record.heartRate < 2000)
.map((Event record) => record.heartRate)
.sdev();
int minHeartRate() {
final List<int> heartRates =
_records.map((Event record) => record.heartRate).nonZeros().cast<int>();
return heartRates.isNotEmpty ? heartRates.min() as int : 0;
}
int maxHeartRate() {
final List<int> heartRates =
_records.map((Event record) => record.heartRate).nonZeros().cast<int>();
return heartRates.isNotEmpty ? heartRates.max() as int : 0;
}
// Speed
double avgSpeedByMeasurements() {
final List<double> speeds =
_records.map((Event record) => record.speed).nonZeros().cast<double>();
return speeds.isNotEmpty ? speeds.mean() : -1;
}
double avgSpeedBySpeed() {
final Map<DateTime, double> speedMap = <DateTime, double>{
for (final Event record in _records) record.timeStamp: record.speed
};
return speedMap.meanUsingSpeed();
}
double avgSpeedByDistance() {
final Map<DateTime, double> speedMap = <DateTime, double>{
for (final Event record in _records) record.timeStamp: record.distance,
};
return speedMap.meanUsingDistance();
}
double sdevSpeed() => _records
.where((Event record) => record.speed != null)
.map((Event record) => record.speed)
.sdev();
double sdevPace() => _records
.where((Event record) => record.speed != null && record.speed > 1)
.map((Event record) => 50 / 3 / record.speed)
.sdev();
double minSpeed() {
final List<double> speeds =
_records.map((Event record) => record.speed).nonZeros().cast<double>();
return speeds.isNotEmpty ? speeds.min() as double : 0;
}
double maxSpeed() {
final List<double> speeds =
_records.map((Event record) => record.speed).nonZeros().cast<double>();
return speeds.isNotEmpty ? speeds.max() as double : 0;
}
// Ground Time
double avgGroundTime() {
final List<double> groundTimes = _records
.map((Event record) => record.groundTime)
.nonZeros()
.cast<double>();
return groundTimes.isNotEmpty ? groundTimes.mean() : -1;
}
double sdevGroundTime() =>
_records.map((Event record) => record.groundTime).nonZeros().sdev();
double minGroundTime() {
final List<double> groundTimes = _records
.map((Event record) => record.groundTime)
.nonZeros()
.cast<double>();
return groundTimes.isNotEmpty ? groundTimes.min() as double : 0;
}
double maxGroundTime() {
final List<double> groundTimes = _records
.map((Event record) => record.groundTime)
.nonZeros()
.cast<double>();
return groundTimes.isNotEmpty ? groundTimes.max() as double : 0;
}
// Stryd Cadence
double avgStrydCadence() {
final List<double> strydCadences = _records
.map((Event record) => record.strydCadence ?? 0.0)
.nonZeros()
.cast<double>();
return strydCadences.isNotEmpty ? strydCadences.mean() : -1;
}
double sdevStrydCadence() => _records
.map((Event record) => record.strydCadence ?? 0.0)
.nonZeros()
.sdev();
double minStrydCadence() {
final List<double> strydCadences = _records
.map((Event record) => record.strydCadence)
.nonZeros()
.cast<double>();
return strydCadences.isNotEmpty ? strydCadences.min() as double : 0;
}
double maxStrydCadence() {
final List<double> strydCadences = _records
.map((Event record) => record.strydCadence)
.nonZeros()
.cast<double>();
return strydCadences.isNotEmpty ? strydCadences.max() as double : 0;
}
// Cadence
double avgCadence() {
final List<double> cadences = _records
.map((Event record) => record.cadence ?? 0.0 * 2)
.nonZeros()
.cast<double>();
return cadences.isNotEmpty ? cadences.mean() : -1;
}
double sdevCadence() => _records
.map((Event record) => record.cadence ?? 0.0 * 2)
.nonZeros()
.sdev();
double minCadence() {
final List<double> cadences = _records
.map((Event record) => record.cadence)
.nonZeros()
.cast<double>();
return cadences.isNotEmpty ? cadences.min() as double : 0;
}
double maxCadence() {
final List<double> cadences = _records
.map((Event record) => record.cadence)
.nonZeros()
.cast<double>();
return cadences.isNotEmpty ? cadences.max() as double : 0;
}
// Leg Spring Stiffness
double avgLegSpringStiffness() {
final List<double> legSpringStiffnesses = _records
.map((Event record) => record.legSpringStiffness)
.nonZeros()
.cast<double>();
return legSpringStiffnesses.isNotEmpty ? legSpringStiffnesses.mean() : -1;
}
double sdevLegSpringStiffness() => _records
.map((Event record) => record.legSpringStiffness)
.nonZeros()
.sdev();
double minLegSpringStiffness() {
final List<double> legSpringStiffnesses = _records
.map((Event record) => record.legSpringStiffness)
.nonZeros()
.cast<double>();
return legSpringStiffnesses.isNotEmpty
? legSpringStiffnesses.min() as double
: 0;
}
double maxLegSpringStiffness() {
final List<double> legSpringStiffnesses = _records
.map((Event record) => record.legSpringStiffness)
.nonZeros()
.cast<double>();
return legSpringStiffnesses.isNotEmpty
? legSpringStiffnesses.max() as double
: 0;
}
// Vertical Oscillation
double avgVerticalOscillation() {
final List<double> verticalOscillation = _records
.map((Event record) => record.verticalOscillation)
.nonZeros()
.cast<double>();
return verticalOscillation.isNotEmpty ? verticalOscillation.mean() : -1;
}
double sdevVerticalOscillation() => _records
.map((Event record) => record.verticalOscillation)
.nonZeros()
.sdev();
double minVerticalOscillation() {
final List<double> verticalOscillations = _records
.map((Event record) => record.verticalOscillation)
.nonZeros()
.cast<double>();
return verticalOscillations.isNotEmpty
? verticalOscillations.min() as double
: 0;
}
double maxVerticalOscillation() {
final List<double> verticalOscillations = _records
.map((Event record) => record.verticalOscillation)
.nonZeros()
.cast<double>();
return verticalOscillations.isNotEmpty
? verticalOscillations.max() as double
: 0;
}
// Form Power
double avgFormPower() {
final Iterable<int> formPowers = _records
.where((Event record) =>
record.formPower != null && record.formPower < 200)
.map((Event record) => record.formPower);
return formPowers.isNotEmpty ? formPowers.mean() : -1;
}
double sdevFormPower() => _records
.where(
(Event record) => record.formPower != null && record.formPower < 200)
.map((Event record) => record.formPower)
.sdev();
int minFormPower() {
final List<int> formPowers =
_records.map((Event record) => record.formPower).nonZeros().cast<int>();
return formPowers.isNotEmpty ? formPowers.min() as int : 0;
}
int maxFormPower() {
final List<int> formPowers =
_records.map((Event record) => record.formPower).nonZeros().cast<int>();
return formPowers.isNotEmpty ? formPowers.max() as int : 0;
}
// Power Ratio
double avgPowerRatio() {
final Iterable<double> powerRatios = _records
.where((Event record) =>
record.power != null &&
record.power != 0 &&
record.formPower != null &&
record.formPower != 0)
.map((Event record) =>
(record.power - record.formPower) / record.power * 100);
return powerRatios.isNotEmpty ? powerRatios.mean() : -1;
}
double sdevPowerRatio() => _records
.where((Event record) =>
record.power != null &&
record.power != 0 &&
record.formPower != null &&
record.formPower != 0)
.map((Event record) =>
(record.power - record.formPower) / record.power * 100)
.sdev();
// Stride Ratio
double avgStrideRatio() {
final Iterable<double> strydRatios = _records
.where((Event record) =>
record.speed != null &&
record.strydCadence != null &&
record.strydCadence != 0 &&
record.verticalOscillation != null &&
record.verticalOscillation != 0)
.map((Event record) =>
10000 /
6 *
record.speed /
record.strydCadence /
record.verticalOscillation);
return strydRatios.isNotEmpty ? strydRatios.mean() : -1;
}
double sdevStrideRatio() => _records
.where((Event record) =>
record.speed != null &&
record.strydCadence != null &&
record.strydCadence != 0 &&
record.verticalOscillation != null &&
record.verticalOscillation != 0)
.map((Event record) =>
10000 /
6 *
record.speed /
record.strydCadence /
record.verticalOscillation)
.sdev();
// Ascend and descend
double totalAscent() {
double lastAltitude = 0;
double sumOfAscents = 0;
final List<double> altitudes = _records
.map((Event record) => record.altitude)
.nonZeros()
.cast<double>();
for (final double altitude in altitudes) {
if (lastAltitude != 0 && altitude > lastAltitude)
sumOfAscents += altitude - lastAltitude;
lastAltitude = altitude;
}
return sumOfAscents;
}
double totalDescent() {
double lastAltitude = 0;
double sumOfDescents = 0;
final List<double> altitudes = _records
.map((Event record) => record.altitude)
.nonZeros()
.cast<double>();
for (final double altitude in altitudes) {
if (lastAltitude != 0 && altitude < lastAltitude)
sumOfDescents += lastAltitude - altitude;
lastAltitude = altitude;
}
return sumOfDescents;
}
// END OF AVERAGES
List<IntPlotPoint> toIntDataPoints({
int amount,
@required LapIntAttr attribute,
}) {
int index = 0;
final List<IntPlotPoint> plotPoints = <IntPlotPoint>[];
int sum = 0;
for (final Event record in _records) {
switch (attribute) {
case LapIntAttr.power:
sum += record.power;
break;
case LapIntAttr.formPower:
sum += record.formPower;
break;
case LapIntAttr.heartRate:
sum += record.heartRate;
}
if (index++ % amount == amount - 1) {
plotPoints.add(IntPlotPoint(
domain: record.distance.round(),
measure: (sum / amount).round(),
));
sum = 0;
}
}
return plotPoints;
}
List<DoublePlotPoint> toDoubleDataPoints({
int amount,
@required LapDoubleAttr attribute,
double weight,
}) {
int index = 0;
final List<DoublePlotPoint> plotPoints = <DoublePlotPoint>[];
double sum = 0.0;
for (final Event record in _records) {
switch (attribute) {
case LapDoubleAttr.powerPerHeartRate:
sum = sum + (record.power / record.heartRate);
break;
case LapDoubleAttr.speedPerHeartRate:
sum = sum + 60 * (record.speed / record.heartRate);
break;
case LapDoubleAttr.groundTime:
sum = sum + record.groundTime;
break;
case LapDoubleAttr.strydCadence:
sum = sum + 2 * record.strydCadence;
break;
case LapDoubleAttr.verticalOscillation:
sum = sum + record.verticalOscillation;
break;
case LapDoubleAttr.legSpringStiffness:
sum = sum + record.legSpringStiffness;
break;
case LapDoubleAttr.powerRatio:
sum = sum + ((record.power - record.formPower) / record.power * 100);
break;
case LapDoubleAttr.strideRatio:
sum = sum +
(10000 /
6 *
record.speed /
record.strydCadence /
record.verticalOscillation);
break;
case LapDoubleAttr.ecor:
sum = sum + (record.power / record.speed / weight);
break;
case LapDoubleAttr.pace:
sum = sum + (50 / 3 / record.speed);
break;
case LapDoubleAttr.speed:
sum = sum + record.speed * 3.6;
break;
case LapDoubleAttr.altitude:
sum = sum + record.altitude;
}
if (index++ % amount == amount - 1) {
plotPoints.add(DoublePlotPoint(
domain: record.distance.round(),
measure: sum / amount,
));
sum = 0;
}
}
return plotPoints;
}
Future<List<BarZone>> powerZoneCounts(
{PowerZoneSchema powerZoneSchema}) async {
final List<BarZone> distributions = <BarZone>[];
double counter = 0.0;
final List<PowerZone> powerZones = await powerZoneSchema.powerZones;
for (final PowerZone powerZone in powerZones) {
final int numberInZone = _records
.where((Event event) =>
(event.power >= powerZone.lowerLimit) &&
(event.power <= powerZone.upperLimit))
.length;
distributions.add(BarZone(
lower: counter,
upper: counter + numberInZone,
color: powerZone.color,
));
counter = counter + numberInZone;
}
return distributions;
}
Future<List<BarZone>> heartRateZoneCounts(
{HeartRateZoneSchema heartRateZoneSchema}) async {
final List<BarZone> distributions = <BarZone>[];
double counter = 0.0;
final List<HeartRateZone> heartRateZones =
await heartRateZoneSchema.heartRateZones;
for (final HeartRateZone heartRateZone in heartRateZones) {
final int numberInZone = _records
.where((Event event) =>
(event.heartRate >= heartRateZone.lowerLimit) &&
(event.heartRate <= heartRateZone.upperLimit))
.length;
distributions.add(BarZone(
lower: counter,
upper: counter + numberInZone,
color: heartRateZone.color,
));
counter = counter + numberInZone;
}
return distributions;
}
}