优化GPS轨迹绘制,限制300个点
authorCaisking <Casiking@zhihuishixun.com>
Mon, 2 Feb 2026 09:28:33 +0000 (17:28 +0800)
committerCaisking <Casiking@zhihuishixun.com>
Mon, 2 Feb 2026 09:28:33 +0000 (17:28 +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 15382b0..8cc0af5 100644 (file)
@@ -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关闭
index 8ccbbf6..5522b82 100644 (file)
@@ -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]);
     }
index b9e20e0..310ab19 100644 (file)
                  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<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);
@@ -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<uint32_t>(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;