Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 4 additions & 2 deletions aviation/airport.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,10 @@ func (ap *Airport) PostDeserialize(icao string, loc Locator, nmPerLongitude floa
e.Pop()
}

if _, ok := controlPositions[ap.DepartureController]; !ok && ap.DepartureController != "" {
e.ErrorString("departure_controller %q unknown", ap.DepartureController)
if ap.DepartureController != "" {
if _, ok := controlPositions[ap.DepartureController]; !ok {
e.ErrorString("departure_controller %q unknown", ap.DepartureController)
}
}

// Departure routes are specified in the JSON as comma-separated lists
Expand Down
7 changes: 0 additions & 7 deletions aviation/aviation.go
Original file line number Diff line number Diff line change
Expand Up @@ -1053,13 +1053,6 @@ func (ar *Arrival) PostDeserialize(loc Locator, nmPerLongitude float32, magnetic
e.ErrorString("controller %q not found for \"initial_controller\"", ar.InitialController)
}

// TODO: Change for only STARS scenarios
// for id, controller := range controlPositions {
// if controller.ERAMFacility && controller.FacilityIdentifier == "" {
// e.ErrorString("%q is an ERAM facility, but has no facility id specified", id)
// }
// }

if !checkScratchpad(ar.Scratchpad) {
e.ErrorString("%s: invalid scratchpad", ar.Scratchpad)
}
Expand Down
24 changes: 9 additions & 15 deletions aviation/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,25 @@ package aviation
type ControlPosition string

type Controller struct {
Position string // This is the key in the controllers map in JSON
Position string // This is the key in the controllers map in JSON (now the sector ID, e.g. "1N", "4P")
Callsign string `json:"callsign,omitempty"` // Human-readable callsign (e.g. "PHL_NA_APP")
RadioName string `json:"radio_name"`
Frequency Frequency `json:"frequency"`
SectorID string `json:"sector_id"` // e.g. N56, 2J, ...
Scope string `json:"scope_char"` // Optional. If unset, facility id is used for external, last char of sector id for local.
FacilityIdentifier string `json:"facility_id"` // For example the "N" in "N4P" showing the N90 TRACON
ERAMFacility bool `json:"eram_facility"` // To weed out N56 and N4P being the same fac
Facility string `json:"facility"` // So we can get the STARS facility from a controller
DefaultAirport string `json:"default_airport"` // only required if CRDA is a thing
Scope string `json:"scope_char"` // Optional. If unset, facility id is used for external, last char of position for local.
FacilityIdentifier string `json:"-"` // Set programmatically by loadNeighborControllers (e.g. "N" in "N4P")
ERAMFacility bool `json:"eram_facility"` // To weed out N56 and N4P being the same fac
Facility string `json:"facility"` // So we can get the STARS facility from a controller
Area int `json:"-"` // Auto-derived from first digit of Position (e.g., "1A" -> area 1)
}

func (c Controller) IsExternal() bool {
return c.ERAMFacility || c.FacilityIdentifier != ""
}

func (c Controller) PositionId() ControlPosition {
if c.ERAMFacility {
return ControlPosition(c.SectorID)
}
return ControlPosition(c.FacilityIdentifier + c.SectorID)
return ControlPosition(c.FacilityIdentifier + c.Position)
}

func (c Controller) ERAMID() string { // For display
if c.ERAMFacility {
return c.SectorID // Already includes the facility letter
}
return c.FacilityIdentifier + c.SectorID
return c.FacilityIdentifier + c.Position
}
83 changes: 56 additions & 27 deletions aviation/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import (
"encoding/xml"
"fmt"
"io"
"io/fs"
"maps"
"net/http"
"os"
"path"
"slices"
"strconv"
"strings"
Expand Down Expand Up @@ -47,9 +49,9 @@ type StaticDatabase struct {
Airlines map[string]Airline
MagneticGrid MagneticGrid
ARTCCs map[string]ARTCC
ERAMAdaptations map[string]ERAMAdaptation
TRACONs map[string]TRACON
MVAs map[string][]MVA // TRACON -> MVAs
ERAMAdaptations map[string]ERAMAdaptation
BravoAirspace map[string][]AirspaceVolume
CharlieAirspace map[string][]AirspaceVolume
DeltaAirspace map[string][]AirspaceVolume
Expand Down Expand Up @@ -745,32 +747,6 @@ func parseMagneticGrid() MagneticGrid {
return mg
}

func parseAdaptations() map[string]ERAMAdaptation {
adaptations := make(map[string]ERAMAdaptation)

r := util.LoadResource("adaptations.json")
defer r.Close()
if err := util.UnmarshalJSON(r, &adaptations); err != nil {
fmt.Fprintf(os.Stderr, "adaptations.json: %v\n", err)
os.Exit(1)
}

// Wire up names in the structs
for artcc, adapt := range adaptations {
adapt.ARTCC = artcc

for fix, fixes := range adapt.CoordinationFixes {
for i := range fixes {
fixes[i].Name = fix
}
}

adaptations[artcc] = adapt
}

return adaptations
}

func (mg *MagneticGrid) Lookup(p math.Point2LL) (float32, error) {
if p[0] < mg.MinLongitude || p[0] > mg.MaxLongitude ||
p[1] < mg.MinLatitude || p[1] > mg.MaxLatitude {
Expand Down Expand Up @@ -1010,6 +986,59 @@ func parseARTCCsAndTRACONs() (map[string]ARTCC, map[string]TRACON) {
return artccs, tracons
}

func parseAdaptations() map[string]ERAMAdaptation {
adaptations := make(map[string]ERAMAdaptation)

resourcesFS := util.GetResourcesFS()
entries, err := fs.ReadDir(resourcesFS, "configurations")
if err != nil {
fmt.Fprintf(os.Stderr, "configurations directory: %v\n", err)
os.Exit(1)
}

type artccConfig struct {
StarsConfig struct {
CoordinationFixes map[string]AdaptationFixes `json:"coordination_fixes"`
} `json:"stars_config"`
}

for _, entry := range entries {
if !entry.IsDir() {
continue
}
artcc := entry.Name()
if len(artcc) != 3 || artcc[0] != 'Z' {
continue
}

configPath := path.Join("configurations", artcc, artcc+".json")
r := util.LoadResource(configPath)

var config artccConfig
if err := util.UnmarshalJSON(r, &config); err != nil {
r.Close()
fmt.Fprintf(os.Stderr, "%s: %v\n", configPath, err)
os.Exit(1)
}
r.Close()

adapt := ERAMAdaptation{
ARTCC: artcc,
CoordinationFixes: config.StarsConfig.CoordinationFixes,
}

for fix, fixes := range adapt.CoordinationFixes {
for i := range fixes {
fixes[i].Name = fix
}
}

adaptations[artcc] = adapt
}

return adaptations
}

func parseAirspace(filename string) map[string][]AirspaceVolume {
aj := util.LoadResource(filename)
defer aj.Close()
Expand Down
64 changes: 32 additions & 32 deletions aviation/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,38 +24,38 @@ import (
// Waypoint

type Waypoint struct {
Fix string `json:"fix"`
Location math.Point2LL // not provided in scenario JSON; derived from fix
AltitudeRestriction *AltitudeRestriction `json:"altitude_restriction,omitempty"`
Speed int `json:"speed,omitempty"`
Heading int `json:"heading,omitempty"` // outbound heading after waypoint
Turn int `json:",omitempty"` // 1 = left, 2= right
PresentHeading bool `json:",omitempty"`
ProcedureTurn *ProcedureTurn `json:"pt,omitempty"`
NoPT bool `json:"nopt,omitempty"`
HumanHandoff bool `json:"human_handoff,omitempty"` // Handoff to human controller
HandoffController ControlPosition `json:"tcp_handoff,omitempty"` // Controller position for handoff
PointOut ControlPosition `json:"pointout,omitempty"`
ClearApproach bool `json:"clear_approach,omitempty"` // used for distractor a/c, clears them for the approach passing the wp.
FlyOver bool `json:"flyover,omitempty"`
Delete bool `json:"delete,omitempty"`
Land bool `json:"land,omitempty"`
Arc *DMEArc `json:"arc,omitempty"`
IAF, IF, FAF bool `json:",omitempty"` // not provided in scenario JSON; derived from fix
Airway string `json:",omitempty"` // when parsing waypoints, this is set if we're on an airway after the fix
OnSID, OnSTAR bool `json:",omitempty"` // set during deserialization
OnApproach bool `json:",omitempty"` // set during deserialization
AirworkRadius int `json:",omitempty"` // set during deserialization
AirworkMinutes int `json:",omitempty"` // set during deserialization
Radius float32 `json:",omitempty"`
Shift float32 `json:",omitempty"`
PrimaryScratchpad string `json:",omitempty"`
ClearPrimaryScratchpad bool `json:",omitempty"`
SecondaryScratchpad string `json:",omitempty"`
ClearSecondaryScratchpad bool `json:",omitempty"`
TransferComms bool `json:",omitempty"`
ClimbAltitude *int `json:",omitempty"` // altitude in feet to climb to when passing waypoint
DescendAltitude *int `json:",omitempty"` // altitude in feet to descend to when passing waypoint
Fix string `json:"fix"`
Location math.Point2LL // not provided in scenario JSON; derived from fix
AltitudeRestriction *AltitudeRestriction `json:"altitude_restriction,omitempty"`
Speed int `json:"speed,omitempty"`
Heading int `json:"heading,omitempty"` // outbound heading after waypoint
Turn int `json:",omitempty"` // 1 = left, 2= right
PresentHeading bool `json:",omitempty"`
ProcedureTurn *ProcedureTurn `json:"pt,omitempty"`
NoPT bool `json:"nopt,omitempty"`
HumanHandoff bool `json:"human_handoff,omitempty"` // Handoff to human controller
HandoffController ControlPosition `json:"tcp_handoff,omitempty"` // Controller position for handoff
PointOut ControlPosition `json:"pointout,omitempty"`
ClearApproach bool `json:"clear_approach,omitempty"` // used for distractor a/c, clears them for the approach passing the wp.
FlyOver bool `json:"flyover,omitempty"`
Delete bool `json:"delete,omitempty"`
Land bool `json:"land,omitempty"`
Arc *DMEArc `json:"arc,omitempty"`
IAF, IF, FAF bool `json:",omitempty"` // not provided in scenario JSON; derived from fix
Airway string `json:",omitempty"` // when parsing waypoints, this is set if we're on an airway after the fix
OnSID, OnSTAR bool `json:",omitempty"` // set during deserialization
OnApproach bool `json:",omitempty"` // set during deserialization
AirworkRadius int `json:",omitempty"` // set during deserialization
AirworkMinutes int `json:",omitempty"` // set during deserialization
Radius float32 `json:",omitempty"`
Shift float32 `json:",omitempty"`
PrimaryScratchpad string `json:",omitempty"`
ClearPrimaryScratchpad bool `json:",omitempty"`
SecondaryScratchpad string `json:",omitempty"`
ClearSecondaryScratchpad bool `json:",omitempty"`
TransferComms bool `json:",omitempty"`
ClimbAltitude *int `json:",omitempty"` // altitude in feet to climb to when passing waypoint
DescendAltitude *int `json:",omitempty"` // altitude in feet to descend to when passing waypoint
}

func (wp Waypoint) LogValue() slog.Value {
Expand Down
2 changes: 1 addition & 1 deletion eram/flightplan.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,7 @@ func checkScratchpad(ctx *panes.Context, contents string, isSecondary, isImplied
// match one of the TCPs
if lc == 2 {
for _, ctrl := range ctx.Client.State.Controllers {
if ctrl.FacilityIdentifier == "" && ctrl.SectorID == contents {
if ctrl.FacilityIdentifier == "" && ctrl.Position == contents {
return ErrCommandFormat
}
}
Expand Down
Loading
Loading