diff --git a/internal/client/dump1090.go b/internal/client/dump1090.go index 99ca30d..1c1510f 100644 --- a/internal/client/dump1090.go +++ b/internal/client/dump1090.go @@ -106,11 +106,28 @@ func (c *Dump1090Client) updateExistingAircraft(existing, update *parser.Aircraf if update.Track != 0 { existing.Track = update.Track } - if update.Latitude != 0 { + if update.Latitude != 0 && update.Longitude != 0 { existing.Latitude = update.Latitude - } - if update.Longitude != 0 { existing.Longitude = update.Longitude + + // Add to track history if position changed significantly + if c.shouldAddTrackPoint(existing, update) { + trackPoint := parser.TrackPoint{ + Timestamp: update.LastSeen, + Latitude: update.Latitude, + Longitude: update.Longitude, + Altitude: update.Altitude, + Speed: update.GroundSpeed, + Track: update.Track, + } + + existing.TrackHistory = append(existing.TrackHistory, trackPoint) + + // Keep only last 200 points (about 3-4 hours at 1 point/minute) + if len(existing.TrackHistory) > 200 { + existing.TrackHistory = existing.TrackHistory[1:] + } + } } if update.VertRate != 0 { existing.VertRate = update.VertRate @@ -121,6 +138,28 @@ func (c *Dump1090Client) updateExistingAircraft(existing, update *parser.Aircraf existing.OnGround = update.OnGround } +func (c *Dump1090Client) shouldAddTrackPoint(existing, update *parser.Aircraft) bool { + // Add track point if: + // 1. No history yet + if len(existing.TrackHistory) == 0 { + return true + } + + lastPoint := existing.TrackHistory[len(existing.TrackHistory)-1] + + // 2. At least 30 seconds since last point + if time.Since(lastPoint.Timestamp) < 30*time.Second { + return false + } + + // 3. Position changed by at least 0.001 degrees (~100m) + latDiff := existing.Latitude - lastPoint.Latitude + lonDiff := existing.Longitude - lastPoint.Longitude + distanceChange := latDiff*latDiff + lonDiff*lonDiff + + return distanceChange > 0.000001 // ~0.001 degrees squared +} + func (c *Dump1090Client) GetAircraftData() parser.AircraftData { c.mutex.RLock() defer c.mutex.RUnlock() @@ -201,9 +240,20 @@ func (c *Dump1090Client) cleanupStaleAircraft() { defer c.mutex.Unlock() cutoff := time.Now().Add(-2 * time.Minute) + trackCutoff := time.Now().Add(-24 * time.Hour) + for hex, aircraft := range c.aircraftMap { if aircraft.LastSeen.Before(cutoff) { delete(c.aircraftMap, hex) + } else { + // Clean up old track points (keep last 24 hours) + validTracks := make([]parser.TrackPoint, 0) + for _, point := range aircraft.TrackHistory { + if point.Timestamp.After(trackCutoff) { + validTracks = append(validTracks, point) + } + } + aircraft.TrackHistory = validTracks } } } \ No newline at end of file diff --git a/internal/parser/sbs1.go b/internal/parser/sbs1.go index 92d76d4..7bb66ee 100644 --- a/internal/parser/sbs1.go +++ b/internal/parser/sbs1.go @@ -6,20 +6,30 @@ import ( "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"` + 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"` } type AircraftData struct { diff --git a/internal/server/server.go b/internal/server/server.go index 6e311df..0ba4f4c 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -61,6 +61,7 @@ func New(cfg *config.Config, staticFiles embed.FS, ctx context.Context) http.Han apiRouter := router.PathPrefix("/api").Subrouter() apiRouter.HandleFunc("/aircraft", s.getAircraft).Methods("GET") + apiRouter.HandleFunc("/aircraft/{hex}/history", s.getAircraftHistory).Methods("GET") apiRouter.HandleFunc("/stats", s.getStats).Methods("GET") apiRouter.HandleFunc("/config", s.getConfig).Methods("GET") @@ -122,6 +123,27 @@ func (s *Server) getStats(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(stats) } +func (s *Server) getAircraftHistory(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + hex := vars["hex"] + + data := s.dump1090.GetAircraftData() + aircraft, exists := data.Aircraft[hex] + if !exists { + http.Error(w, "Aircraft not found", http.StatusNotFound) + return + } + + response := map[string]interface{}{ + "hex": aircraft.Hex, + "flight": aircraft.Flight, + "track_history": aircraft.TrackHistory, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} + func (s *Server) getConfig(w http.ResponseWriter, r *http.Request) { configData := map[string]interface{}{ "origin": map[string]interface{}{ diff --git a/static/css/style.css b/static/css/style.css index cba00a8..c4e9c1a 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -213,6 +213,20 @@ body { background: #404040; } +.type-badge { + padding: 0.2rem 0.4rem; + border-radius: 3px; + font-size: 0.7rem; + font-weight: bold; + color: #000000; +} + +.type-badge.commercial { background: #00ff88; } +.type-badge.cargo { background: #ff8c00; } +.type-badge.military { background: #ff4444; } +.type-badge.ga { background: #ffff00; } +.type-badge.ground { background: #888888; color: #ffffff; } + .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); diff --git a/static/index.html b/static/index.html index b5cac57..1298550 100644 --- a/static/index.html +++ b/static/index.html @@ -30,6 +30,7 @@