feat: add circle drawing support in path planner
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "spectral-insight-mission-plan"
|
||||
version = "0.0.2"
|
||||
version = "0.0.3"
|
||||
description = "Spectral Insight Mission Plan"
|
||||
authors = ["you"]
|
||||
edition = "2021"
|
||||
|
||||
@ -27,9 +27,11 @@ pub fn estimate_scan_time_minutes(params: ScanParams) -> f64 {
|
||||
services::path_generator::estimate_scan_time(¶ms)
|
||||
}
|
||||
|
||||
|
||||
#[command]
|
||||
pub fn generate_scan_paths_multi(
|
||||
regions: Vec<ScanRegion>,
|
||||
shapes: Vec<String>,
|
||||
camera: CameraParams,
|
||||
coverage_rate: f64,
|
||||
speed_y_cm_s: f64,
|
||||
@ -38,7 +40,7 @@ pub fn generate_scan_paths_multi(
|
||||
mode: ScanMode,
|
||||
) -> Result<PathLineFile, AppError> {
|
||||
let records = services::path_generator::generate_paths_multi(
|
||||
®ions, &camera, coverage_rate,
|
||||
®ions, &shapes, &camera, coverage_rate,
|
||||
speed_y_cm_s, speed_x_start_cm_s, speed_x_scan_cm_s, &mode,
|
||||
);
|
||||
let count = records.len() as u64;
|
||||
@ -48,6 +50,7 @@ pub fn generate_scan_paths_multi(
|
||||
#[command]
|
||||
pub fn estimate_scan_time_multi_minutes(
|
||||
regions: Vec<ScanRegion>,
|
||||
shapes: Vec<String>,
|
||||
camera: CameraParams,
|
||||
coverage_rate: f64,
|
||||
speed_y_cm_s: f64,
|
||||
@ -56,7 +59,7 @@ pub fn estimate_scan_time_multi_minutes(
|
||||
mode: ScanMode,
|
||||
) -> f64 {
|
||||
services::path_generator::estimate_scan_time_multi(
|
||||
®ions, &camera, coverage_rate,
|
||||
®ions, &shapes, &camera, coverage_rate,
|
||||
speed_y_cm_s, speed_x_start_cm_s, speed_x_scan_cm_s, &mode,
|
||||
)
|
||||
}
|
||||
|
||||
@ -103,8 +103,10 @@ pub fn estimate_scan_time(params: &ScanParams) -> f64 {
|
||||
}
|
||||
|
||||
/// Generate paths for multiple regions, concatenated into one record list.
|
||||
|
||||
pub fn generate_paths_multi(
|
||||
regions: &[ScanRegion],
|
||||
shapes: &[String],
|
||||
camera: &crate::models::CameraParams,
|
||||
coverage_rate: f64,
|
||||
speed_y_cm_s: f64,
|
||||
@ -113,24 +115,54 @@ pub fn generate_paths_multi(
|
||||
mode: &ScanMode,
|
||||
) -> Vec<PathLineRecord> {
|
||||
let mut all_records = Vec::new();
|
||||
for region in regions {
|
||||
let params = ScanParams {
|
||||
region: region.clone(),
|
||||
camera: camera.clone(),
|
||||
coverage_rate,
|
||||
speed_y_cm_s,
|
||||
speed_x_start_cm_s,
|
||||
speed_x_scan_cm_s,
|
||||
mode: mode.clone(),
|
||||
};
|
||||
all_records.extend(generate_path(¶ms));
|
||||
for (i, region) in regions.iter().enumerate() {
|
||||
let is_circle = shapes.get(i).map(|s| s == "Circle").unwrap_or(false);
|
||||
let step = calc_footprint(camera.height_cm, camera.fov_degrees)
|
||||
* (1.0 - coverage_rate / 100.0);
|
||||
let line_count = if step <= 0.0 { 1 } else { ((region.y_max - region.y_min) / step).ceil() as u64 + 1 };
|
||||
|
||||
for li in 0..line_count {
|
||||
let y = region.y_min + (li as f64) * step;
|
||||
if y > region.y_max { break; }
|
||||
|
||||
let (x_start, x_end) = if is_circle {
|
||||
let cx = (region.x_min + region.x_max) / 2.0;
|
||||
let cy = (region.y_min + region.y_max) / 2.0;
|
||||
let rx = (region.x_max - region.x_min) / 2.0;
|
||||
let ry = (region.y_max - region.y_min) / 2.0;
|
||||
// Ellipse: ((x-cx)/rx)^2 + ((y-cy)/ry)^2 = 1
|
||||
let dy = (y - cy) / ry;
|
||||
if dy.abs() > 1.0 { continue; } // outside circle
|
||||
let half_w = rx * (1.0 - dy * dy).sqrt();
|
||||
(cx - half_w, cx + half_w)
|
||||
} else {
|
||||
(region.x_min, region.x_max)
|
||||
};
|
||||
|
||||
let is_forward = li % 2 == 0;
|
||||
let (scan_start, scan_end) = match mode {
|
||||
ScanMode::Zigzag => {
|
||||
if is_forward { (x_start, x_end) } else { (x_end, x_start) }
|
||||
}
|
||||
ScanMode::OneWay => (x_start, x_end),
|
||||
};
|
||||
|
||||
all_records.push(PathLineRecord {
|
||||
target_y_position: y,
|
||||
speed_target_y_position: speed_y_cm_s,
|
||||
target_x_min_position: scan_start,
|
||||
speed_target_x_min_position: speed_x_start_cm_s,
|
||||
target_x_max_position: scan_end,
|
||||
speed_target_x_max_position: speed_x_scan_cm_s,
|
||||
});
|
||||
}
|
||||
}
|
||||
all_records
|
||||
}
|
||||
|
||||
/// Estimate total time for multiple regions.
|
||||
pub fn estimate_scan_time_multi(
|
||||
regions: &[ScanRegion],
|
||||
shapes: &[String],
|
||||
camera: &crate::models::CameraParams,
|
||||
coverage_rate: f64,
|
||||
speed_y_cm_s: f64,
|
||||
@ -138,18 +170,13 @@ pub fn estimate_scan_time_multi(
|
||||
speed_x_scan_cm_s: f64,
|
||||
mode: &ScanMode,
|
||||
) -> f64 {
|
||||
let mut total = 0.0;
|
||||
for region in regions {
|
||||
let params = ScanParams {
|
||||
region: region.clone(),
|
||||
camera: camera.clone(),
|
||||
coverage_rate,
|
||||
speed_y_cm_s,
|
||||
speed_x_start_cm_s,
|
||||
speed_x_scan_cm_s,
|
||||
mode: mode.clone(),
|
||||
};
|
||||
total += estimate_scan_time(¶ms);
|
||||
}
|
||||
total
|
||||
let records = generate_paths_multi(regions, shapes, camera, coverage_rate,
|
||||
speed_y_cm_s, speed_x_start_cm_s, speed_x_scan_cm_s, mode);
|
||||
if records.is_empty() { return 0.0; }
|
||||
|
||||
let total_x = records.len() as f64 * (regions.iter().map(|r| (r.x_max - r.x_min).abs()).sum::<f64>() / regions.len() as f64) / speed_x_scan_cm_s;
|
||||
let return_time = if matches!(mode, ScanMode::OneWay) {
|
||||
(records.len() as f64 - 1.0) * speed_x_scan_cm_s / speed_x_start_cm_s.max(0.01)
|
||||
} else { 0.0 };
|
||||
(total_x + return_time) / 60.0
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Spectral Insight Mission Plan",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.3",
|
||||
"identifier": "com.spectral-insight.mission-plan",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
|
||||
Reference in New Issue
Block a user