117 lines
3.5 KiB
Go
117 lines
3.5 KiB
Go
package engine
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"predictor-refactored/internal/numerics"
|
|
)
|
|
|
|
// Altitude triggers when the balloon altitude satisfies Op against Limit.
|
|
//
|
|
// Examples:
|
|
//
|
|
// Altitude{Op: OpGreaterEqual, Limit: 30000} — burst at 30 km
|
|
// Altitude{Op: OpLessEqual, Limit: 0} — sea-level descent termination
|
|
type Altitude struct {
|
|
Op Operator
|
|
Limit float64
|
|
On Action
|
|
}
|
|
|
|
func (c Altitude) Name() string {
|
|
return fmt.Sprintf("altitude %s %g", c.Op, c.Limit)
|
|
}
|
|
func (c Altitude) Violated(_ float64, s State) bool { return c.Op.Test(s.Altitude, c.Limit) }
|
|
func (c Altitude) Action() Action { return c.On }
|
|
|
|
// Time triggers when the integration time t (UNIX seconds) satisfies Op
|
|
// against Limit.
|
|
type Time struct {
|
|
Op Operator
|
|
Limit float64
|
|
On Action
|
|
}
|
|
|
|
func (c Time) Name() string { return fmt.Sprintf("time %s %g", c.Op, c.Limit) }
|
|
func (c Time) Violated(t float64, _ State) bool { return c.Op.Test(t, c.Limit) }
|
|
func (c Time) Action() Action { return c.On }
|
|
|
|
// TerrainContact triggers when the ground elevation exceeds the balloon's
|
|
// altitude — i.e. the balloon has hit the ground.
|
|
type TerrainContact struct {
|
|
Provider TerrainProvider
|
|
On Action
|
|
}
|
|
|
|
func (c TerrainContact) Name() string { return "terrain_contact" }
|
|
func (c TerrainContact) Violated(_ float64, s State) bool {
|
|
return c.Provider.Elevation(s.Lat, s.Lng) > s.Altitude
|
|
}
|
|
func (c TerrainContact) Action() Action { return c.On }
|
|
|
|
// PolygonMode selects whether Polygon fires when the balloon is inside or
|
|
// outside the configured polygon.
|
|
type PolygonMode int
|
|
|
|
const (
|
|
// PolygonInside fires when (lat, lng) lies inside the polygon — useful
|
|
// for "must not enter restricted airspace".
|
|
PolygonInside PolygonMode = iota
|
|
// PolygonOutside fires when (lat, lng) lies outside the polygon —
|
|
// useful for "must remain over the test range".
|
|
PolygonOutside
|
|
)
|
|
|
|
// PolygonVertex is one vertex of a geographic polygon. Latitudes are in
|
|
// degrees [-90, 90]; longitudes in degrees [0, 360) or [-180, 180]
|
|
// (callers normalise — see Polygon.Violated).
|
|
type PolygonVertex struct {
|
|
Lat float64
|
|
Lng float64
|
|
}
|
|
|
|
// Polygon is a constraint over a closed geographic polygon, evaluated in
|
|
// plate-carrée coordinates with antimeridian handling (see
|
|
// numerics.PointInPolygon). Build one with NewPolygon so the flattened
|
|
// vertex slices used by the hot path are precomputed.
|
|
type Polygon struct {
|
|
Vertices []PolygonVertex
|
|
Mode PolygonMode
|
|
On Action
|
|
// Label, if set, is returned by Name. Defaults to "polygon_inside" or
|
|
// "polygon_outside" based on Mode.
|
|
Label string
|
|
|
|
// Precomputed parallel vertex slices for numerics.PointInPolygon.
|
|
polyLat, polyLng []float64
|
|
}
|
|
|
|
// NewPolygon builds a Polygon, precomputing the flattened vertex slices.
|
|
func NewPolygon(verts []PolygonVertex, mode PolygonMode, on Action, label string) Polygon {
|
|
lat := make([]float64, len(verts))
|
|
lng := make([]float64, len(verts))
|
|
for i, v := range verts {
|
|
lat[i], lng[i] = v.Lat, v.Lng
|
|
}
|
|
return Polygon{Vertices: verts, Mode: mode, On: on, Label: label, polyLat: lat, polyLng: lng}
|
|
}
|
|
|
|
func (c Polygon) Name() string {
|
|
if c.Label != "" {
|
|
return c.Label
|
|
}
|
|
if c.Mode == PolygonOutside {
|
|
return "polygon_outside"
|
|
}
|
|
return "polygon_inside"
|
|
}
|
|
func (c Polygon) Action() Action { return c.On }
|
|
|
|
// Violated reports whether the state satisfies the polygon-containment rule.
|
|
func (c Polygon) Violated(_ float64, s State) bool {
|
|
in := numerics.PointInPolygon(s.Lat, s.Lng, c.polyLat, c.polyLng)
|
|
if c.Mode == PolygonInside {
|
|
return in
|
|
}
|
|
return !in
|
|
}
|