feat: polish & windviz & deploy
This commit is contained in:
parent
81b8e763bd
commit
465ad00f7b
78 changed files with 20622 additions and 2154 deletions
|
|
@ -2,7 +2,8 @@ package engine
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"predictor-refactored/internal/numerics"
|
||||
)
|
||||
|
||||
// Altitude triggers when the balloon altitude satisfies Op against Limit.
|
||||
|
|
@ -31,9 +32,9 @@ type Time struct {
|
|||
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 }
|
||||
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.
|
||||
|
|
@ -69,23 +70,30 @@ type PolygonVertex struct {
|
|||
Lng float64
|
||||
}
|
||||
|
||||
// Polygon is a constraint over a geographic polygon. The polygon is
|
||||
// considered closed (last vertex connects to the first) and is interpreted
|
||||
// in plate-carrée (rectangular lat/lng) coordinates with longitude
|
||||
// wrap-around handling.
|
||||
//
|
||||
// Edges crossing the 180/-180 antimeridian are split via longitude
|
||||
// normalisation against the polygon's centroid: callers that need
|
||||
// great-circle accuracy should clip their polygon along the antimeridian
|
||||
// before submitting.
|
||||
// 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 {
|
||||
|
|
@ -101,49 +109,9 @@ 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 {
|
||||
if len(c.Vertices) < 3 {
|
||||
return false
|
||||
}
|
||||
in := pointInPolygon(s.Lat, s.Lng, c.Vertices)
|
||||
in := numerics.PointInPolygon(s.Lat, s.Lng, c.polyLat, c.polyLng)
|
||||
if c.Mode == PolygonInside {
|
||||
return in
|
||||
}
|
||||
return !in
|
||||
}
|
||||
|
||||
// pointInPolygon implements the ray-casting algorithm in lat/lng space.
|
||||
//
|
||||
// All vertices and the query point are normalised to within 180° of
|
||||
// verts[0] before testing, so a polygon spanning the antimeridian is
|
||||
// handled correctly as long as the polygon itself spans no more than 180°
|
||||
// in longitude.
|
||||
func pointInPolygon(lat, lng float64, verts []PolygonVertex) bool {
|
||||
if len(verts) == 0 {
|
||||
return false
|
||||
}
|
||||
ref := verts[0].Lng
|
||||
qx := normLng(lng, ref)
|
||||
|
||||
inside := false
|
||||
n := len(verts)
|
||||
for i, j := 0, n-1; i < n; j, i = i, i+1 {
|
||||
yi, yj := verts[i].Lat, verts[j].Lat
|
||||
xi := normLng(verts[i].Lng, ref)
|
||||
xj := normLng(verts[j].Lng, ref)
|
||||
|
||||
if (yi > lat) != (yj > lat) {
|
||||
xIntersect := (xj-xi)*(lat-yi)/(yj-yi) + xi
|
||||
if qx < xIntersect {
|
||||
inside = !inside
|
||||
}
|
||||
}
|
||||
}
|
||||
return inside
|
||||
}
|
||||
|
||||
// normLng rewrites v so that it lies within 180° of ref. With ref=10 and
|
||||
// v=350, normLng returns -10.
|
||||
func normLng(v, ref float64) float64 {
|
||||
diff := math.Mod(v-ref+540, 360) - 180
|
||||
return ref + diff
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue