c++中四个点生成绕圈点集

有起点、起绕点、转弯绕点、终点这四个点以及一个时间,每个点都有经纬度、高度,用于生成在给定时间内,飞行这四个点的顺序,由于时间的问题,肯定需要生成一个向终点飞行的结束绕圈的脱离点

效果

直径圆

  • 起绕点<->转弯绕点为直径

8字圆(双直径)

  • 圆 A:entry → 中点 前半段为直径
  • 圆心 CA = entry→中点 中点
  • 半径 R = |entry-turn|/4
  • 圆 B:中点 → turn 后半段为直径
  • 圆心 CB = 中点→turn 中点
  • 半径同 R
  • 两圆仅在中点相切,走向相反,组成 ∞ 形
  • 顺序:entry → A(π) → B(π) → turn → B(π) → A(π) → entry → ...

跑道型 = 两半圆 + 2 条正方形直边 ,先圆后直

cpp 复制代码
顺时针 顺序
					边1
		entry◀─────────▶p1
		│                      │
		│                      │ 边2
		│                      │
		▼                      ▼
		p4◀─────────▶turn
				边3

逆时针 顺序
					边4
		entry◀─────────▶p4
		│                      ▲
	 边1│                      │ 边3
		│                      │
		▼                      │
		p1◀─────────▶turn
				边2
半圆 A 贴在 边1中点 外侧 → 圆心即中点

半圆 B 贴在 边3中点 外侧 → 圆心即中点

走向:
entry → A(π) → p1 → 边2 → turn → B(π) → p4 → 边4 →entry

跑道型 = 两半圆 + 2 条正方形直边 ,先直后圆

cpp 复制代码
顺时针顺序:
			  边1
	entry ◀──────▶ p1
	  │            │
	  │            │ 边2
	  │            │
	  ▼            ▼
	p4 ◀──────▶ turn
			  边3

逆时针顺序:
			  边4
	entry ◀──────▶ p4
	  │            ▲
   边1│            │边3
	  │            │
	  ▼            │
	p1 ◀──────▶ turn
			  边2

半圆A贴在边2中点外侧(圆心为边2中点)
半圆B贴在边4中点外侧(圆心为边4中点)

走向(顺时针):
entry → 边1 → p1 → A(π) → turn → 边3 → p4 → B(π) → entry

代码

cpp 复制代码
#pragma once

#include <QGuiApplication>
#include <QTime>
#include <QTimer>

#include <QtWidgets/QApplication>
#include <QtWidgets/QWidget>
#include <QPainter>
#include <QtMath>
/*
在c++qt中有起点、起绕点、转弯绕点、终点这四个点以及一个时间,每个点都有经纬度,高度,
写一套算法用于生成在给定时间内,飞行这四个点的顺序,由于时间的问题,肯定需要生成一个向终点飞行的结束绕圈的脱离点,
*/

// 基本点
struct WayPoint {
	double t;   // 秒
	double lat, lon; // 弧度
	double h;   // 米
	WayPoint() = default;
	WayPoint(double _t, double _lat, double _lon, double _h)
		:t(_t), lat(_lat), lon(_lon), h(_h) {
	}
	// 相等比较
	bool operator==(const WayPoint& other) const {
		return qFuzzyCompare(t, other.t)
			&& qFuzzyCompare(lat, other.lat)
			&& qFuzzyCompare(lon, other.lon)
			&& qFuzzyCompare(h, other.h);
	}
};

// 把 lat/lon(弧度) 转成局部平面米制坐标,以 p0 为原点
static void ll2local(double lat0, double lon0,
	double lat, double lon,
	double& x, double& y)
{
	const double Re = 6371000; // 地球平均半径
	double dlat = lat - lat0;
	double dlon = lon - lon0;
	y = dlat * Re;
	x = dlon * Re * cos(lat0);
}

// 平面米制坐标反算回 lat/lon
static void local2ll(double lat0, double lon0,
	double x, double y,
	double& lat, double& lon)
{
	const double Re = 6371000;
	double dlat = y / Re;
	double dlon = x / (Re * cos(lat0));
	lat = lat0 + dlat;
	lon = lon0 + dlon;
}



// 转弯模型
enum TurnModel
{
	TM_CIRCLE,	//直径圆
	TM_8_CIRCLE,// 8字圆(双直径)
	TM_RUNWAY_CIRCLE_LINE,	//  跑道型 = 两半圆 + 2 条正方形直边 ,先圆后直
	TM_RUNWAY_LINE_CIRCLE,	//  跑道型 = 两半圆 + 2 条正方形直边 ,先直后圆
};

// 旋转方向
enum  RotationDirection
{
	RD_CLOCKWISE,// 顺时针
	RD_COUNTERCLOCKWISE// 逆时针
};
/* 函数:生成航迹,时间驱动,形状固定
 * 输入:4 个航点(rad),总时间(s),地速(m/s),采样周期(s)
 * 输出:带时间戳的完整航迹
 * 极端时间:start→P→final 或 start→final 直线
 */
static QVector<WayPoint> planDiameterCircle2(
	const WayPoint& start,	/* 起点*/
	const WayPoint& entry,	/* 起绕点*/
	const WayPoint& turn,	/* 转弯绕点*/
	const WayPoint & final,	/* 终点*/
	double Ttotal,			/* 总飞行时间,单位:秒(s) */
	double V,				/* 地速,单位:米/秒(m/s) */
	double dt,				/* 采样周期,单位:秒(s) */
	TurnModel model = TM_CIRCLE,
	RotationDirection dir = RD_CLOCKWISE
)
{
	QVector<WayPoint> path;                       // 输出航迹

	/* 0. 局部坐标:以 start 为原点,米制(x,y) */
	double xs, ys, xe, ye, xt, yt, xf, yf;
	ll2local(start.lat, start.lon, start.lat, start.lon, xs, ys);  // (0,0)
	ll2local(start.lat, start.lon, entry.lat, entry.lon, xe, ye);  // entry 向量(m)
	ll2local(start.lat, start.lon, turn.lat, turn.lon, xt, yt);    // turn 向量(m)
	ll2local(start.lat, start.lon, final.lat, final.lon, xf, yf);  // final 向量(m)

	/* 1. 起点→起绕点 直线长度 & 时间 */
	double leg1 = hypot(xe - xs, ye - ys);  // 长度,单位:米(m)
	double t1 = leg1 / V;                 // 时间,单位:秒(s)  t1 = 距离/速度

	/* 2. 极端时间:t1 ≥ Ttotal 时,无法到达 entry,只能 start→P→final 两段直线 */
	if (t1 >= Ttotal)
	{
		/* 2.0 更极端:直飞 final 就够,连中间点都不需要 */
		double legSF = hypot(xf - xs, yf - ys);  // start→final 长度(m)
		double tSF = legSF / V;                // 所需时间(s)
		if (tSF >= Ttotal) {                     // 连直飞都来不及
			int N = qCeil(Ttotal / dt);          // 总采样点数
			for (int i = 0; i <= N; i++) {
				double k = i / double(N);        // 归一化时间 0→1
				double x = xs + k * (xf - xs);   // 直线插值 x(m)
				double y = ys + k * (yf - ys);   // 直线插值 y(m)
				double h = start.h + k * (final.h - start.h); // 高度插值(m)
				double lat, lon;
				local2ll(start.lat, start.lon, x, y, lat, lon);
				path.append(WayPoint(k * Ttotal, lat, lon, h)); // 时间戳(s)
			}
			return path;                         //  early return
		}

		/* 2.1 两段直线:start→P→final,总路程 = V·Ttotal
		 * 设 P = start + s·(entry-start),解二次方程求 s∈[0,1] */
		double dx = xe - xs, dy = ye - ys;     // start→entry 向量(m)
		double fx = xf - xs, fy = yf - ys;     // start→final 向量(m)
		double a = dx * dx + dy * dy;              // |entry-start|² (m²)
		double b = dx * fx + dy * fy;              // dot(entry-start, final-start) (m²)
		double c = fx * fx + fy * fy;              // |final-start|² (m²)
		double S = V * Ttotal;                 // 总路程(m)  S = V·Ttotal
		double delta = b * b - a * (c - S * S);      // 二次方程判别式(m⁴)
		double s = 0;                            // 脱离比例 0≤s≤1
		if (delta >= 0) {
			double s1 = (-b + qSqrt(delta)) / a; // 公式解 1
			double s2 = (-b - qSqrt(delta)) / a; // 公式解 2
			s = qBound(0.0, s1, 1.0);
			if (s < 0 || s > 1) s = qBound(0.0, s2, 1.0);
		}
		/* 2.2 脱离点 P(局部米) */
		double px = xs + s * dx;               // P.x = start.x + s·dx(m)
		double py = ys + s * dy;               // P.y = start.y + s·dy(m)
		double ph = start.h + s * (entry.h - start.h); // P.h 线性插值(m)

		/* 2.3 统一时间轴采样:总点数 = Ttotal/dt,全程匀速 V */
		int N = qCeil(Ttotal / dt);
		for (int i = 0; i <= N; i++) {
			double k = i / double(N);            // 归一化时间 0→1
			double x, y, h;
			if (k <= s) {                        // 第一段 start→P
				double r = k / s;                // 路程比例 0→1
				x = xs + r * (px - xs);          // 直线插值 x(m)
				y = ys + r * (py - ys);          // 直线插值 y(m)
				h = start.h + r * (ph - start.h);// 直线插值 h(m)
			}
			else {                               // 第二段 P→final
				double r = (k - s) / (1.0 - s);  // 路程比例 0→1
				x = px + r * (xf - px);          // 直线插值 x(m)
				y = py + r * (yf - py);          // 直线插值 y(m)
				h = ph + r * (final.h - ph);     // 直线插值 h(m)
			}
			double lat, lon;
			local2ll(start.lat, start.lon, x, y, lat, lon);
			path.append(WayPoint(k * Ttotal, lat, lon, h)); // 时间戳(s)
		}
		return path;                             //  early return
	}

	/* 3. 正常情况:时间足够,生成 start→entry 完整直线 */
	int N1 = qCeil(t1 / dt);
	for (int i = 0; i <= N1; i++) {
		double s = i * dt / t1;                  // 归一化路程 0→1
		double x = xs + (xe - xs) * s;           // 直线插值 x(m)
		double y = ys + (ye - ys) * s;           // 直线插值 y(m)
		double h = start.h + (entry.h - start.h) * s; // 直线插值 h(m)
		double lat, lon;
		local2ll(start.lat, start.lon, x, y, lat, lon);
		path.append(WayPoint(i * dt, lat, lon, h)); // 时间戳(s)
	}

	/* 4. 剩余时间 Tleft = Ttotal - t1 */
	double Tleft = Ttotal - t1;                // 单位:秒(s)

	/* 4.0 极端:剩余时间只够 entry→final 直飞,不绕圈 */
	double legSF = hypot(xf - xe, yf - ye);  // entry→final 长度(m)
	double tSF = legSF / V;                // 所需时间(s)
	if (tSF >= Tleft) {                      // 连切线都不够
		int N = qCeil(Tleft / dt);
		for (int i = 0; i <= N; i++) {
			double k = i / double(N);        // 归一化时间 0→1
			double x = xe + k * (xf - xe);   // 直线插值 x(m)
			double y = ye + k * (yf - ye);   // 直线插值 y(m)
			double h = entry.h + k * (final.h - entry.h); // 直线插值 h(m)
			double lat, lon;
			local2ll(start.lat, start.lon, x, y, lat, lon);
			path.append(WayPoint(t1 + k * Tleft, lat, lon, h)); // 时间戳(s)
		}
		return path;                         //  early return
	}

	const int cw = (dir == RD_CLOCKWISE) ? 1 : -1;

	if (model == TM_CIRCLE)
	{
		/*起绕点<->转弯绕点为直径*/
		/* 5. 固定几何:直径圆 + 切线(形状固定,时间可任意) */
		double cx = (xe + xt) * 0.5;             // 圆心 x(m)
		double cy = (ye + yt) * 0.5;             // 圆心 y(m)
		double R = hypot(xt - xe, yt - ye) * 0.5; // 半径(m)  R = |entry-turn|/2
		double OFx = xf - cx, OFy = yf - cy;     // 圆心→final 向量(m)
		double D = hypot(OFx, OFy);             // 圆心到 final 距离(m)
		double alphaExit = qAcos(R / D);         // 切线角(rad)  cosα = R/D
		double ang0 = qAtan2(ye - cy, xe - cx); // entry 对应角度(rad)
		double angEnd = qAtan2(OFy, OFx) - alphaExit; // 切点角度(rad)
		if (angEnd < ang0) angEnd += 2 * M_PI;   // 保证在 ang0 之后

		double Sline = qSqrt(D * D - R * R);     // 切线长度(m)  Sline = √(D²-R²)
		double TlineMin = Sline / V;             // 最低切线时间(s)
		double TarcMax = Tleft - TlineMin;      // 留给圆弧的极限时间(s)
		if (TarcMax < 0) TarcMax = 0;            // 下限 0
		double Tarc = TarcMax;                  // 真实圆弧时间(s)
		double Tline = Tleft - Tarc;             // 真实切线时间(s)
		double theta = Tarc * V / R;             // 真实绕角(rad)  θ = Tarc·V/R
		double omega = theta / Tarc;             // 角速度(rad/s)  ω = θ/Tarc

		/* 6. 圆弧段:绕 θ 角,脱离点始终在切点 */
		int Narc = qCeil(Tarc / dt);             // 圆弧采样点数
		for (int i = 0; i <= Narc; i++) {
			double tau = i * dt;                   // 当前弧段时间(s)
			double a = ang0 - omega * tau* cw;       // 当前角度(rad)

			double x = cx + R * qCos(a);         // 当前 x(m)
			double y = cy + R * qSin(a);         // 当前 y(m)
			double h = entry.h + (turn.h - entry.h) * (tau / Tarc); // 高度线性(m)
			double lat, lon;
			local2ll(start.lat, start.lon, x, y, lat, lon);
			path.append(WayPoint(t1 + tau, lat, lon, h)); // 时间戳(s)
		}

		/* 7. 直线段:切点 → final */
		double px = cx + R * qCos(ang0 - theta * cw); // 切点 x(m)
		double py = cy + R * qSin(ang0 - theta * cw); // 切点 y(m)
		double ph = entry.h + (turn.h - entry.h); // 切点高度(m)
		int Nline = qCeil(Tline / dt);           // 切线采样点数
		for (int i = 0; i <= Nline; i++) {
			double s = i * dt / Tline;             // 归一化路程 0→1
			double x = px + s * (xf - px);         // 直线插值 x(m)
			double y = py + s * (yf - py);         // 直线插值 y(m)
			double h = ph + s * (final.h - ph);    // 直线插值 h(m)
			double lat, lon;
			local2ll(start.lat, start.lon, x, y, lat, lon);
			path.append(WayPoint(t1 + Tarc + i * dt, lat, lon, h)); // 时间戳(s)
		}
	}
	else if (model == TM_8_CIRCLE)
	{
		/* 5. 模式二:8 字双半圆 + 切线脱离
		 * 圆 A:entry → 中点 前半段为直径
		 *   圆心 CA = entry→中点 中点
		 *   半径 R = |entry-turn|/4
		 * 圆 B:中点 → turn 后半段为直径
		 *   圆心 CB = 中点→turn 中点
		 *   半径同 R
		 * 两圆仅在中点相切,走向相反,组成 ∞ 形
		 * 顺序:entry → A(π) → B(π) → turn → B(π) → A(π) → entry → ...
		 * 脱离:当前圆上切点 → final 直线
		 */

		/* 先算出 entry↔turn 中点(公共相切点)*/
		double mx = (xe + xt) * 0.5;               // 中点 x(m)
		double my = (ye + yt) * 0.5;               // 中点 y(m)
		double R = hypot(xt - xe, yt - ye) * 0.25; // 公共半径(m)  R = |entry-turn|/4

		/* 圆 A:entry → 中点 前半段直径 */
		double CAx = (xe + mx) * 0.5;              // 圆A中心 x(m)  CA = entry→中点 中点
		double CAy = (ye + my) * 0.5;              // 圆A中心 y(m)

		/* 圆 B:中点 → turn 后半段直径 */
		double CBx = (mx + xt) * 0.5;              // 圆B中心 x(m)  CB = 中点→turn 中点
		double CBy = (my + yt) * 0.5;              // 圆B中心 y(m)

		/* 5.2 单半圈时间:Tsemi = π·R / V (s) */
		double Tsemi = M_PI * R / V;               // 半圈时间(s)  Tsemi = πR/V

		/* 5.5 高度:每半圈增量 */
		int   nSemiTot = qCeil(Tleft / Tsemi);
		double dHsemi = (turn.h - entry.h) / nSemiTot;// 每半圈高度增量
		/* 5.6 真 ∞ 8 字 + 切线脱离:重写采样循环 */
		int Nsamp = qCeil(Tleft / dt);

		for (int i = 0; i <= Nsamp; i++) {
			double tau = i * dt;                     // 当前时间(s)
			double tArc = tau;                        // 圆弧内时间(s)
			int nSemi = int(tArc / Tsemi);				// 第 n 个半圈
			double tSemi = tArc - nSemi * Tsemi;       // 半圈内时间(s)

			int    phase = nSemi & 3;                    // 0,1,2,3 只用来选圆心
			/* 当前圆心 & 半径 */
			double cx = 0, cy = 0;
			switch (phase) {
			case 0: cx = CAx; cy = CAy; break;   // A 前半:entry→中点
			case 1: cx = CBx; cy = CBy; break;   // B 后半:中点→turn
			case 2: cx = CBx; cy = CBy; break;   // B 前半:turn→中点
			case 3: cx = CAx; cy = CAy; break;   // A 后半:中点→entry
			}
			/* 4 段循环:A前→B后→B前→A后→A前→...(走向相反,交叉 ∞)*/
			/* ---- 起始角 ---- */
			double a0 = 0;
			if (phase == 0)      a0 = qAtan2(ye - CAy, xe - CAx);
			else if (phase == 1) a0 = qAtan2(my + CBy, mx + CBx);
			else if (phase == 2) a0 = qAtan2(yt - CBy, xt - CBx);
			else                 a0 = qAtan2(my - CAy, mx - CAx);

			/* ---- 当前角度 ---- */
			double ang = tSemi / Tsemi * M_PI;// 0→π(rad)
			//if (!clockwise) ang = -(M_PI - ang);// B 反向:π→0,交叉 ∞
			double a = a0;

			if (phase == 0) {
				a -= ang * cw;
			}
			else if (phase == 1) {
				ang = -(M_PI - ang);
				a += ang * cw;
			}
			else if (phase == 2)
			{
				a += ang * cw;
			}
			else if (phase == 3)
			{
				a -= ang * cw;
			}
			a = qAtan2(qSin(a), qCos(a));       // 正规化到 [-π,π]


			/* 当前位置 & 高度 */
			double x = cx + R * qCos(a);             // x(m)
			double y = cy + R * qSin(a);             // y(m)
			double h = entry.h + dHsemi * (tArc / Tsemi); // 高度线性(m)
			
			/* 脱离判断:
		   当角度 ≥ 切点角,且剩余时间足够切线,则直接进入切线模式,按当前圆实时计算 */
		   /* ---------- 1. 当前圆切线几何 ---------- */
			double OFx = xf - cx, OFy = yf - cy;		// 圆→final 向量(m)
			double D = hypot(OFx, OFy);					// 圆心到 final 距离(m)
			double alphaE = (D > R) ? qAcos(R / D) : 0.0;// 切线角(rad)  cosα = R/D
			double TlineNeed = qSqrt(D * D - R * R) / V;     // 当前圆→final 必须切线时间

			/* ---------- 2. 脱离条件 ---------- */
			bool  canExit = (tau >= (Tleft - TlineNeed));
			if (canExit)
			{
				double tLine = tau - (Tleft - TlineNeed);	// 直线内时间(s)
				double px = x;			// 脱离点 x(m)
				double py = y;			// 脱离点 y(m)
				double ph = h;			// 脱离点高度(m)
				const int N = Nsamp - i + 1;
				for (int j = 1; j <= N; j++) {
					double k = j / double(N);        // 归一化时间 0→1
					double x = px + k * (xf - px);   // 直线插值 x(m)
					double y = py + k * (yf - py);   // 直线插值 y(m)
					double h = ph + k * (final.h - ph); // 直线插值 h(m)
					double lat, lon;
					local2ll(start.lat, start.lon, x, y, lat, lon);
					path.append(WayPoint(t1 + k * tLine, lat, lon, h)); // 时间戳(s)
				}

				break; // 退出主循环
			}


			double lat, lon;
			local2ll(start.lat, start.lon, x, y, lat, lon);
			path.append(WayPoint(t1 + tau, lat, lon, h)); // 时间戳(s)
		}
	}
	else if (model == TM_RUNWAY_CIRCLE_LINE)      // 模式三:跑道型 = 两半圆 + 2 条正方形直边 ,先圆后直
	{
			/*
		顺时针 顺序
							边1
				entry◀─────────▶p1
				│                      │
				│                      │ 边2
				│                      │
				▼                      ▼
				p4◀─────────▶turn
						边3

		逆时针 顺序
							边4
				entry◀─────────▶p4
				│                      ▲
			 边1│                      │ 边3
				│                      │
				▼                      │
				p1◀─────────▶turn
						边2
		半圆 A 贴在 边1中点 外侧 → 圆心即中点

		半圆 B 贴在 边3中点 外侧 → 圆心即中点

		走向:
		entry → A(π) → p1 → 边2 → turn → B(π) → p4 → 边4 →entry
		*/

		/* ---------------- 几何预计算 ---------------- */
		double dx = xt - xe;
		double dy = yt - ye;
		double L_diag = hypot(dx, dy);               // 对角线长度
		double L_side = L_diag / M_SQRT2;            // 正方形边长
		double R = L_side * 0.5;                // 半圆半径

		/* 对角线单位向量 */
		double ux = dx / L_diag;
		double uy = dy / L_diag;

		// 顺时针 45°
		double s45_x = ux * M_SQRT1_2 + uy * M_SQRT1_2; // cos45·ux + sin45·uy
		double s45_y = -ux * M_SQRT1_2 + uy * M_SQRT1_2; // -sin45·ux + cos45·uy
		double s45_px = xe + s45_x * L_side;             // 边远端
		double s45_py = ye + s45_y * L_side;

		// 逆时针 45°
		double n45_x = ux * M_SQRT1_2 - uy * M_SQRT1_2; // cos45·ux - sin45·uy
		double n45_y = ux * M_SQRT1_2 + uy * M_SQRT1_2; // sin45·ux + cos45·uy

		/* 四个角点(顺时针) */
		double n45_px = xe + n45_x * L_side;               // 边远端
		double n45_py = ye + n45_y * L_side;
		double vx = 0., vy = 0.;	// 边1方向
		double p1x = 0., p1y = 0.;	// 边1远端
		double wx = 0., wy = 0.;	// 边4方向
		double p4x = 0., p4y = 0.;	// 边4远端
		if (dir == RD_CLOCKWISE)
		{
			vx = n45_x;
			vy = n45_y;
			p1x = n45_px;
			p1y = n45_py;

			wx = s45_x;
			wy = s45_y;
			p4x = s45_px;
			p4y = s45_py;
		}
		else
		{
			vx = s45_x;
			vy = s45_y;
			p1x = s45_px;
			p1y = s45_py;

			wx = n45_x;
			wy = n45_y;
			p4x = n45_px;
			p4y = n45_py;
		}

		/* 圆心:边1中点 与 边3中点 */
		double CAx = (xe + p1x) * 0.5;               // 圆 A 圆心(边1中点)
		double CAy = (ye + p1y) * 0.5;
		double CBx = (xt + p4x) * 0.5;               // 圆 B 圆心(边3中点)
		double CBy = (yt + p4y) * 0.5;

		/* 两段直线方向(单位) */
		/* ---------- 直线方向(用角点差一次性算对) ---------- */
		double sx2 = xt - p1x;                      // 边2方向 p1→turn
		double sy2 = yt - p1y;
		double len2 = hypot(sx2, sy2);               // 必 = L_side,可断言
		sx2 /= len2;  sy2 /= len2;                    // 单位化

		double sx4 = xe - p4x;                      // 边4方向 turn→p4
		double sy4 = ye - p4y;
		double len4 = hypot(sx4, sy4);
		sx4 /= len4;  sy4 /= len4;

		/* 单段耗时 */
		double Tsemi = M_PI * R / V;
		double Tstraight = L_side / V;
		double Tloop = 2 * (Tsemi + Tstraight);   // 一整圈时间

		/* 每圈高度增量 */
		int   nLoopTot = qCeil(Tleft / Tloop);
		double dHloop = (turn.h - entry.h) / nLoopTot;

		/* ---------------- 采样循环 ---------------- */
		int Nsamp = qCeil(Tleft / dt);
		for (int i = 0; i <= Nsamp; ++i) {
			double tau = i * dt;
			double tRem = tau;
			int   nLoop = int(tau / Tloop);
			tRem -= nLoop * Tloop;                 // 本圈剩余时间

			/*顺时针 4 段顺序:entry → A(π) → p1 → 边2 → turn → B(π) → p4 → 边4 →entry*/
			double cx = 0, cy = 0, a0 = 0;
			double px1 = 0, py1 = 0;                // 直线段起点
			double px2 = 0, py2 = 0;                // 直线段终点
			bool   inArc = true;                    // 当前是否圆弧

			if (tRem < Tsemi) {                      // 段1:A 半圆
				cx = CAx;  cy = CAy;
				a0 = qAtan2(ye - CAy, xe - CAx);
				px1 = xe + vx * L_side;              // 直线段起点
				py1 = ye + vy * L_side;
			}
			else if ((tRem -= Tsemi) < Tstraight) {  // 段2:边2 p1→turn
				inArc = false;
				px1 = p1x;  py1 = p1y;                 // 直线起点
				px2 = xt;   py2 = yt;                  // 直线终点
			}
			else if ((tRem -= Tstraight) < Tsemi) {  // 段3:B 半圆
				cx = CBx;  cy = CBy;
				a0 = qAtan2(yt - CBy, xt - CBx);	  // 圆弧终点是 p4
				px1 = p4x;  py1 = p4y;                // 段4起点
			}
			else {                                   // 段4:边4 p4→entry
				inArc = false;
				tRem -= Tsemi;
				px1 = p4x;  py1 = p4y;                // 直线起点
				px2 = xe;   py2 = ye;                  // 直线终点
			}
			/* ---- 当前位置 ---- */
			double x = 0, y = 0, h = 0;
			if (inArc) {
				double ang = tRem / Tsemi * M_PI;
				double a = a0 - ang*cw;            // 顺时针:角度递减
				x = cx + R * cos(a);
				y = cy + R * sin(a);
				h = entry.h + dHloop * (nLoop + tRem / Tsemi); // 圆弧内插值
			}
			else {
				double s = tRem / Tstraight;
				x = px1 + s * (px2 - px1);
				y = py1 + s * (py2 - py1);
				h = entry.h + dHloop * (nLoop + 0.5 + s);      // 直线段插值
			}

			/* ---- 脱离判断(按当前圆或当前直线段实时算)---- */
			double OFx = xf - x, OFy = yf - y;
			double D = hypot(OFx, OFy);
			double Tneed = D / V;                  // 当前点到 final 必须时间
			bool canExit = (tau >= (Tleft - Tneed));
			if (canExit) {
				double s = (Tleft - tau) / Tneed;  // 剩余路程比例
				double px = x, py = y, ph = h;
				const int N = Nsamp - i + 1;
				for (int j = 1; j <= N; j++) {
					double k = j / double(N);        // 归一化时间 0→1
					double lx = px + k * (xf - px);
					double ly = py + k * (yf - py);
					double lh = ph + k * (final.h - ph);
					double lat, lon;
					local2ll(start.lat, start.lon, lx, ly, lat, lon);
					path.append(WayPoint(t1 + tau + k * (Tleft - tau), lat, lon, lh));
				}
				return path;                       // 完成,退出
			}

			/* ---- 正常落地 ---- */
			double lat, lon;
			local2ll(start.lat, start.lon, x, y, lat, lon);
			path.append(WayPoint(t1 + tau, lat, lon, h));
		}
	}
	else if (model == TM_RUNWAY_LINE_CIRCLE) // 模式四:跑道型 = 两半圆 + 2条正方形直边,先直后圆
	{
		/*
		顺时针顺序:
					  边1
			entry ◀──────▶ p1
			  │            │
			  │            │ 边2
			  │            │
			  ▼            ▼
			p4 ◀──────▶ turn
					  边3

		逆时针顺序:
					  边4
			entry ◀──────▶ p4
			  │            ▲
		   边1│            │边3
			  │            │
			  ▼            │
			p1 ◀──────▶ turn
					  边2

		半圆A贴在边2中点外侧(圆心为边2中点)
		半圆B贴在边4中点外侧(圆心为边4中点)

		走向(顺时针):
		entry → 边1 → p1 → A(π) → turn → 边3 → p4 → B(π) → entry
		*/

		/* ---------------- 几何预计算 ---------------- */
		double dx = xt - xe;
		double dy = yt - ye;
		double L_diag = hypot(dx, dy);             // 对角线长度
		double L_side = L_diag / M_SQRT2;          // 正方形边长
		double R = L_side * 0.5;                    // 半圆半径

		/* 对角线单位向量 */
		double ux = dx / L_diag;
		double uy = dy / L_diag;

		// 顺时针45°方向向量
		double s45_x = ux * M_SQRT1_2 + uy * M_SQRT1_2;
		double s45_y = -ux * M_SQRT1_2 + uy * M_SQRT1_2;
		double s45_px = xe + s45_x * L_side;         // 边远端
		double s45_py = ye + s45_y * L_side;

		// 逆时针45°方向向量
		double n45_x = ux * M_SQRT1_2 - uy * M_SQRT1_2;
		double n45_y = ux * M_SQRT1_2 + uy * M_SQRT1_2;
		double n45_px = xe + n45_x * L_side;         // 边远端
		double n45_py = ye + n45_y * L_side;

		/* 四个角点(根据方向选择) */
		double vx = 0., vy = 0.;    // 边1方向
		double p1x = 0., p1y = 0.;  // 边1远端
		double wx = 0., wy = 0.;    // 边4方向
		double p4x = 0., p4y = 0.;  // 边4远端

		if (dir == RD_CLOCKWISE) {
			vx = n45_x; vy = n45_y;
			p1x = n45_px; p1y = n45_py;
			wx = s45_x; wy = s45_y;
			p4x = s45_px; p4y = s45_py;
		}
		else {
			vx = s45_x; vy = s45_y;
			p1x = s45_px; p1y = s45_py;
			wx = n45_x; wy = n45_y;
			p4x = n45_px; p4y = n45_py;
		}

		/* 圆心:边2中点(半圆A) 和 边4中点(半圆B) */
		double CAx = (p1x + xt) * 0.5;   // 半圆A圆心(边2中点)
		double CAy = (p1y + yt) * 0.5;
		double CBx = (p4x + xe) * 0.5;   // 半圆B圆心(边4中点)
		double CBy = (p4y + ye) * 0.5;

		/* 直线段方向向量(单位化) */
		double sx1 = p1x - xe;          // 边1方向
		double sy1 = p1y - ye;
		double len1 = hypot(sx1, sy1);
		sx1 /= len1; sy1 /= len1;

		double sx3 = p4x - xt;          // 边3方向
		double sy3 = p4y - yt;
		double len3 = hypot(sx3, sy3);
		sx3 /= len3; sy3 /= len3;

		double sx4 = xe - p4x;          // 边4方向
		double sy4 = ye - p4y;
		double len4 = hypot(sx4, sy4);
		sx4 /= len4; sy4 /= len4;

		/* 时间参数 */
		double Tstraight = L_side / V;   // 单段直线时间
		double Tsemi = M_PI * R / V;      // 半圆时间
		double Tloop = 2 * (Tstraight + Tsemi); // 单圈总时间

		/* 每圈高度增量 */
		int nLoopTot = qCeil(Tleft / Tloop);
		double dHloop = (turn.h - entry.h) / nLoopTot;

		/* ---------------- 采样循环 ---------------- */
		int Nsamp = qCeil(Tleft / dt);
		for (int i = 0; i <= Nsamp; ++i) {
			double tau = i * dt;
			double tRem = tau;
			int nLoop = static_cast<int>(tau / Tloop);
			tRem -= nLoop * Tloop;         // 本圈剩余时间

			/* 顺时针四段顺序:边1 → 半圆A → 边3 → 半圆B */
			double cx = 0, cy = 0, a0 = 0;
			double px1 = 0, py1 = 0;        // 直线段起点
			double px2 = 0, py2 = 0;        // 直线段终点
			bool inArc = true;              // 当前是否圆弧段
			double tInSeg = tRem;           // 当前段内时间

			if (tRem < Tstraight) {         // 段1:直线边1 (entry→p1)
				inArc = false;
				px1 = xe; py1 = ye;
				px2 = p1x; py2 = p1y;
			}
			else if ((tInSeg = tRem - Tstraight) < Tsemi) { // 段2:半圆A (p1→turn)
				inArc = true;
				cx = CAx; cy = CAy;
				a0 = qAtan2(p1y - CAy, p1x - CAx);
			}
			else if ((tInSeg = tRem - Tstraight - Tsemi) < Tstraight) { // 段3:直线边3 (turn→p4)
				inArc = false;
				px1 = xt; py1 = yt;
				px2 = p4x; py2 = p4y;
			}
			else {                          // 段4:半圆B (p4→entry)
				inArc = true;
				tInSeg = tRem - Tstraight - Tsemi - Tstraight;
				cx = CBx; cy = CBy;
				a0 = qAtan2(p4y - CBy, p4x - CBx);
			}

			/* ---- 计算当前位置 ---- */
			double x = 0, y = 0, h = 0;
			if (inArc) {
				// 圆弧段:角度随时间变化
				double ang = tInSeg / Tsemi * M_PI;
				double a = a0 - ang * cw;   // 顺时针角度递减
				x = cx + R * cos(a);
				y = cy + R * sin(a);
				// 高度插值:当前段内线性变化
				h = entry.h + dHloop * (nLoop + tInSeg / Tsemi);
			}
			else {
				// 直线段:线性插值
				double s = tInSeg / Tstraight;
				x = px1 + s * (px2 - px1);
				y = py1 + s * (py2 - py1);
				// 高度插值:段中心高度 + 偏移
				h = entry.h + dHloop * (nLoop + 0.5 + s);
			}

			/* ---- 脱离判断(检查是否能到达终点)---- */
			double OFx = xf - x, OFy = yf - y;
			double D = hypot(OFx, OFy);
			double Tneed = D / V;  // 到达终点所需时间
			bool canExit = (tau >= (Tleft - Tneed));

			if (canExit) {
				// 生成剩余路径点
				double s = (Tleft - tau) / Tneed;
				double px = x, py = y, ph = h;
				const int N = Nsamp - i + 1;
				for (int j = 1; j <= N; j++) {
					double k = j / static_cast<double>(N);
					double lx = px + k * (xf - px);
					double ly = py + k * (yf - py);
					double lh = ph + k * (final.h - ph);
					double lat, lon;
					local2ll(start.lat, start.lon, lx, ly, lat, lon);
					path.append(WayPoint(t1 + tau + k * (Tleft - tau), lat, lon, lh));
				}
				return path;  // 完成路径生成
			}

			/* ---- 记录常规路径点 ---- */
			double lat, lon;
			local2ll(start.lat, start.lon, x, y, lat, lon);
			path.append(WayPoint(t1 + tau, lat, lon, h));
		}
	}
	return path;                                 // 完整航迹
}



// 画图窗口
class Plot : public QWidget
{
	Q_OBJECT
public:
	Plot(const QVector<WayPoint>& all,
		const QVector<WayPoint>& four,
		QWidget* parent = nullptr)
		: QWidget(parent), way(all), input4(four)
	{
		//setFixedSize(800, 600);
		setWindowTitle(u8"航迹显示");
		
		startTime = QTime::currentTime();
		QTimer* t = new QTimer(this);
		connect(t, &QTimer::timeout, this, QOverload<>::of(&QWidget::update));
		t->start(50); // 20 fps
	}

protected:
	void paintEvent(QPaintEvent*) override
	{
		QPainter p(this);
		p.setRenderHint(QPainter::Antialiasing);

		/* 1. 边界:包含生成航迹 + 4 个输入点 */
		double minLat = way.first().lat, maxLat = way.first().lat;
		double minLon = way.first().lon, maxLon = way.first().lon;
		double minH = way.first().h, maxH = way.first().h;
		for (auto& v : way) {
			minLat = qMin(minLat, v.lat); maxLat = qMax(maxLat, v.lat);
			minLon = qMin(minLon, v.lon); maxLon = qMax(maxLon, v.lon);
			minH = qMin(minH, v.h);     maxH = qMax(maxH, v.h);
		}
		for (auto& v : input4) {
			minLat = qMin(minLat, v.lat); maxLat = qMax(maxLat, v.lat);
			minLon = qMin(minLon, v.lon); maxLon = qMax(maxLon, v.lon);
			minH = qMin(minH, v.h);     maxH = qMax(maxH, v.h);
		}
		double Cx = (minLat + maxLat) / 2;   // 中心纬度(rad)
		double Cy = (minLon + maxLon) / 2;   // 中心经度(rad)
		double R = qMax(maxLat - minLat, maxLon - minLon) * 0.5; // 半跨度(rad)

		/* 2. 动态尺寸 */
		int W = this->width();                 // 窗口总宽(px)
		int H = this->height();                // 窗口总高(px)
		int side = qMin(W, H);                 // 取短边作为参考(px)
		int mapW = W / 2;                      // 俯视图区域宽(px)  左半
		int mapH = H;                          // 俯视图区域高(px)  全高
		int hgtW = W - mapW;                   // 高度图区域宽(px)  右半
		int hgtH = H;                          // 高度图区域高(px)  全高

		float scale = side / 600.0f;           // 相对 600px 的缩放系数
		int   pointR = qBound(3, int(6 * scale), 20); // 红点半径(px)
		int   lineW = qBound(1, int(2 * scale), 6);  // 线宽(px)
		const double angle_half = 180.0; // 俯视图半宽对应角度(rad)
		/* 3. 俯视图:左半区域,居中绘制 */
		p.setPen(QPen(Qt::black, 1));
		p.drawRect(0, 0, mapW, mapH);
		p.setPen(QPen(Qt::blue, lineW));
		for (int i = 1; i < way.size(); i++) {
			int x1 =mapW/2 + ((way[i - 1].lon - Cy) / R * angle_half);
			int y1 = mapH / 2 - ((way[i - 1].lat - Cx) / R * angle_half);
			int x2 =mapW/2 + ((way[i].lon - Cy) / R * angle_half);
			int y2 = mapH / 2 - ((way[i].lat - Cx) / R * angle_half);
			p.drawLine(x1, y1, x2, y2);
		}
		// 4 个输入点
#if 0
		p.setPen(Qt::NoPen);
		p.setBrush(QBrush(Qt::red));
		for (const auto& w : input4) {
			int x = mapW / 2 + ((w.lon - Cy) / R * angle_half);
			int y = mapH / 2 - ((w.lat - Cx) / R * angle_half);
			p.drawEllipse(QPoint(x, y), pointR, pointR);
		}
#else
		p.setPen(Qt::NoPen);
		QFont font = p.font();
		font.setPixelSize(pointR * 3);   // 字号 ≈ 3 倍圆环半径
		font.setBold(true);
		p.setFont(font);

		int i_number = 0;
		for (const auto& w : input4) {
			int x = mapW / 2 + ((w.lon - Cy) / R * angle_half);
			int y = mapH / 2 - ((w.lat - Cx) / R * angle_half);

			/* -------- 外圈圆环 -------- */
			QPainterPath ring;
			ring.addEllipse(QPointF(x, y), pointR, pointR);
			ring.addEllipse(QPointF(x, y), pointR * 0.6, pointR * 0.6); // 内圈挖空
			p.setBrush(QBrush(Qt::red));
			p.drawPath(ring);

			/* -------- 序号 -------- */
			p.setPen(QPen(Qt::green));
			p.drawText(QRectF(x - pointR, y - pointR, pointR * 2, pointR * 2),
				Qt::AlignCenter, QString::number(++i_number));
			p.setPen(Qt::NoPen);   // 恢复无笔
		}
#endif
		
		// 当前飞机
		//double t = startTime.elapsed() / 1000.0;// 1倍速
		//double t = startTime.elapsed() / 100.0;// 10倍速
		double t = startTime.elapsed() / 100.;// 100倍速
		if (t > way.last().t) t = way.last().t;
		int idx = (t / way.last().t * (way.size() - 1));
		int px =mapW/2 + ((way[idx].lon - Cy) / R * angle_half);
		int py = mapH / 2 - ((way[idx].lat - Cx) / R * angle_half);
		p.setBrush(Qt::red);
		p.setPen(Qt::NoPen);
		p.drawEllipse(px - pointR, py - pointR, 2 * pointR, 2 * pointR);

		/* 4. 高度剖面:右半区域 */
		p.setPen(QPen(Qt::black, 1));
		p.drawRect(mapW, 0, hgtW, hgtH);
		p.setPen(QPen(Qt::green, lineW));
		double dx = way.last().t;
		double dy = maxH - minH;
		for (int i = 1; i < way.size(); i++) {
			int x1 = mapW + (hgtW * way[i - 1].t / dx);
			int y1 = (hgtH * 0.9 * (1.0 - (way[i - 1].h - minH) / dy) + hgtH * 0.05);
			int x2 = mapW + (hgtW * way[i].t / dx);
			int y2 = (hgtH * 0.9 * (1.0 - (way[i].h - minH) / dy) + hgtH * 0.05);
			p.drawLine(x1, y1, x2, y2);
		}
		// 当前高度
		int hx = mapW + (hgtW * t / dx);
		int hy = (hgtH * 0.9 * (1.0 - (way[idx].h - minH) / dy) + hgtH * 0.05);
		p.setBrush(Qt::red);
		p.drawEllipse(hx - pointR, hy - pointR, 2 * pointR, 2 * pointR);
	}

private:
	QVector<WayPoint> way;
	QVector<WayPoint> input4;   // 仅 4 个原始点
	QTime startTime;
};
cpp 复制代码
#if 0
	WayPoint start(0, qDegreesToRadians(39.9), qDegreesToRadians(116.4), 100);
	WayPoint entry(0, qDegreesToRadians(69.5), qDegreesToRadians(101.0), 150);
	WayPoint turn(0, qDegreesToRadians(49.3), qDegreesToRadians(126.2), 120);
	WayPoint final(0, qDegreesToRadians(19.1), qDegreesToRadians(106.8), 80);
#else

	WayPoint start(0, qDegreesToRadians(39.6), qDegreesToRadians(116.9), 150);
	WayPoint entry(0, qDegreesToRadians(39.5), qDegreesToRadians(117.0), 150);
	WayPoint turn(0, qDegreesToRadians(39.3), qDegreesToRadians(117.2), 120);
	WayPoint final(0, qDegreesToRadians(39.4), qDegreesToRadians(116.5), 80);
#endif

	/*总时间 Ttotal(s),地速 V(m/s),采样周期 dt(s)*/
	double Ttotal = 1800 ;
	double V = 250;  // m/s
	double dt = 1.0; // 输出间隔

	QVector<WayPoint> w, fourPts;
	{
		//QVector<WayPoint> track = planDiameterCircle2(start, entry, turn, final, Ttotal, V, dt);
		//QVector<WayPoint> track = planDiameterCircle2(start, entry, turn, final, Ttotal, V, dt, TM_8_CIRCLE);
		//QVector<WayPoint> track = planDiameterCircle2(start, entry, turn, final, Ttotal, V, dt, TM_CIRCLE,RD_COUNTERCLOCKWISE);
#if 0
		
		QVector<WayPoint> track = planDiameterCircle2(start, entry, turn, final, Ttotal, V, dt, TM_RUNWAY_CIRCLE_LINE, RD_CLOCKWISE);

#else		
		
		QVector<WayPoint> track = planDiameterCircle2(start, entry, turn, final, Ttotal, V, dt, TM_RUNWAY_LINE_CIRCLE, RD_CLOCKWISE);
#endif
	
		for (auto p : track)
		{
			
			p.lat = qRadiansToDegrees(p.lat);// 转为度
			p.lon = qRadiansToDegrees(p.lon);// 转为度
			w.append(p);
		}

		QVector<WayPoint>  input4= { start,entry,turn,final }; // 原始输入
		for (auto p : input4)
		{
			p.lat = qRadiansToDegrees(p.lat);
			p.lon = qRadiansToDegrees(p.lon);
			fourPts.append(p);
		}
	}
	
	Plot plot(w, fourPts);
	plot.showMaximized();
	
相关推荐
小小晓.4 小时前
Pinely Round 4 (Div. 1 + Div. 2)
c++·算法
SHOJYS4 小时前
学习离线处理 [CSP-J 2022 山东] 部署
数据结构·c++·学习·算法
steins_甲乙4 小时前
C++并发编程(3)——资源竞争下的安全栈
开发语言·c++·安全
煤球王子5 小时前
学而时习之:C++中的异常处理2
c++
仰泳的熊猫5 小时前
1084 Broken Keyboard
数据结构·c++·算法·pat考试
我不会插花弄玉5 小时前
C++的内存管理【由浅入深-C++】
c++
CSDN_RTKLIB5 小时前
代码指令与属性配置
开发语言·c++
上不如老下不如小5 小时前
2025年第七届全国高校计算机能力挑战赛 决赛 C++组 编程题汇总
开发语言·c++
雍凉明月夜5 小时前
c++ 精学笔记记录Ⅱ
开发语言·c++·笔记·vscode
GHL2842710906 小时前
文件重命名(C++源码)
前端·c++·windows