step one
This commit is contained in:
parent
7a8d5d13fa
commit
9e663db9dc
68 changed files with 5647 additions and 2958 deletions
145
internal/api/v2/profile.go
Normal file
145
internal/api/v2/profile.go
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue