///|
fn route_horizon_profile(
evidence : @mission.LocalHorizonEvidence,
) -> Array[RouteHorizonSampleView] {
evidence.azimuth_samples.map(fn(sample) {
{
label: sample.label,
azimuth_center_deg: sample.azimuth_center_deg,
horizon_angle_deg: sample.horizon_angle_deg,
obstruction_elevation_m: sample.obstruction_elevation_m,
}
})
}
///|
fn route_mission_window_view(
search : @mission.MissionWindowSearchEvidence,
route_id : String,
) -> RouteMissionWindowView {
for candidate in search.candidates {
if candidate.route_id == route_id {
return {
decision: @mission.traverse_decision_label(candidate.decision),
time_start_utc: candidate.time_start_utc,
time_end_utc: candidate.time_end_utc,
terrain_sunlit_hours: candidate.terrain_sunlit_hours,
longest_dark_hours: candidate.longest_dark_hours,
terrain_available_energy_wh: candidate.terrain_available_energy_wh,
minimum_solar_clearance_deg: candidate.minimum_solar_clearance_deg,
maximum_solar_clearance_deg: candidate.maximum_solar_clearance_deg,
meets_illumination_constraint: candidate.meets_illumination_constraint,
}
}
}
abort("mission-window search is missing route \{route_id}")
}
///|
fn route_views(
routes : Array[@mission.RouteCandidate],
selected_route_id : String,
window_search : @mission.MissionWindowSearchEvidence,
) -> Array[RouteView] {
let views : Array[RouteView] = []
for route in routes {
views.push({
route_id: route.route_id,
label: route.label,
selected: route.route_id == selected_route_id,
decision: @mission.route_candidate_status_label(route),
score: route.score,
max_grade: route.expected_max_grade,
roughness_m: route.expected_roughness_m,
evidence_tile_id: route.evidence_tile_id,
illumination: {
decision: @mission.illumination_decision_label(route.illumination),
confidence: route.illumination.confidence,
relief_shadow_risk: route.illumination.relief_shadow_risk,
max_horizon_angle_deg: route.illumination.local_horizon.max_horizon_angle_deg,
max_sun_altitude_deg: route.illumination.local_horizon.max_sun_altitude_deg,
evaluated_sample_count: route.illumination.local_horizon.evaluated_sample_count,
spherical_sunlit_hours: route.illumination.local_horizon.spherical_sunlit_hours,
terrain_sunlit_hours: route.illumination.local_horizon.terrain_sunlit_hours,
terrain_shadowed_hours: route.illumination.local_horizon.terrain_shadowed_hours,
longest_dark_hours: route.illumination.local_horizon.longest_dark_hours,
terrain_available_energy_wh: route.illumination.local_horizon.terrain_available_energy_wh,
minimum_solar_clearance_deg: route.illumination.local_horizon.minimum_solar_clearance_deg,
maximum_solar_clearance_deg: route.illumination.local_horizon.maximum_solar_clearance_deg,
bounded_horizon: route.illumination.local_horizon.output_path != "",
time_start_utc: route.illumination.constraint.time_start_utc,
time_end_utc: route.illumination.constraint.time_end_utc,
power_window_source_path: route.illumination.constraint.power_window_source_path,
horizon_evidence_path: if route.illumination.local_horizon.output_path !=
"" {
route.illumination.local_horizon.output_path
} else {
route.illumination.constraint.power_window_source_path
},
horizon_profile: route_horizon_profile(route.illumination.local_horizon),
recommended_window: route_mission_window_view(
window_search,
route.route_id,
),
},
next_action: route.next_action,
})
}
views
}
///|
fn route_overlays(
routes : Array[@mission.RouteCandidate],
selected_route_id : String,
) -> Array[RouteOverlay] {
let overlays : Array[RouteOverlay] = []
for route in routes {
let decision = @mission.route_candidate_status_label(route)
overlays.push({
overlay_id: "route-overlay/\{route.route_id}",
route_id: route.route_id,
label: route.label,
selected: route.route_id == selected_route_id,
decision,
evidence_tile_id: route.evidence_tile_id,
viewport_ref: "evidence-window:\{route.evidence_tile_id}",
status: decision,
summary: "\{route.evidence_tile_id}: grade \{route.expected_max_grade}, roughness \{route.expected_roughness_m} m",
})
}
overlays
}