145 lines
4 KiB
Go
145 lines
4 KiB
Go
package v2
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"predictor-refactored/internal/engine"
|
|
"predictor-refactored/internal/weather"
|
|
)
|
|
|
|
// buildProfile translates a PredictionRequest into an engine.Profile.
|
|
//
|
|
// elev may be nil when no terrain dataset is loaded; TerrainContact constraints
|
|
// will return an error in that case.
|
|
func buildProfile(req PredictionRequest, field weather.WindField, elev engine.TerrainProvider, warnings *engine.Warnings) (engine.Profile, error) {
|
|
if len(req.Profile) == 0 {
|
|
return engine.Profile{}, fmt.Errorf("profile must contain at least one stage")
|
|
}
|
|
|
|
step := req.Options.StepSeconds
|
|
if step == 0 {
|
|
step = 60
|
|
}
|
|
tol := req.Options.Tolerance
|
|
if tol == 0 {
|
|
tol = 0.01
|
|
}
|
|
|
|
dir := engine.Forward
|
|
switch req.Direction {
|
|
case "", "forward":
|
|
dir = engine.Forward
|
|
case "reverse":
|
|
dir = engine.Reverse
|
|
default:
|
|
return engine.Profile{}, fmt.Errorf("unknown direction %q", req.Direction)
|
|
}
|
|
|
|
props := make([]*engine.Propagator, len(req.Profile))
|
|
for i, stage := range req.Profile {
|
|
model, err := buildModel(stage.Model, field, warnings)
|
|
if err != nil {
|
|
return engine.Profile{}, fmt.Errorf("stage %q: %w", stage.Name, err)
|
|
}
|
|
constraints, err := buildConstraints(stage.Constraints, elev)
|
|
if err != nil {
|
|
return engine.Profile{}, fmt.Errorf("stage %q: %w", stage.Name, err)
|
|
}
|
|
props[i] = &engine.Propagator{
|
|
Name: stage.Name,
|
|
Step: step,
|
|
Model: model,
|
|
Constraints: constraints,
|
|
Tolerance: tol,
|
|
}
|
|
}
|
|
|
|
// Wire fallbacks once all stages exist.
|
|
for i, stage := range req.Profile {
|
|
if stage.FallbackIndex == nil {
|
|
continue
|
|
}
|
|
idx := *stage.FallbackIndex
|
|
if idx < 0 || idx >= len(props) {
|
|
return engine.Profile{}, fmt.Errorf("stage %q: fallback_index %d out of range", stage.Name, idx)
|
|
}
|
|
props[i].Fallback = props[idx]
|
|
}
|
|
|
|
return engine.Profile{Stages: props, Direction: dir}, nil
|
|
}
|
|
|
|
func buildModel(spec ModelSpec, field weather.WindField, warnings *engine.Warnings) (engine.Model, error) {
|
|
var base engine.Model
|
|
switch spec.Type {
|
|
case "constant_rate":
|
|
base = engine.ConstantRate(spec.Rate)
|
|
case "parachute_descent":
|
|
if spec.SeaLevelRate <= 0 {
|
|
return nil, fmt.Errorf("parachute_descent requires positive sea_level_rate")
|
|
}
|
|
base = engine.ParachuteDescent(spec.SeaLevelRate)
|
|
case "piecewise":
|
|
segs := make([]engine.RateSegment, len(spec.Segments))
|
|
for i, s := range spec.Segments {
|
|
segs[i] = engine.RateSegment{Until: s.Until, Rate: s.Rate}
|
|
}
|
|
base = engine.Piecewise(segs)
|
|
case "wind":
|
|
if field == nil {
|
|
return nil, fmt.Errorf("wind model requires a loaded dataset")
|
|
}
|
|
return engine.WindTransport(field, warnings), nil
|
|
default:
|
|
return nil, fmt.Errorf("unknown model type %q", spec.Type)
|
|
}
|
|
|
|
if spec.IncludeWind {
|
|
if field == nil {
|
|
return nil, fmt.Errorf("include_wind requires a loaded dataset")
|
|
}
|
|
return engine.Sum(base, engine.WindTransport(field, warnings)), nil
|
|
}
|
|
return base, nil
|
|
}
|
|
|
|
func buildConstraints(specs []ConstraintSpec, elev engine.TerrainProvider) ([]engine.Constraint, error) {
|
|
out := make([]engine.Constraint, 0, len(specs))
|
|
for _, spec := range specs {
|
|
action, err := parseAction(spec.Action)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var c engine.Constraint
|
|
switch spec.Type {
|
|
case "max_altitude":
|
|
c = engine.MaxAltitude{Limit: spec.Limit, On: action}
|
|
case "min_altitude":
|
|
c = engine.MinAltitude{Limit: spec.Limit, On: action}
|
|
case "max_time":
|
|
c = engine.MaxTime{Limit: spec.Limit, On: action}
|
|
case "terrain_contact":
|
|
if elev == nil {
|
|
return nil, fmt.Errorf("terrain_contact requires an elevation dataset")
|
|
}
|
|
c = engine.TerrainContact{Provider: elev, On: action}
|
|
default:
|
|
return nil, fmt.Errorf("unknown constraint type %q", spec.Type)
|
|
}
|
|
out = append(out, c)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func parseAction(s string) (engine.Action, error) {
|
|
switch s {
|
|
case "", "stop":
|
|
return engine.ActionStop, nil
|
|
case "fallback":
|
|
return engine.ActionFallback, nil
|
|
case "clip":
|
|
return engine.ActionClip, nil
|
|
default:
|
|
return 0, fmt.Errorf("unknown constraint action %q", s)
|
|
}
|
|
}
|