TrainingInfo.fromJson constructor
TrainingInfo.fromJson(
- Map<String, dynamic> json
)
Implementation
factory TrainingInfo.fromJson(Map<String, dynamic> json) {
debugPrint("解析训练数据: $json");
int startTime = json["startTime"] ?? -1;
final endTime = json["endTime"] ?? -1;
final validTime = json["validTime"] ?? -1;
final steps = json["steps"] ?? -1;
final distance = json["distance"] ?? -1;
final rawHrList = List<int>.from((json["hrList"] ?? []).map((x) => x));
// 检测并修复被截断的时间戳
// 正确的时间戳应该是10位数字(Unix时间戳),如果少于10位则可能被截断
if (startTime > 0 && startTime.toString().length < 10) {
// 检测可能的截断情况
final timeStr = startTime.toString();
if (timeStr.length == 7) {
// 如果是7位数字,可能是从10位时间戳的前7位,尝试补全
// 例如:1756190 -> 1756190000 (添加3个0)
startTime = startTime * 1000;
print("检测到时间戳被截断,从 $timeStr 修复为 $startTime");
} else if (timeStr.length == 8) {
// 如果是8位数字,可能是从10位时间戳的前8位,尝试补全
startTime = startTime * 100;
print("检测到时间戳被截断,从 $timeStr 修复为 $startTime");
} else if (timeStr.length == 9) {
// 如果是9位数字,可能是从10位时间戳的前9位,尝试补全
startTime = startTime * 10;
print("检测到时间戳被截断,从 $timeStr 修复为 $startTime");
}
}
// 将原始心率数据转换为 HeartRateData 对象列表
List<HeartRateData> processedHrList = [];
for (int i = 0; i < rawHrList.length; i++) {
processedHrList.add(HeartRateData(
timestamp: startTime + (i * 10), // 每10秒一个数据点
hr: rawHrList[i],
));
}
// 计算最大数据点数
final maxPoints10s = validTime > 0 ? (validTime / 10).ceil() : 0;
final endTimestamp = startTime + validTime;
// 处理步频数据
List<int> finalCadencesPerMinute = []; // 每分钟数据
List<int> finalCadencesPer10s = []; // 每10秒数据(详细分析用)
List<CadenceData> processedCadencesList = [];
// 统一处理来自 iOS(cadences) 或 Android(stepsList) 的每10秒数据
final rawCadencesData = json["cadences"] ?? json["stepsList"];
if (rawCadencesData != null) {
// 统一处理来自 iOS(cadences) 或 Android(stepsList) 的每10秒数据
final per10sData = List<int>.from(rawCadencesData.map((x) => x));
// 根据实际训练时长截断数据
final truncatedCadences = maxPoints10s > 0
? per10sData.take(maxPoints10s).toList()
: per10sData;
finalCadencesPer10s = truncatedCadences; // 保留原始10秒数据
// 将每10秒数据转换为每分钟数据(每6个10秒数据合并为1个分钟数据)
for (int i = 0; i < truncatedCadences.length; i += 6) {
int sum = 0;
// 取6个数据点求和(如果不足6个则取实际数量)
for (int j = i; j < i + 6 && j < truncatedCadences.length; j++) {
sum += truncatedCadences[j];
}
finalCadencesPerMinute.add(sum);
}
// 生成带时间戳的详细数据
for (int i = 0; i < truncatedCadences.length; i++) {
final timestamp = startTime + (i * 10);
// 严格检查时间边界
if (timestamp > endTimestamp) {
break;
}
processedCadencesList.add(CadenceData(
timestamp: timestamp,
cadence: truncatedCadences[i],
));
}
} else if (validTime > 0 && steps > 0) {
// 如果原生层没有提供步频数据,根据总步数和有效时间计算
final cadencePerMinute = (steps * 60) ~/ validTime; // 每分钟步数
final cadencePer10Seconds = (cadencePerMinute * 10) ~/ 60; // 每10秒步数
final totalPointsPer10s = (validTime / 10).ceil(); // 总的10秒数据点数
final totalPointsPerMinute = (validTime / 60).ceil(); // 总的分钟数据点数
// 生成每分钟数据
for (int i = 0; i < totalPointsPerMinute; i++) {
// 为了模拟真实情况,在基础值上添加小幅波动(±5%)
final baseValue = cadencePerMinute;
final variation = (baseValue * 0.05).round(); // 5%变化
final actualValue = baseValue + (i % 3 - 1) * variation; // 简单的波动模式
final finalValue = actualValue > 0 ? actualValue : baseValue;
finalCadencesPerMinute.add(finalValue);
}
// 生成每10秒详细数据
for (int i = 0; i < totalPointsPer10s; i++) {
final currentTimestamp = startTime + (i * 10);
if (currentTimestamp > endTimestamp) {
break;
}
// 为了模拟真实情况,在基础值上添加小幅波动(±5%)
final baseValue = cadencePer10Seconds;
final variation = (baseValue * 0.05).round(); // 5%变化
final actualValue = baseValue + (i % 3 - 1) * variation; // 简单的波动模式
final finalValue = actualValue > 0 ? actualValue : baseValue;
finalCadencesPer10s.add(finalValue);
processedCadencesList.add(CadenceData(
timestamp: currentTimestamp,
cadence: finalValue,
));
}
}
// 处理步幅数据
List<int> finalStridesPerMinute = []; // 每分钟数据
List<int> finalStridesPer10s = []; // 每10秒数据(详细分析用)
List<StrideData> processedStridesList = [];
// 将 distanceList (Android) 和 strides (iOS) 视为同一类型的数据源
final rawStridesData = json["strides"] ?? json["distanceList"];
if (rawStridesData != null) {
// 统一处理来自 iOS(strides) 或 Android(distanceList) 的每10秒数据
final per10sData = List<int>.from(rawStridesData.map((x) => x));
// 根据实际训练时长截断数据
final truncatedStrides = maxPoints10s > 0
? per10sData.take(maxPoints10s).toList()
: per10sData;
finalStridesPer10s = truncatedStrides; // 保留原始10秒数据
// 1. 直接使用原始的10秒数据生成带时间戳的 processedStridesList
for (int i = 0; i < truncatedStrides.length; i++) {
final timestamp = startTime + (i * 10);
// 严格检查时间边界
if (timestamp > endTimestamp) {
break;
}
processedStridesList.add(StrideData(
timestamp: timestamp,
stride: truncatedStrides[i],
));
}
// 2. 从精确的10秒数据中,计算出每分钟的数据
for (int i = 0; i < truncatedStrides.length; i += 6) {
int sum = 0;
// 取6个数据点求和(如果不足6个则取实际数量)
for (int j = i; j < i + 6 && j < truncatedStrides.length; j++) {
sum += truncatedStrides[j];
}
finalStridesPerMinute.add(sum);
}
} else if (validTime > 0 && distance > 0) {
// 如果原生层没有提供步幅数据,根据总距离和有效时间计算
final stridePerMinute = (distance * 60) ~/ validTime; // 每分钟距离(厘米)
final stridePer10Seconds = (stridePerMinute * 10) ~/ 60; // 每10秒距离(厘米)
final totalPointsPer10s = (validTime / 10).ceil(); // 总的10秒数据点数
final totalPointsPerMinute = (validTime / 60).ceil(); // 总的分钟数据点数
// 生成每分钟数据
for (int i = 0; i < totalPointsPerMinute; i++) {
// 为了模拟真实情况,在基础值上添加小幅波动(±8%)
final baseValue = stridePerMinute;
final variation = (baseValue * 0.08).round(); // 8%变化
final actualValue = baseValue + (i % 4 - 1) * variation; // 简单的波动模式
final finalValue = actualValue > 0 ? actualValue : baseValue;
finalStridesPerMinute.add(finalValue);
}
// 生成每10秒详细数据
for (int i = 0; i < totalPointsPer10s; i++) {
final currentTimestamp = startTime + (i * 10);
// 严格检查时间边界
if (currentTimestamp > endTimestamp) {
break;
}
// 为了模拟真实情况,在基础值上添加小幅波动(±8%)
final baseValue = stridePer10Seconds;
final variation = (baseValue * 0.08).round(); // 8%变化
final actualValue = baseValue + (i % 4 - 1) * variation; // 简单的波动模式
final finalValue = actualValue > 0 ? actualValue : baseValue;
finalStridesPer10s.add(finalValue);
processedStridesList.add(StrideData(
timestamp: currentTimestamp,
stride: finalValue,
));
}
}
// 处理海拔数据 - 兼容iOS和Android层的字段名
int? finalAvgElevation, finalMaxElevation, finalMinElevation;
// iOS层字段:avgElevation, maxElevation, minElevation
// Android层字段:averageAltitude, maxAltitude, minAltitude
finalAvgElevation = json["avgElevation"] ?? json["averageAltitude"];
finalMaxElevation = json["maxElevation"] ?? json["maxAltitude"];
finalMinElevation = json["minElevation"] ?? json["minAltitude"];
// 处理配速数据 - 兼容iOS和Android层的字段名,优先从JSON读取,如果没有则根据距离和时间计算
// 处理配速数据 - 需要同时处理 s/m 和 s/km 两种单位
int? finalAvgPace, finalMaxPace, finalMinPace; // 最终输出的 s/m 单位值
int? initialAvgPaceInSm; // 初始平均配速,单位 s/m
// 1. 获取或计算初始的平均配速 (单位: s/m),优先从JSON直接读取配速数据
// iOS层字段:avgPace, maxPace, minPace
// Android层字段:averageSpeed, maxSpeed, minSpeed
if (json["avgPace"] != null || json["averageSpeed"] != null) {
int avgPaceInSk = json["avgPace"] ?? json["averageSpeed"];
initialAvgPaceInSm = (avgPaceInSk / 1000).round(); // 转换为 s/m
} else if (distance > 0 && validTime > 0) {
// 如果JSON中没有配速数据,根据总距离和总时间计算平均配速
final distanceInMeters = distance / 100.0; // 转换为米(distance单位是厘米)
if (distanceInMeters > 0) {
initialAvgPaceInSm = (validTime / distanceInMeters).round(); // s/m
}
}
// 2. 计算每10秒的详细配速列表(paceList,单位:s/km)
List<PaceData> processedPaceList = [];
// 基于每10秒步幅数据计算配速
if (processedStridesList.isNotEmpty) {
// 使用每10秒的详细步幅数据计算配速
for (int i = 0; i < processedStridesList.length; i++) {
final strideData = processedStridesList[i];
final distance10sInMeter = strideData.stride / 100.0; // 厘米转换为米
const double minEffectiveDistance = 0.1; // 0.1米 (10厘米)
// --- 数据清洗:增加有效运动的最小距离阈值 ---
// 如果10秒内移动距离过小(如小于10厘米),则视为静止,不计入有效配速计算
if (distance10sInMeter > minEffectiveDistance) {
// --- 计算 s/m 单位的配速 ---
// 每米用时:10秒 / 距离米数 = 秒/米,公式:配速 = 时间 / 距离,衡量的是移动单位距离所需的时间
final pacePerMeter = 10 / distance10sInMeter; // 秒/米
// --- 计算 s/km 单位的配速 ---
// 转换为每公里用时:秒/米 × 1000 = 秒/公里
// 如需要每min/公里的单位值,除以60即可
final pacePerKm = (pacePerMeter * 1000).round(); // 秒/公里
// 应用合理的配速范围限制 (3分钟/公里 到 20分钟/公里)
// int limitedPacePerKm = pacePerKm.round();
// if (limitedPacePerKm < 180) limitedPacePerKm = 180; // 最快 3:00/km
// if (limitedPacePerKm > 1200) limitedPacePerKm = 1200; // 最慢 20:00/km
processedPaceList.add(PaceData(
timestamp: strideData.timestamp,
pace: pacePerKm, // limitedPacePerKm, // [需要限制范围的话使用此值]
));
} else {
// --- 静止或无效运动状态处理 ---
// 对于 paceList,将静止或无效运动的配速设置为 0
processedPaceList.add(PaceData(
timestamp: strideData.timestamp,
pace: 0,
));
}
}
} else if (initialAvgPaceInSm != null && initialAvgPaceInSm > 0) {
// 如果没有详细步幅数据,则基于平均配速生成模拟数据
final initialAvgPaceInSk = (initialAvgPaceInSm * 1000).round();
final totalPoints10s = (validTime / 10).ceil();
for (int i = 0; i < totalPoints10s; i++) {
final timestamp = startTime + (i * 10);
if (timestamp > endTimestamp) break;
// 添加±10%的随机波动使数据更真实
final variation = (initialAvgPaceInSk * 0.1).round();
final randomFactor = (i % 7 - 3) / 3.0; // -1.0 到 1.0 的变化
final actualPaceSk =
initialAvgPaceInSk + (variation * randomFactor).round();
final finalPaceValueSk =
actualPaceSk > 0 ? actualPaceSk : initialAvgPaceInSk;
processedPaceList.add(PaceData(
timestamp: timestamp,
pace: finalPaceValueSk, // 单位:秒/公里
));
}
}
// JSON中的值已是 s/km 单位值
finalAvgPace = json["avgPace"] ?? json["averageSpeed"];
finalMaxPace = json["maxPace"] ?? json["maxSpeed"];
finalMinPace = json["minPace"] ?? json["minSpeed"];
// 创建 TrainingInfo 实例
TrainingInfo trainingInfo = TrainingInfo(
type: json["type"] ?? -1,
startTime: startTime,
endTime: endTime,
validTime: validTime,
steps: steps,
distance: distance,
calories: json["calories"] ?? -1,
hrList: processedHrList,
cadences: finalCadencesPerMinute, // 现在是每分钟数据
strides: finalStridesPerMinute, // 现在是每分钟数据
cadencesPer10s: finalCadencesPer10s, // 详细的每10秒数据(值的集合)
stridesPer10s: finalStridesPer10s, // 详细的每10秒数据(值的集合)
hrInterval: json["hrInterval"] ?? 10, // 默认10秒间隔
cadencesInterval: 10, // 统一每10秒间隔
stridesInterval: 10, // 统一每10秒间隔
cadencesList: processedCadencesList, // 每10秒详细数据(值+时间戳的map集合)
stridesList: processedStridesList, // 每10秒详细数据(值+时间戳的map集合)
avgElevation: finalAvgElevation,
maxElevation: finalMaxElevation,
minElevation: finalMinElevation,
avgPace: finalAvgPace,
maxPace: finalMaxPace,
minPace: finalMinPace,
paceList: processedPaceList,
);
// 计算心率区间统计
trainingInfo.hrZone = trainingInfo._calculateHrZone(processedHrList);
return trainingInfo;
}