skyview/internal/parser/sbs1.go

224 lines
4.8 KiB
Go
Raw Normal View History

package parser
import (
"strconv"
"strings"
"time"
)
type TrackPoint struct {
Timestamp time.Time `json:"timestamp"`
Latitude float64 `json:"lat"`
Longitude float64 `json:"lon"`
Altitude int `json:"altitude"`
Speed int `json:"speed"`
Track int `json:"track"`
}
type Aircraft struct {
Hex string `json:"hex"`
Flight string `json:"flight,omitempty"`
Altitude int `json:"alt_baro,omitempty"`
GroundSpeed int `json:"gs,omitempty"`
Track int `json:"track,omitempty"`
Latitude float64 `json:"lat,omitempty"`
Longitude float64 `json:"lon,omitempty"`
VertRate int `json:"vert_rate,omitempty"`
Squawk string `json:"squawk,omitempty"`
Emergency bool `json:"emergency,omitempty"`
OnGround bool `json:"on_ground,omitempty"`
LastSeen time.Time `json:"last_seen"`
Messages int `json:"messages"`
TrackHistory []TrackPoint `json:"track_history,omitempty"`
RSSI float64 `json:"rssi,omitempty"`
Country string `json:"country,omitempty"`
Registration string `json:"registration,omitempty"`
}
type AircraftData struct {
Now int64 `json:"now"`
Messages int `json:"messages"`
Aircraft map[string]Aircraft `json:"aircraft"`
}
func ParseSBS1Line(line string) (*Aircraft, error) {
parts := strings.Split(strings.TrimSpace(line), ",")
if len(parts) < 22 {
return nil, nil
}
// messageType := parts[1]
// Accept all message types to get complete data
// MSG types: 1=ES_IDENT_AND_CATEGORY, 2=ES_SURFACE_POS, 3=ES_AIRBORNE_POS
// 4=ES_AIRBORNE_VEL, 5=SURVEILLANCE_ALT, 6=SURVEILLANCE_ID, 7=AIR_TO_AIR, 8=ALL_CALL_REPLY
aircraft := &Aircraft{
Hex: strings.TrimSpace(parts[4]),
LastSeen: time.Now(),
Messages: 1,
}
// Different message types contain different fields
// Always try to extract what's available
if parts[10] != "" {
aircraft.Flight = strings.TrimSpace(parts[10])
}
if parts[11] != "" {
if alt, err := strconv.Atoi(parts[11]); err == nil {
aircraft.Altitude = alt
}
}
if parts[12] != "" {
if gs, err := strconv.Atoi(parts[12]); err == nil {
aircraft.GroundSpeed = gs
}
}
if parts[13] != "" {
if track, err := strconv.ParseFloat(parts[13], 64); err == nil {
aircraft.Track = int(track)
}
}
if parts[14] != "" && parts[15] != "" {
if lat, err := strconv.ParseFloat(parts[14], 64); err == nil {
aircraft.Latitude = lat
}
if lon, err := strconv.ParseFloat(parts[15], 64); err == nil {
aircraft.Longitude = lon
}
}
if parts[16] != "" {
if vr, err := strconv.Atoi(parts[16]); err == nil {
aircraft.VertRate = vr
}
}
if parts[17] != "" {
aircraft.Squawk = strings.TrimSpace(parts[17])
}
if parts[21] != "" {
aircraft.OnGround = parts[21] == "1"
}
aircraft.Country = getCountryFromICAO(aircraft.Hex)
aircraft.Registration = getRegistrationFromICAO(aircraft.Hex)
return aircraft, nil
}
func getCountryFromICAO(icao string) string {
if len(icao) < 6 {
return "Unknown"
}
prefix := icao[:1]
switch prefix {
case "4":
return getCountryFrom4xxxx(icao)
case "A":
return "United States"
case "C":
return "Canada"
case "D":
return "Germany"
case "F":
return "France"
case "G":
return "United Kingdom"
case "I":
return "Italy"
case "J":
return "Japan"
case "P":
return getPCountry(icao)
case "S":
return getSCountry(icao)
case "O":
return getOCountry(icao)
default:
return "Unknown"
}
}
func getCountryFrom4xxxx(icao string) string {
if len(icao) >= 2 {
switch icao[:2] {
case "40":
return "United Kingdom"
case "44":
return "Austria"
case "45":
return "Denmark"
case "46":
return "Germany"
case "47":
return "Germany"
case "48":
return "Netherlands"
case "49":
return "Netherlands"
}
}
return "Europe"
}
func getPCountry(icao string) string {
if len(icao) >= 2 {
switch icao[:2] {
case "PH":
return "Netherlands"
case "PJ":
return "Netherlands Antilles"
}
}
return "Unknown"
}
func getSCountry(icao string) string {
if len(icao) >= 2 {
switch icao[:2] {
case "SE":
return "Sweden"
case "SX":
return "Greece"
}
}
return "Unknown"
}
func getOCountry(icao string) string {
if len(icao) >= 2 {
switch icao[:2] {
case "OO":
return "Belgium"
case "OH":
return "Finland"
}
}
return "Unknown"
}
func getRegistrationFromICAO(icao string) string {
// This is a simplified conversion - real registration lookup would need a database
country := getCountryFromICAO(icao)
switch country {
case "Germany":
return "D-" + icao[2:]
case "United Kingdom":
return "G-" + icao[2:]
case "France":
return "F-" + icao[2:]
case "Netherlands":
return "PH-" + icao[2:]
case "Sweden":
return "SE-" + icao[2:]
default:
return icao
}
}