Fix issue #21 and add aircraft position tracking indicators

- Fix Debian package upgrade issue by separating upgrade vs remove behavior in prerm script
- Add aircraft position tracking statistics in merger GetStatistics() method
- Update frontend to display position tracking indicators in both header and stats view
- Format Go code to maintain consistency

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2025-08-31 11:25:42 +02:00
commit 66a995b4d0
8 changed files with 124 additions and 74 deletions

View file

@ -53,6 +53,7 @@
<!-- Summary stats --> <!-- Summary stats -->
<div class="stats-summary"> <div class="stats-summary">
<span id="aircraft-count">0 aircraft</span> <span id="aircraft-count">0 aircraft</span>
<span id="position-summary">0 positioned</span>
<span id="sources-count">0 sources</span> <span id="sources-count">0 sources</span>
<span id="active-clients">1 viewer</span> <span id="active-clients">1 viewer</span>
<span id="connection-status" class="connection-status disconnected">Connecting...</span> <span id="connection-status" class="connection-status disconnected">Connecting...</span>
@ -208,6 +209,14 @@
<h3>Max Range</h3> <h3>Max Range</h3>
<div class="stat-value" id="max-range">0 km</div> <div class="stat-value" id="max-range">0 km</div>
</div> </div>
<div class="stat-card">
<h3>Aircraft with Position</h3>
<div class="stat-value" id="aircraft-with-position">0</div>
</div>
<div class="stat-card">
<h3>Aircraft without Position</h3>
<div class="stat-value" id="aircraft-without-position">0</div>
</div>
</div> </div>
<!-- Charts --> <!-- Charts -->

View file

@ -244,6 +244,8 @@ export class UIManager {
const activeViewersEl = document.getElementById('active-viewers'); const activeViewersEl = document.getElementById('active-viewers');
const maxRangeEl = document.getElementById('max-range'); const maxRangeEl = document.getElementById('max-range');
const messagesSecEl = document.getElementById('messages-sec'); const messagesSecEl = document.getElementById('messages-sec');
const aircraftWithPositionEl = document.getElementById('aircraft-with-position');
const aircraftWithoutPositionEl = document.getElementById('aircraft-without-position');
if (totalAircraftEl) totalAircraftEl.textContent = this.aircraftData.size; if (totalAircraftEl) totalAircraftEl.textContent = this.aircraftData.size;
if (activeSourcesEl) { if (activeSourcesEl) {
@ -253,6 +255,14 @@ export class UIManager {
activeViewersEl.textContent = this.stats.active_clients || 1; activeViewersEl.textContent = this.stats.active_clients || 1;
} }
// Update position tracking statistics from backend
if (aircraftWithPositionEl) {
aircraftWithPositionEl.textContent = this.stats.aircraft_with_position || 0;
}
if (aircraftWithoutPositionEl) {
aircraftWithoutPositionEl.textContent = this.stats.aircraft_without_position || 0;
}
// Calculate max range // Calculate max range
let maxDistance = 0; let maxDistance = 0;
for (const aircraft of this.aircraftData.values()) { for (const aircraft of this.aircraftData.values()) {
@ -270,10 +280,18 @@ export class UIManager {
updateHeaderInfo() { updateHeaderInfo() {
const aircraftCountEl = document.getElementById('aircraft-count'); const aircraftCountEl = document.getElementById('aircraft-count');
const positionSummaryEl = document.getElementById('position-summary');
const sourcesCountEl = document.getElementById('sources-count'); const sourcesCountEl = document.getElementById('sources-count');
const activeClientsEl = document.getElementById('active-clients'); const activeClientsEl = document.getElementById('active-clients');
if (aircraftCountEl) aircraftCountEl.textContent = `${this.aircraftData.size} aircraft`; if (aircraftCountEl) aircraftCountEl.textContent = `${this.aircraftData.size} aircraft`;
// Update position summary in header
if (positionSummaryEl) {
const positioned = this.stats.aircraft_with_position || 0;
positionSummaryEl.textContent = `${positioned} positioned`;
}
if (sourcesCountEl) sourcesCountEl.textContent = `${this.sourcesData.size} sources`; if (sourcesCountEl) sourcesCountEl.textContent = `${this.sourcesData.size} sources`;
// Update active clients count // Update active clients count

11
debian/DEBIAN/prerm vendored
View file

@ -2,8 +2,8 @@
set -e set -e
case "$1" in case "$1" in
remove|upgrade|deconfigure) remove|deconfigure)
# Stop and disable the service # Stop and disable the service on removal
if systemctl is-active --quiet skyview-adsb.service; then if systemctl is-active --quiet skyview-adsb.service; then
systemctl stop skyview-adsb.service systemctl stop skyview-adsb.service
fi fi
@ -12,6 +12,13 @@ case "$1" in
systemctl disable skyview-adsb.service systemctl disable skyview-adsb.service
fi fi
;; ;;
upgrade)
# Only stop service during upgrade, preserve enabled state
if systemctl is-active --quiet skyview-adsb.service; then
systemctl stop skyview-adsb.service
fi
# Don't disable - postinst will restart if service was enabled
;;
esac esac
exit 0 exit 0

View file

@ -795,10 +795,13 @@ func (m *Merger) GetSources() []*Source {
// //
// The statistics include: // The statistics include:
// - total_aircraft: Current number of tracked aircraft // - total_aircraft: Current number of tracked aircraft
// - aircraft_with_position: Number of aircraft with valid position data
// - aircraft_without_position: Number of aircraft without position data
// - total_messages: Sum of all messages processed // - total_messages: Sum of all messages processed
// - active_sources: Number of currently connected sources // - active_sources: Number of currently connected sources
// - aircraft_by_sources: Distribution of aircraft by number of tracking sources // - aircraft_by_sources: Distribution of aircraft by number of tracking sources
// //
// The position statistics help assess data quality and tracking effectiveness.
// The aircraft_by_sources map shows data quality - aircraft tracked by // The aircraft_by_sources map shows data quality - aircraft tracked by
// multiple sources generally have better position accuracy and reliability. // multiple sources generally have better position accuracy and reliability.
// //
@ -810,11 +813,22 @@ func (m *Merger) GetStatistics() map[string]interface{} {
totalMessages := int64(0) totalMessages := int64(0)
activeSources := 0 activeSources := 0
aircraftBySources := make(map[int]int) // Count by number of sources aircraftBySources := make(map[int]int) // Count by number of sources
aircraftWithPosition := 0
aircraftWithoutPosition := 0
for _, state := range m.aircraft { for _, state := range m.aircraft {
totalMessages += state.TotalMessages totalMessages += state.TotalMessages
numSources := len(state.Sources) numSources := len(state.Sources)
aircraftBySources[numSources]++ aircraftBySources[numSources]++
// Check if aircraft has valid position data
if state.Aircraft.PositionValid &&
state.Aircraft.Latitude != 0.0 &&
state.Aircraft.Longitude != 0.0 {
aircraftWithPosition++
} else {
aircraftWithoutPosition++
}
} }
for _, src := range m.sources { for _, src := range m.sources {
@ -824,10 +838,12 @@ func (m *Merger) GetStatistics() map[string]interface{} {
} }
return map[string]interface{}{ return map[string]interface{}{
"total_aircraft": len(m.aircraft), "total_aircraft": len(m.aircraft),
"total_messages": totalMessages, "aircraft_with_position": aircraftWithPosition,
"active_sources": activeSources, "aircraft_without_position": aircraftWithoutPosition,
"aircraft_by_sources": aircraftBySources, "total_messages": totalMessages,
"active_sources": activeSources,
"aircraft_by_sources": aircraftBySources,
} }
} }

View file

@ -157,9 +157,9 @@ type Aircraft struct {
BaroSetting float64 // Barometric pressure setting (QNH) in millibars BaroSetting float64 // Barometric pressure setting (QNH) in millibars
// Additional fields from VRS JSON and extended sources // Additional fields from VRS JSON and extended sources
Registration string // Aircraft registration (e.g., "N12345") Registration string // Aircraft registration (e.g., "N12345")
AircraftType string // Aircraft type (e.g., "B738") AircraftType string // Aircraft type (e.g., "B738")
Operator string // Airline or operator name Operator string // Airline or operator name
// Validity flags for optional fields (used by VRS and other sources) // Validity flags for optional fields (used by VRS and other sources)
CallsignValid bool // Whether callsign is valid CallsignValid bool // Whether callsign is valid

View file

@ -34,29 +34,29 @@ type VRSMessage struct {
// VRSAircraft represents a single aircraft in VRS JSON format // VRSAircraft represents a single aircraft in VRS JSON format
type VRSAircraft struct { type VRSAircraft struct {
Icao string `json:"Icao"` // ICAO hex address (may have ~ prefix for non-ICAO) Icao string `json:"Icao"` // ICAO hex address (may have ~ prefix for non-ICAO)
Lat float64 `json:"Lat"` // Latitude Lat float64 `json:"Lat"` // Latitude
Long float64 `json:"Long"` // Longitude Long float64 `json:"Long"` // Longitude
Alt int `json:"Alt"` // Barometric altitude in feet Alt int `json:"Alt"` // Barometric altitude in feet
GAlt int `json:"GAlt"` // Geometric altitude in feet GAlt int `json:"GAlt"` // Geometric altitude in feet
Spd float64 `json:"Spd"` // Speed in knots Spd float64 `json:"Spd"` // Speed in knots
Trak float64 `json:"Trak"` // Track/heading in degrees Trak float64 `json:"Trak"` // Track/heading in degrees
Vsi int `json:"Vsi"` // Vertical speed in feet/min Vsi int `json:"Vsi"` // Vertical speed in feet/min
Sqk string `json:"Sqk"` // Squawk code Sqk string `json:"Sqk"` // Squawk code
Call string `json:"Call"` // Callsign Call string `json:"Call"` // Callsign
Gnd bool `json:"Gnd"` // On ground flag Gnd bool `json:"Gnd"` // On ground flag
TAlt int `json:"TAlt"` // Target altitude TAlt int `json:"TAlt"` // Target altitude
Mlat bool `json:"Mlat"` // MLAT position flag Mlat bool `json:"Mlat"` // MLAT position flag
Tisb bool `json:"Tisb"` // TIS-B flag Tisb bool `json:"Tisb"` // TIS-B flag
Sat bool `json:"Sat"` // Satellite (JAERO) position flag Sat bool `json:"Sat"` // Satellite (JAERO) position flag
// Additional fields that may be present // Additional fields that may be present
Reg string `json:"Reg"` // Registration Reg string `json:"Reg"` // Registration
Type string `json:"Type"` // Aircraft type Type string `json:"Type"` // Aircraft type
Mdl string `json:"Mdl"` // Model Mdl string `json:"Mdl"` // Model
Op string `json:"Op"` // Operator Op string `json:"Op"` // Operator
From string `json:"From"` // Departure airport From string `json:"From"` // Departure airport
To string `json:"To"` // Destination airport To string `json:"To"` // Destination airport
// Timing fields // Timing fields
PosTime int64 `json:"PosTime"` // Position timestamp (milliseconds) PosTime int64 `json:"PosTime"` // Position timestamp (milliseconds)