From 7f4127fb6c55e54528832e142fb5eea4abf2ccd3 Mon Sep 17 00:00:00 2001 From: Caisking Date: Mon, 2 Feb 2026 17:28:33 +0800 Subject: [PATCH] =?utf8?q?=E4=BC=98=E5=8C=96GPS=E8=BD=A8=E8=BF=B9=E7=BB=98?= =?utf8?q?=E5=88=B6=EF=BC=8C=E9=99=90=E5=88=B6300=E4=B8=AA=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../nativeui/traing/include/TraingModel.h | 2 + .../nativeui/traing/src/TraingEnddataView.cpp | 3 + .../nativeui/traing/src/TraingModel.cpp | 170 ++++++++++-------- 3 files changed, 99 insertions(+), 76 deletions(-) diff --git a/code/sdk/application/wearable/fd272/nativeapp/nativeui/traing/include/TraingModel.h b/code/sdk/application/wearable/fd272/nativeapp/nativeui/traing/include/TraingModel.h index 15382b03e..8cc0af507 100644 --- a/code/sdk/application/wearable/fd272/nativeapp/nativeui/traing/include/TraingModel.h +++ b/code/sdk/application/wearable/fd272/nativeapp/nativeui/traing/include/TraingModel.h @@ -392,6 +392,8 @@ public: bool SaveGPSData(); bool LoadGPSData(const std::string &filePath); std::string GetGPSDataPath(uint32_t timestamp); + /** 根据GPS轨迹点计算总距离(米),用于骑行等CYWEE无距离时的补算 */ + uint32_t CalculateDistanceFromGPSTrack(void); //运动心率、gps开启准备 void start_training_heart(); //运动心率、gps关闭 diff --git a/code/sdk/application/wearable/fd272/nativeapp/nativeui/traing/src/TraingEnddataView.cpp b/code/sdk/application/wearable/fd272/nativeapp/nativeui/traing/src/TraingEnddataView.cpp index 8ccbbf6d1..5522b82a4 100644 --- a/code/sdk/application/wearable/fd272/nativeapp/nativeui/traing/src/TraingEnddataView.cpp +++ b/code/sdk/application/wearable/fd272/nativeapp/nativeui/traing/src/TraingEnddataView.cpp @@ -758,6 +758,7 @@ void TraingEnddataView::OnStart() { if (!gpsFilePath.empty()) { if (TraingModel::GetInstance()->LoadGPSData(gpsFilePath) == true) { WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "GPS原始数据文件加载成功,将使用所有GPS点"); + TraingModel::GetInstance()->GetPointData(); // 从GPS点生成画布点(含300点限制),供结束页绘制轨迹 } else { WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "GPS原始数据文件加载失败,尝试加载canvas文件"); // 如果GPS文件加载失败,回退到canvas文件 @@ -914,6 +915,8 @@ void TraingEnddataView::DrawTrackSegment(UICanvas *canvas) { canvas->BeginPath(); canvas->MoveTo(seg.points[0]); + //绘制点位 + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "绘制点位:seg.points.size() = %zu", seg.points.size()); for (size_t i = 1; i < seg.points.size(); ++i) { canvas->LineTo(seg.points[i]); } diff --git a/code/sdk/application/wearable/fd272/nativeapp/nativeui/traing/src/TraingModel.cpp b/code/sdk/application/wearable/fd272/nativeapp/nativeui/traing/src/TraingModel.cpp index b9e20e010..310ab1908 100644 --- a/code/sdk/application/wearable/fd272/nativeapp/nativeui/traing/src/TraingModel.cpp +++ b/code/sdk/application/wearable/fd272/nativeapp/nativeui/traing/src/TraingModel.cpp @@ -504,10 +504,11 @@ 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; @@ -1404,7 +1405,7 @@ void TraingModel::SetSportdata(SportData *data) { uint32_t cir; // 圈数 uint32_t alt; // 最高海拔高度 */ - /*保存数据*/ +/*保存数据*/ bool TraingModel::SaveSportData() { // 配置训练存储状态 @@ -2725,43 +2726,41 @@ bool TraingModel::GetMomentum() { } } +// 手表轨迹画布最大点数,控制内存与绘制性能 +static const size_t MAX_CANVAS_POINTS = 300; + +/** + * @brief 将画布点集均匀采样至最多 maxPoints 个点(保留首尾,中间等间隔取点) + */ +static void uniformSampleToMax(std::vector &points, size_t maxPoints) { + if (points.size() <= maxPoints) return; + size_t n = points.size(); + std::vector 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); @@ -2769,66 +2768,85 @@ void TraingModel::GetPointData() { 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(totalM + 0.5); + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, + "CalculateDistanceFromGPSTrack: %zu点 -> %u米", gpsData.size(), dist); + return dist; } // 添加经纬度数据集 @@ -3072,7 +3090,7 @@ bool TraingModel::LoadGPSData(const std::string &filePath) { WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "LoadGPSData Success. 加载了 %zu 个GPS点", gpsData.size()); - // 加载GPS数据后,重新生成canvas数据(使用所有GPS点) + // 重新生成canvas数据 GetPointData(); return true; -- 2.17.1