cyweeInfo->steps, cyweeInfo->distance, cyweeInfo->calories,
cyweeInfo->pace, cyweeInfo->step_frequency,
cyweeInfo->step_length);
- // 通用数据
- sportdata->steps = cyweeInfo->steps;
- sportdata->distance = cyweeInfo->distance;
- sportdata->calories = cyweeInfo->calories;
+ // 通用数据
+ sportdata->steps = cyweeInfo->steps;
+ sportdata->distance = cyweeInfo->distance;
+ sportdata->calories = cyweeInfo->calories;
+ // 骑行等距离不在实时补算,结束时在 SaveSportData 里用 GPS 轨迹一次性计算
// sportdata->cdc = cyweeInfo->step_frequency;
// sportdata->std = cyweeInfo->step_length;
uint32_t cir; // 圈数
uint32_t alt; // 最高海拔高度
*/
- /*保存数据*/
+/*保存数据*/
bool TraingModel::SaveSportData() {
// 配置训练存储状态
}
}
+// 手表轨迹画布最大点数,控制内存与绘制性能
+static const size_t MAX_CANVAS_POINTS = 300;
+
+/**
+ * @brief 将画布点集均匀采样至最多 maxPoints 个点(保留首尾,中间等间隔取点)
+ */
+static void uniformSampleToMax(std::vector<Point> &points, size_t maxPoints) {
+ if (points.size() <= maxPoints) return;
+ size_t n = points.size();
+ std::vector<Point> sampled;
+ sampled.reserve(maxPoints);
+ for (size_t i = 0; i < maxPoints; i++) {
+ size_t idx = (i * (n - 1)) / (maxPoints - 1);
+ sampled.push_back(points[idx]);
+ }
+ points = std::move(sampled);
+}
+
/**
- * @brief GPS点位映射到画布坐标的核心函数
- *
- * 映射原理(三步法):
- * 1. 归一化:将GPS经纬度转换为0-1之间的相对位置
- * 2. 缩放:将归一化坐标缩放到画布可用区域(300x300,留50像素边距)
- * 3. 翻转:Y轴需要翻转(GPS纬度:北纬大,画布Y:上小下大)
- *
- * 画布布局:
- * - 总大小:400x400像素
- * - 可用区域:300x300像素(从50,50到350,350)
- * - 边距:上下左右各50像素(避免轨迹贴边显示不全)
- *
- * 示例:
- * 假设GPS轨迹范围:纬度[39.9, 40.0],经度[116.3, 116.4]
- * - 点A: (39.95, 116.35) -> 归一化(0.5, 0.5) -> 画布(200, 200)
- * - 点B: (39.9, 116.3) -> 归一化(0.0, 0.0) -> 画布(50, 350) [左下角]
- * - 点C: (40.0, 116.4) -> 归一化(1.0, 1.0) -> 画布(350, 50) [右上角]
+ * @brief GPS点位映射到画布坐标,并限制画布点数≤300以简化手表显示
+ *
+ * 1. 计算边界框并映射所有GPS点到画布
+ * 2. 若点数>300则均匀采样至300点,保证轨迹形状不失真
*/
void TraingModel::GetPointData() {
WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,
"开始GPS到画布映射,GPS点数: %zu", gpsData.size());
- // 步骤0:数据检查
if (gpsData.empty()) {
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "GPS数据为空,无法映射");
canvasData.clear();
return;
}
- // 步骤1:计算GPS轨迹的边界框(Bounding Box)
- // 找到所有GPS点中的最小和最大经纬度
- double minLat = 90.0; // 纬度最小值(初始化为最大值,确保会被替换)
- double maxLat = -90.0; // 纬度最大值(初始化为最小值,确保会被替换)
- double minLon = 180.0; // 经度最小值(初始化为最大值,确保会被替换)
- double maxLon = -180.0; // 经度最大值(初始化为最小值,确保会被替换)
-
+ double minLat = 90.0, maxLat = -90.0, minLon = 180.0, maxLon = -180.0;
for (const auto &point : gpsData) {
minLat = getmin(minLat, point.latitude);
maxLat = getmax(maxLat, point.latitude);
maxLon = getmax(maxLon, point.longitude);
}
- // 计算经纬度的范围(跨度)
- double rangeLat = maxLat - minLat; // 纬度跨度
- double rangeLon = maxLon - minLon; // 经度跨度
+ double rangeLat = maxLat - minLat;
+ double rangeLon = maxLon - minLon;
+ const double MIN_RANGE = 1e-6;
+ if (rangeLat < MIN_RANGE) rangeLat = MIN_RANGE;
+ if (rangeLon < MIN_RANGE) rangeLon = MIN_RANGE;
- // 处理特殊情况:所有GPS点几乎在同一个位置(范围接近0)
- const double MIN_RANGE = 1e-6; // 最小有效范围(约0.000001度,约0.1米)
- if (rangeLat < MIN_RANGE) {
- WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP,
- "纬度范围过小(%.9f),使用默认值", rangeLat);
- rangeLat = MIN_RANGE; // 避免除零,同时保持轨迹可见
- }
- if (rangeLon < MIN_RANGE) {
- WEARABLE_LOGW(WEARABLE_LOG_MODULE_APP,
- "经度范围过小(%.9f),使用默认值", rangeLon);
- rangeLon = MIN_RANGE; // 避免除零,同时保持轨迹可见
- }
-
- WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,
- "GPS边界框: 纬度[%.6f, %.6f] 跨度=%.6f, 经度[%.6f, %.6f] 跨度=%.6f",
- minLat, maxLat, rangeLat, minLon, maxLon, rangeLon);
+ const double CANVAS_SIZE = 400.0, CANVAS_MARGIN = 50.0, CANVAS_USABLE = 300.0;
- // 步骤2:定义画布参数(常量)
- const double CANVAS_SIZE = 400.0; // 画布总大小:400x400
- const double CANVAS_MARGIN = 50.0; // 边距:50像素(避免轨迹贴边)
- const double CANVAS_USABLE = 300.0; // 可用区域:300x300(400-50*2)
-
- // 步骤3:映射所有GPS点到画布坐标
canvasData.clear();
- canvasData.reserve(gpsData.size()); // 预分配内存,提高性能
+ int max_points = gpsData.size() > MAX_CANVAS_POINTS ? MAX_CANVAS_POINTS : gpsData.size();
+ canvasData.reserve(max_points);
+ WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "max_points = %d", max_points);
for (const auto &point : gpsData) {
- // 3.1 归一化:将GPS坐标转换为0-1之间的相对位置
- // normLat = 0.0 表示最南边(minLat),1.0 表示最北边(maxLat)
- // normLon = 0.0 表示最西边(minLon),1.0 表示最东边(maxLon)
double normLat = (point.latitude - minLat) / rangeLat;
double normLon = (point.longitude - minLon) / rangeLon;
-
- // 3.2 缩放到画布可用区域
- // X坐标:直接映射(经度从左到右对应X从左到右)
- // Y坐标:需要翻转(纬度从南到北对应Y从下到上,但画布Y是从上到下)
double x = normLon * CANVAS_USABLE + CANVAS_MARGIN;
- double y = (1.0 - normLat) * CANVAS_USABLE + CANVAS_MARGIN;
- // 注意:Y轴翻转使用 (1.0 - normLat),因为:
- // - GPS纬度:北纬大(如40.0)应该显示在画布上方(Y小,如50)
- // - GPS纬度:南纬小(如39.9)应该显示在画布下方(Y大,如350)
-
- // 3.3 边界检查(确保坐标在画布范围内)
+ double y = (1.0 - normLat) * CANVAS_USABLE + CANVAS_MARGIN;
if (x < CANVAS_MARGIN) x = CANVAS_MARGIN;
if (x > CANVAS_SIZE - CANVAS_MARGIN) x = CANVAS_SIZE - CANVAS_MARGIN;
if (y < CANVAS_MARGIN) y = CANVAS_MARGIN;
if (y > CANVAS_SIZE - CANVAS_MARGIN) y = CANVAS_SIZE - CANVAS_MARGIN;
-
canvasData.push_back({x, y});
}
- WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,
- "映射完成:%zu个GPS点 -> %zu个画布点,画布范围[%.1f, %.1f]到[%.1f, %.1f]",
- gpsData.size(), canvasData.size(),
- CANVAS_MARGIN, CANVAS_MARGIN,
- CANVAS_SIZE - CANVAS_MARGIN, CANVAS_SIZE - CANVAS_MARGIN);
+ if (canvasData.size() > MAX_CANVAS_POINTS) {
+ uniformSampleToMax(canvasData, MAX_CANVAS_POINTS);
+ WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,
+ "画布点超限,已均匀采样至 %zu 点(最多300点)", canvasData.size());
+ }
+
+ WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,
+ "映射完成:%zu个GPS点 -> %zu个画布点",
+ gpsData.size(), canvasData.size());
+}
+
+// 地球平均半径(米),用于Haversine公式
+static const double EARTH_RADIUS_M = 6371000.0;
+static const double DEG_TO_RAD = 3.141592653589793 / 180.0;
+
+/**
+ * @brief Haversine公式:计算两点间球面距离(米)
+ * @param lat1, lon1 起点纬度、经度(度)
+ * @param lat2, lon2 终点纬度、经度(度)
+ * @return 距离(米)
+ */
+static double haversineDistanceM(double lat1, double lon1, double lat2, double lon2) {
+ double lat1_rad = lat1 * DEG_TO_RAD;
+ double lon1_rad = lon1 * DEG_TO_RAD;
+ double lat2_rad = lat2 * DEG_TO_RAD;
+ double lon2_rad = lon2 * DEG_TO_RAD;
+ double dlat = lat2_rad - lat1_rad;
+ double dlon = lon2_rad - lon1_rad;
+ double a = std::sin(dlat / 2) * std::sin(dlat / 2) +
+ std::cos(lat1_rad) * std::cos(lat2_rad) *
+ std::sin(dlon / 2) * std::sin(dlon / 2);
+ double c = 2 * std::atan2(std::sqrt(a), std::sqrt(1 - a));
+ return EARTH_RADIUS_M * c;
+}
+
+/**
+ * @brief 根据GPS轨迹点逐段累加计算总距离(米)
+ * 用于骑行等CYWEE不输出距离时,用GPS点位补算
+ * @return 总距离(米),点数<2时返回0
+ */
+uint32_t TraingModel::CalculateDistanceFromGPSTrack(void) {
+ if (gpsData.size() < 2) {
+ return 0;
+ }
+ double totalM = 0;
+ for (size_t i = 1; i < gpsData.size(); i++) {
+ totalM += haversineDistanceM(
+ gpsData[i - 1].latitude, gpsData[i - 1].longitude,
+ gpsData[i].latitude, gpsData[i].longitude);
+ }
+ uint32_t dist = static_cast<uint32_t>(totalM + 0.5);
+ WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP,
+ "CalculateDistanceFromGPSTrack: %zu点 -> %u米", gpsData.size(), dist);
+ return dist;
}
// 添加经纬度数据集
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP,
"LoadGPSData Success. 加载了 %zu 个GPS点", gpsData.size());
- // 加载GPS数据后,重新生成canvas数据(使用所有GPS点)
+ // 重新生成canvas数据
GetPointData();
return true;