修复轨迹显示不全的问题
authorCaisking <Casiking@zhihuishixun.com>
Mon, 19 Jan 2026 01:43:17 +0000 (09:43 +0800)
committerCaisking <Casiking@zhihuishixun.com>
Mon, 19 Jan 2026 01:43:17 +0000 (09:43 +0800)
code/sdk/application/wearable/fd272/nativeapp/nativeui/traing/include/TraingModel.h
code/sdk/application/wearable/fd272/nativeapp/nativeui/traing/src/TraingEnddataView.cpp
code/sdk/application/wearable/fd272/nativeapp/nativeui/traing/src/TraingModel.cpp

index 878b80c..11d7b2a 100644 (file)
@@ -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();
index a7d78ba..5c742c6 100644 (file)
@@ -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文件加载失败");
+          }
         }
       }
     }
index eb6faa4..8e6eb48 100644 (file)
@@ -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<GPSData> 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<double>(gpsData.size() - 1) / 99;
-       for (int i = 0; i < 100; i++) {
-         int index = static_cast<int>(i * step);
-         sampledData.push_back(gpsData[index]);
-       }
-     } else {
-       // 转换GPS坐标到屏幕坐标
-       std::vector<Point> 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<Point> 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<double>(gpsData.size() - 1) / 99;
-         for (int i = 0; i < 100; i++) {
-           int index = static_cast<int>(i * step);
-           sampledData.push_back(gpsData[index]);
-         }
-       } else if (simplifiedPoints.size() > 100) {
-         // 简化后仍超过100点,再次均匀采样
-         double step = static_cast<double>(simplifiedPoints.size() - 1) / 99;
-         for (int i = 0; i < 100; i++) {
-           int index = static_cast<int>(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<char *>(&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) {