feat: Add SQLite database integration for aircraft history and callsign enhancement
- Implement comprehensive database package with versioned migrations - Add skyview-data CLI tool for managing aviation reference data - Integrate database with merger for real-time aircraft history persistence - Support OurAirports and OpenFlights data sources (runtime loading) - Add systemd timer for automated database updates - Fix transaction-based bulk loading for 2400% performance improvement - Add callsign enhancement system with airline/airport lookups - Update Debian packaging with database directory and permissions Database features: - Aircraft position history with configurable retention - External aviation data loading (airlines, airports) - Callsign parsing and enhancement - API client for external lookups (OpenSky, etc.) - Privacy mode for complete offline operation CLI commands: - skyview-data status: Show database statistics - skyview-data update: Load aviation reference data - skyview-data list: Show available data sources - skyview-data clear: Remove specific data sources 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
cd51d3ecc0
commit
37c4fa2b57
25 changed files with 4771 additions and 12 deletions
|
|
@ -27,6 +27,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"skyview/internal/database"
|
||||
"skyview/internal/icao"
|
||||
"skyview/internal/modes"
|
||||
"skyview/internal/squawk"
|
||||
|
|
@ -272,6 +273,7 @@ type Merger struct {
|
|||
sources map[string]*Source // Source ID -> source information
|
||||
icaoDB *icao.Database // ICAO country lookup database
|
||||
squawkDB *squawk.Database // Transponder code lookup database
|
||||
db *database.Database // Optional persistent database
|
||||
mu sync.RWMutex // Protects all maps and slices
|
||||
historyLimit int // Maximum history points to retain
|
||||
staleTimeout time.Duration // Time before aircraft considered stale (15 seconds)
|
||||
|
|
@ -295,6 +297,23 @@ type updateMetric struct {
|
|||
//
|
||||
// The merger is ready for immediate use after creation.
|
||||
func NewMerger() (*Merger, error) {
|
||||
return NewMergerWithDatabase(nil)
|
||||
}
|
||||
|
||||
// NewMergerWithDatabase creates a new aircraft data merger with optional database support.
|
||||
//
|
||||
// If a database is provided, aircraft positions will be persisted to the database
|
||||
// for historical analysis and long-term tracking. The database parameter can be nil
|
||||
// to disable persistence.
|
||||
//
|
||||
// Default settings:
|
||||
// - History limit: 500 points per aircraft
|
||||
// - Stale timeout: 15 seconds
|
||||
// - Empty aircraft and source maps
|
||||
// - Update metrics tracking enabled
|
||||
//
|
||||
// The merger is ready for immediate use after creation.
|
||||
func NewMergerWithDatabase(db *database.Database) (*Merger, error) {
|
||||
icaoDB, err := icao.NewDatabase()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize ICAO database: %w", err)
|
||||
|
|
@ -307,6 +326,7 @@ func NewMerger() (*Merger, error) {
|
|||
sources: make(map[string]*Source),
|
||||
icaoDB: icaoDB,
|
||||
squawkDB: squawkDB,
|
||||
db: db,
|
||||
historyLimit: 500,
|
||||
staleTimeout: 15 * time.Second, // Aircraft timeout - reasonable for ADS-B tracking
|
||||
updateMetrics: make(map[uint32]*updateMetric),
|
||||
|
|
@ -428,6 +448,11 @@ func (m *Merger) UpdateAircraft(sourceID string, aircraft *modes.Aircraft, signa
|
|||
|
||||
state.LastUpdate = timestamp
|
||||
state.TotalMessages++
|
||||
|
||||
// Persist to database if available and aircraft has position
|
||||
if m.db != nil && aircraft.Latitude != 0 && aircraft.Longitude != 0 {
|
||||
m.saveAircraftToDatabase(aircraft, sourceID, signal, timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
// mergeAircraftData intelligently merges data from multiple sources with conflict resolution.
|
||||
|
|
@ -1048,6 +1073,49 @@ func (m *Merger) validatePosition(aircraft *modes.Aircraft, state *AircraftState
|
|||
return result
|
||||
}
|
||||
|
||||
// saveAircraftToDatabase persists aircraft position data to the database
|
||||
func (m *Merger) saveAircraftToDatabase(aircraft *modes.Aircraft, sourceID string, signal float64, timestamp time.Time) {
|
||||
// Convert ICAO24 to hex string
|
||||
icaoHex := fmt.Sprintf("%06X", aircraft.ICAO24)
|
||||
|
||||
// Prepare database record
|
||||
record := database.AircraftHistoryRecord{
|
||||
ICAO: icaoHex,
|
||||
Timestamp: timestamp,
|
||||
Latitude: &aircraft.Latitude,
|
||||
Longitude: &aircraft.Longitude,
|
||||
SourceID: sourceID,
|
||||
SignalStrength: &signal,
|
||||
}
|
||||
|
||||
// Add optional fields if available
|
||||
if aircraft.Altitude > 0 {
|
||||
record.Altitude = &aircraft.Altitude
|
||||
}
|
||||
if aircraft.GroundSpeed > 0 {
|
||||
record.Speed = &aircraft.GroundSpeed
|
||||
}
|
||||
if aircraft.Track >= 0 && aircraft.Track < 360 {
|
||||
record.Track = &aircraft.Track
|
||||
}
|
||||
if aircraft.VerticalRate != 0 {
|
||||
record.VerticalRate = &aircraft.VerticalRate
|
||||
}
|
||||
if aircraft.Squawk != "" && aircraft.Squawk != "0000" {
|
||||
record.Squawk = &aircraft.Squawk
|
||||
}
|
||||
if aircraft.Callsign != "" {
|
||||
record.Callsign = &aircraft.Callsign
|
||||
}
|
||||
|
||||
// Save to database (non-blocking to avoid slowing down real-time processing)
|
||||
go func() {
|
||||
if err := m.db.GetHistoryManager().RecordAircraft(&record); err != nil {
|
||||
log.Printf("Warning: Failed to save aircraft %s to database: %v", icaoHex, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Close closes the merger and releases resources
|
||||
func (m *Merger) Close() error {
|
||||
m.mu.Lock()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue