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 } }