/**
* 点对象
* @typedef {Object} Point
* @property {number} x - X坐标
* @property {number} y - Y坐标
*/
/**
* 生成5条倾斜模型采集航线(0°, 45°, 90°, 135°, 自由航线)
* @param {Point[]} polygon - 多边形顶点数组(顺时针或逆时针顺序)
* @param {number} spacing - 航线间隔距离
* @param {number} [overlap=0.3] - 航线重叠率(0-1),默认30%
* @returns {Object} 包含5条航线的对象
*/
function generateFiveTiltedFlightPaths(polygon, spacing, overlap = 0.3) {
// 1. 生成0°航线(正北方向)
const path0 = generateTiltedFlightPath(polygon, spacing, 0, overlap);
// 2. 生成45°航线
const path45 = generateTiltedFlightPath(polygon, spacing, 45, overlap);
// 3. 生成90°航线(正东方向)
const path90 = generateTiltedFlightPath(polygon, spacing, 90, overlap);
// 4. 生成135°航线
const path135 = generateTiltedFlightPath(polygon, spacing, 135, overlap);
// 5. 生成自由航线(沿多边形最长边方向)
const freeAngle = calculateOptimalAngle(polygon);
const pathFree = generateTiltedFlightPath(polygon, spacing, freeAngle, overlap);
return {
path0: { angle: 0, points: path0 },
path45: { angle: 45, points: path45 },
path90: { angle: 90, points: path90 },
path135: { angle: 135, points: path135 },
pathFree: { angle: freeAngle, points: pathFree }
};
}
/**
* 生成倾斜航线
* @param {Point[]} polygon - 多边形顶点数组
* @param {number} spacing - 航线间隔
* @param {number} angle - 倾斜角度(度数)
* @param {number} overlap - 重叠率
* @returns {Point[]} 航线点数组
*/
function generateTiltedFlightPath(polygon, spacing, angle, overlap) {
const radians = angle * Math.PI / 180;
const cosAngle = Math.cos(radians);
const sinAngle = Math.sin(radians);
// 计算多边形旋转后的边界
const bounds = getRotatedBounds(polygon, radians);
// 计算有效间距(考虑重叠)
const effectiveSpacing = spacing * (1 - overlap);
// 生成扫描线位置
const scanLines = [];
for (let y = bounds.minY; y <= bounds.maxY; y += effectiveSpacing) {
scanLines.push(y);
}
const flightPath = [];
// 处理每条扫描线
scanLines.forEach(y => {
// 获取多边形与当前扫描线的交点
const intersections = getPolygonIntersections(polygon, y, radians);
// 交点按x坐标排序
intersections.sort((a, b) => a.x - b.x);
// 在每对交点之间生成航点
for (let i = 0; i < intersections.length; i += 2) {
if (i + 1 < intersections.length) {
const start = intersections[i];
const end = intersections[i + 1];
// 计算两点间距离
const dx = end.x - start.x;
const dy = end.y - start.y;
const length = Math.sqrt(dx * dx + dy * dy);
// 计算需要生成的航点数量
const pointCount = Math.ceil(length / spacing);
// 生成航点
for (let j = 0; j <= pointCount; j++) {
const t = j / pointCount;
const point = {
x: start.x + t * dx,
y: start.y + t * dy
};
flightPath.push(point);
}
}
}
});
return flightPath;
}
/**
* 计算多边形旋转后的边界
* @param {Point[]} polygon - 多边形顶点
* @param {number} radians - 旋转弧度
* @returns {Object} 边界 {minX, maxX, minY, maxY}
*/
function getRotatedBounds(polygon, radians) {
const cosAngle = Math.cos(radians);
const sinAngle = Math.sin(radians);
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
polygon.forEach(p => {
// 旋转点坐标
const x = p.x * cosAngle + p.y * sinAngle;
const y = -p.x * sinAngle + p.y * cosAngle;
minX = Math.min(minX, x);
maxX = Math.max(maxX, x);
minY = Math.min(minY, y);
maxY = Math.max(maxY, y);
});
return { minX, maxX, minY, maxY };
}
/**
* 获取多边形与扫描线的交点
* @param {Point[]} polygon - 多边形顶点
* @param {number} y - 扫描线y坐标(旋转后坐标系)
* @param {number} radians - 旋转弧度
* @returns {Point[]} 交点数组
*/
function getPolygonIntersections(polygon, y, radians) {
const cosAngle = Math.cos(radians);
const sinAngle = Math.sin(radians);
// 旋转多边形到水平位置
const rotatedPolygon = polygon.map(p => ({
x: p.x * cosAngle + p.y * sinAngle,
y: -p.x * sinAngle + p.y * cosAngle
}));
const intersections = [];
// 检查每条边与扫描线的交点
for (let i = 0; i < rotatedPolygon.length; i++) {
const p1 = rotatedPolygon[i];
const p2 = rotatedPolygon[(i + 1) % rotatedPolygon.length];
// 忽略水平边
if (Math.abs(p1.y - p2.y) < 1e-6) continue;
// 检查边是否跨越扫描线
if ((p1.y <= y && p2.y > y) || (p2.y <= y && p1.y > y)) {
// 计算交点x坐标
const t = (y - p1.y) / (p2.y - p1.y);
const x = p1.x + t * (p2.x - p1.x);
// 旋转回原始坐标系
intersections.push({
x: x * cosAngle - y * sinAngle,
y: x * sinAngle + y * cosAngle
});
}
}
return intersections;
}
/**
* 计算最优自由航线角度(沿多边形最长边方向)
* @param {Point[]} polygon - 多边形顶点
* @returns {number} 最优角度(度数)
*/
function calculateOptimalAngle(polygon) {
let maxLength = 0;
let optimalAngle = 0;
// 检查每条边
for (let i = 0; i < polygon.length; i++) {
const p1 = polygon[i];
const p2 = polygon[(i + 1) % polygon.length];
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const length = Math.sqrt(dx * dx + dy * dy);
if (length > maxLength) {
maxLength = length;
// 计算边与x轴的夹角(度数)
optimalAngle = Math.atan2(dy, dx) * 180 / Math.PI;
}
}
return optimalAngle;
}
// 示例用法
const polygon = [
{ x: 0, y: 0 },
{ x: 100, y: 0 },
{ x: 120, y: 50 },
{ x: 80, y: 80 },
{ x: 20, y: 60 }
];
const spacing = 20;
const overlap = 0.2; // 20%重叠
const flightPaths = generateFiveTiltedFlightPaths(polygon, spacing, overlap);
console.log("航线点数:", flightPaths);
// console.log("0°航线点数:", flightPaths.path0.points.length,flightPaths.path0.points);
// console.log("45°航线点数:", flightPaths.path45.points.length,flightPaths.path45.points);
// console.log("90°航线点数:", flightPaths.path90.points.length,flightPaths.path90.points);
// console.log("135°航线点数:", flightPaths.path135.points.length,flightPaths.path135.points);
// console.log("自由航线角度:", flightPaths.pathFree.angle, "° 点数:", flightPaths.pathFree.points.length,flightPaths.pathFree.points);
// 可视化示例(假设有绘图函数)
// drawPolygon(polygon);
// drawFlightPath(flightPaths.path0.points, 'red');
// drawFlightPath(flightPaths.path45.points, 'blue');
// drawFlightPath(flightPaths.path90.points, 'green');
// drawFlightPath(flightPaths.path135.points, 'purple');
// drawFlightPath(flightPaths.pathFree.points, 'orange');
console