From 91a784d94d97b37a9e7cfc822694b09bdc7baedf Mon Sep 17 00:00:00 2001 From: Caisking Date: Mon, 19 Jan 2026 09:43:17 +0800 Subject: [PATCH] =?utf8?q?=E4=BF=AE=E5=A4=8D=E8=BD=A8=E8=BF=B9=E6=98=BE?= =?utf8?q?=E7=A4=BA=E4=B8=8D=E5=85=A8=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../nativeui/traing/include/TraingModel.h | 1 + .../nativeui/traing/src/TraingEnddataView.cpp | 31 +- .../nativeui/traing/src/TraingModel.cpp | 332 ++++++++---------- 3 files changed, 182 insertions(+), 182 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 878b80ccb..11d7b2a48 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 @@ -387,6 +387,7 @@ public: void OpenGPS(); void CloseGPS(); bool SaveGPSData(); + bool LoadGPSData(const std::string &filePath); std::string GetGPSDataPath(uint32_t timestamp); //运动心率、gps开启准备 void start_training_heart(); 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 a7d78bac5..5c742c689 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 @@ -739,16 +739,37 @@ void TraingEnddataView::OnStart() { if (isNeedGPS == true) { WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "该运动项需要GPS"); #if defined(SUPPORT_GNSS) - // 读取一下gps文件 运动开始时间戳 + // 读取原始GPS数据文件,而不是canvas文件,确保使用所有GPS点 SportData *enddata = TraingModel::GetInstance()->GetSportdata(); if (enddata != nullptr) { uint32_t startTimestamp = enddata->starttime; - std::string gpsFilePath =TraingModel::GetInstance()->GetCanvasDataPath(startTimestamp); + // 优先读取原始GPS数据文件 + std::string gpsFilePath = TraingModel::GetInstance()->GetGPSDataPath(startTimestamp); if (!gpsFilePath.empty()) { - if (TraingModel::GetInstance()->LoadCanvasData(gpsFilePath) == true) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "gps文件加载成功"); + if (TraingModel::GetInstance()->LoadGPSData(gpsFilePath) == true) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "GPS原始数据文件加载成功,将使用所有GPS点"); } else { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "gps文件加载失败"); + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "GPS原始数据文件加载失败,尝试加载canvas文件"); + // 如果GPS文件加载失败,回退到canvas文件 + std::string canvasFilePath = TraingModel::GetInstance()->GetCanvasDataPath(startTimestamp); + if (!canvasFilePath.empty()) { + if (TraingModel::GetInstance()->LoadCanvasData(canvasFilePath) == true) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "canvas文件加载成功"); + } else { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "canvas文件加载失败"); + } + } + } + } else { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "未找到GPS原始数据文件,尝试加载canvas文件"); + // 如果找不到GPS文件,尝试加载canvas文件 + std::string canvasFilePath = TraingModel::GetInstance()->GetCanvasDataPath(startTimestamp); + if (!canvasFilePath.empty()) { + if (TraingModel::GetInstance()->LoadCanvasData(canvasFilePath) == true) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "canvas文件加载成功"); + } else { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "canvas文件加载失败"); + } } } } 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 eb6faa4bb..8e6eb48f4 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 @@ -2602,16 +2602,16 @@ void TraingModel::SetSportdata(SportData *data) { return true; } - //////////////////////////////////////GPS/////////////////////////////////////////////// - /* - Latitude(纬度):范围:-90° 至 +90°(或南纬 90° 至北纬 90°); - Longitude(经度):范围:-180° 至 +180°(或西经 180° 至东经 180°); - - -画布数据集最多是100个定位坐标,超过按比例进行拿取100个点位; - -通过映射关系,将经纬度转换为400*400画布上的坐标; - -但是不能靠近400,以免显示不全等问题,350*350; - -如果处理负数,需要将其转换为正数,然后再进行映射? - -如果有正数又有负数,应该怎么处理? +//////////////////////////////////////GPS/////////////////////////////////////////////// +/* + Latitude(纬度):范围:-90° 至 +90°(或南纬 90° 至北纬 90°); + Longitude(经度):范围:-180° 至 +180°(或西经 180° 至东经 180°); + + -画布数据集使用所有GPS定位坐标,不进行点位限制,确保绘制完整; + -通过映射关系,将经纬度转换为400*400画布上的坐标; + -但是不能靠近400,以免显示不全等问题,350*350; + -如果处理负数,需要将其转换为正数,然后再进行映射? + -如果有正数又有负数,应该怎么处理? */ static double getmin(double a, double b) { return a < b ? a : b; } @@ -2694,168 +2694,111 @@ void TraingModel::SetSportdata(SportData *data) { } } - // 改进的GetPointData函数(优化版) - void TraingModel::GetPointData() { - WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, - "Generating point data from %zu GPS points", gpsData.size()); - - std::vector sampledData; - - // 处理GPS数据 - if (gpsData.size() > 100) { - // 计算经纬度范围 - double minLat = 90.0, maxLat = -90.0; - double minLon = 180.0, maxLon = -180.0; - - // 同时计算平均值用于判断数据分布 - double sumLat = 0, sumLon = 0; - - for (const auto &point : gpsData) { - minLat = getmin(minLat, point.latitude); - maxLat = getmax(maxLat, point.latitude); - minLon = getmin(minLon, point.longitude); - maxLon = getmax(maxLon, point.longitude); - sumLat += point.latitude; - sumLon += point.longitude; - } - - // 计算经纬度范围和平均值 - double rangeLat = maxLat - minLat; - double rangeLon = maxLon - minLon; - double avgLat = sumLat / gpsData.size(); - double avgLon = sumLon / gpsData.size(); - - WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, - "GPS range: lat=[%.6f, %.6f], lon=[%.6f, %.6f]", minLat, - maxLat, minLon, maxLon); - - // 检查数据是否集中在极小区域或几乎相同 - const double MIN_SIGNIFICANT_RANGE = 0.01; // 根据实际情况调整 - bool isDataDegenerate = - (rangeLat < MIN_SIGNIFICANT_RANGE && rangeLon < MIN_SIGNIFICANT_RANGE); - - if (isDataDegenerate) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, - "GPS data is degenerate (very small range)"); - // 使用均匀采样,避免算法失效 - double step = static_cast(gpsData.size() - 1) / 99; - for (int i = 0; i < 100; i++) { - int index = static_cast(i * step); - sampledData.push_back(gpsData[index]); - } - } else { - // 转换GPS坐标到屏幕坐标 - std::vector screenPoints; - screenPoints.reserve(gpsData.size()); - - for (const auto &point : gpsData) { - double normLat = (point.latitude - minLat) / rangeLat; - double normLon = (point.longitude - minLon) / rangeLon; - screenPoints.push_back( - {normLon * 300 + 50, (1.0 - normLat) * 300 + 50}); - } - - // 使用Douglas-Peucker算法简化轨迹 - std::vector simplifiedPoints; - douglasPeucker(screenPoints, 0.5, simplifiedPoints); - - WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, - "After Douglas-Peucker: %zu points", - simplifiedPoints.size()); - - // 处理简化结果 - if (simplifiedPoints.size() <= 2) { - WEARABLE_LOGE( - WEARABLE_LOG_MODULE_APP, - "Trajectory over-simplified! Using uniform sampling instead."); - // 过度简化时回退到均匀采样 - double step = static_cast(gpsData.size() - 1) / 99; - for (int i = 0; i < 100; i++) { - int index = static_cast(i * step); - sampledData.push_back(gpsData[index]); - } - } else if (simplifiedPoints.size() > 100) { - // 简化后仍超过100点,再次均匀采样 - double step = static_cast(simplifiedPoints.size() - 1) / 99; - for (int i = 0; i < 100; i++) { - int index = static_cast(i * step); - // 屏幕坐标反推回GPS坐标 - double normX = (simplifiedPoints[index].x - 50) / 300; - double normY = (simplifiedPoints[index].y - 50) / 300; - double lat = minLat + (1.0 - normY) * rangeLat; - double lon = minLon + normX * rangeLon; - sampledData.push_back({0, lat, lon}); - } - } else { - // 简化后点数量合适,直接使用 - for (const auto &point : simplifiedPoints) { - double normX = (point.x - 50) / 300; - double normY = (point.y - 50) / 300; - double lat = minLat + (1.0 - normY) * rangeLat; - double lon = minLon + normX * rangeLon; - sampledData.push_back({0, lat, lon}); - } - } - } - } else { - // GPS点数不足100,直接使用全部数据 - sampledData = gpsData; - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, - "Using all %zu GPS points (no sampling)", sampledData.size()); - } - - // 映射到画布坐标 - canvasData.clear(); - canvasData.reserve(sampledData.size()); - - // 如果没有采样数据,直接返回 - if (sampledData.empty()) { - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "No sampled data to map!"); - return; - } - - // 基于采样数据重新计算经纬度范围 - double minLat = 90.0, maxLat = -90.0; - double minLon = 180.0, maxLon = -180.0; - - for (const auto &point : sampledData) { - minLat = getmin(minLat, point.latitude); - maxLat = getmax(maxLat, point.latitude); - minLon = getmin(minLon, point.longitude); - maxLon = getmax(maxLon, point.longitude); - } - - double rangeLat = maxLat - minLat; - double rangeLon = maxLon - minLon; - - // 避免除零错误 - if (rangeLat == 0) - rangeLat = 1e-10; - if (rangeLon == 0) - rangeLon = 1e-10; - - WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, - "Sampled GPS range: lat=[%.6f, %.6f], lon=[%.6f, %.6f]", minLat, - maxLat, minLon, maxLon); - - // 映射每个点到画布 - for (const auto &point : sampledData) { - double normLat = (point.latitude - minLat) / rangeLat; - double normLon = (point.longitude - minLon) / rangeLon; - - // 转换到画布坐标 - double x = normLon * 300 + 50; - double y = (1.0 - normLat) * 300 + 50; - - canvasData.push_back({x, y}); - WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, - "Mapped point: (%.6f, %.6f) -> (%.2f, %.2f)", point.latitude, - point.longitude, x, y); - } - - WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "Generated %zu canvas points", - canvasData.size()); - } +/** + * @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) [右上角] + */ +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; // 经度最大值(初始化为最小值,确保会被替换) + + for (const auto &point : gpsData) { + minLat = getmin(minLat, point.latitude); + maxLat = getmax(maxLat, point.latitude); + minLon = getmin(minLon, point.longitude); + maxLon = getmax(maxLon, point.longitude); + } + + // 计算经纬度的范围(跨度) + double rangeLat = maxLat - minLat; // 纬度跨度 + double rangeLon = maxLon - minLon; // 经度跨度 + + // 处理特殊情况:所有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); + + // 步骤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()); // 预分配内存,提高性能 + + 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 边界检查(确保坐标在画布范围内) + 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); +} // 添加经纬度数据集 void TraingModel::AddGPSData(uint32_t timestamp, double latitude, @@ -3065,11 +3008,46 @@ void TraingModel::SetSportdata(SportData *data) { return filePath; // 返回找到的文件路径 } } - WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "未找到与时间戳相关的找到gps表。"); - return ""; - } - - //获取GPS连接状态 + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "未找到与时间戳相关的找到gps表。"); + return ""; +} + +// 加载GPS数据 +bool TraingModel::LoadGPSData(const std::string &filePath) { + std::ifstream gpsFile(filePath, std::ios::binary); + if (!gpsFile) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "无法打开GPS文件: %s", + filePath.c_str()); + return false; + } + + // 清空现有数据 + gpsData.clear(); + + // 动态读取GPS数据 + GPSData data; + while (gpsFile.read(reinterpret_cast(&data), sizeof(GPSData))) { + gpsData.push_back(data); + } + + gpsFile.close(); + + if (gpsData.empty()) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "未读取到有效GPS数据: %s", + filePath.c_str()); + return false; + } + + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, + "LoadGPSData Success. 加载了 %zu 个GPS点", gpsData.size()); + + // 加载GPS数据后,重新生成canvas数据(使用所有GPS点) + GetPointData(); + + return true; +} + +//获取GPS连接状态 bool TraingModel::GetGPSConnectState() { return gpsconnect; } //设置GPS连接状态 void TraingModel::SetGPSConnectState(bool state) { -- 2.17.1