/// <summary> /// Performs a local decode of the tracked aircraft's position against the previous position of the aircraft. /// </summary> /// <param name="trackedAircraft"></param> /// <returns></returns> private GlobalCoordinate LocalCprDecode(TrackedAircraft trackedAircraft) { var cpr = trackedAircraft.LaterCpr.Cpr; var result = _CompactPositionReporting.LocalDecode(cpr, trackedAircraft.LastPosition); if(result != null) { var distance = GreatCircleMaths.Distance(result.Latitude, result.Longitude, trackedAircraft.LastPosition.Latitude, trackedAircraft.LastPosition.Longitude); var countPeriods = (int)Math.Max(1, Math.Ceiling((trackedAircraft.LaterCpr.Time - trackedAircraft.LastPositionTime).TotalSeconds / LocalDecodeMaxSpeedSeconds)); bool earlierIsSurface = trackedAircraft.LastPositionIsSurface; bool laterIsSurface = cpr.NumberOfBits == 19; double allowableDistance = 0.0; double distancePerPeriod = laterIsSurface ? LocalDecodeMaxSpeedSurface : LocalDecodeMaxSpeedAirborne; if(earlierIsSurface != laterIsSurface) { allowableDistance = LocalDecodeMaxSpeedTransition; distancePerPeriod = LocalDecodeMaxSpeedAirborne; --countPeriods; } allowableDistance += countPeriods * distancePerPeriod; if(distance >= allowableDistance) { result = null; if(_Statistics.Lock != null) { lock(_Statistics.Lock) ++_Statistics.AdsbPositionsExceededSpeedCheck; } } } return result; }
/// <summary> /// Resets the position tracking state of the aircraft. /// </summary> /// <param name="trackedAircraft"></param> /// <returns></returns> private GlobalCoordinate ResetPositionTracking(TrackedAircraft trackedAircraft) { trackedAircraft.LastPosition = null; trackedAircraft.EarlierCpr = trackedAircraft.LaterCpr = trackedAircraft.AcquisitionCpr = null; trackedAircraft.InitialLocalDecodingUnsafe = true; trackedAircraft.State = TrackedAircraftState.Acquiring; if(_Statistics.Lock != null) { lock(_Statistics.Lock) ++_Statistics.AdsbPositionsReset; } OnPositionReset(new EventArgs<string>(trackedAircraft.Icao24)); return null; }
/// <summary> /// Decodes the vehicle's position. /// </summary> /// <param name="result"></param> /// <param name="trackedAircraft"></param> /// <param name="groundSpeed"></param> private void DecodePosition(BaseStationMessage result, TrackedAircraft trackedAircraft, double? groundSpeed) { GlobalCoordinate position = null; TrackedAircraftState newState = trackedAircraft.State; if(UseLocalDecodeForInitialPosition && trackedAircraft.EarlierCpr == null && trackedAircraft.LaterCpr != null && ReceiverLocation != null && !trackedAircraft.InitialLocalDecodingUnsafe) { position = _CompactPositionReporting.LocalDecode(trackedAircraft.LaterCpr.Cpr, ReceiverLocation); newState = TrackedAircraftState.Acquired; } else if(trackedAircraft.LaterCpr != null && trackedAircraft.EarlierCpr != null) { switch(trackedAircraft.State) { case TrackedAircraftState.Acquiring: position = GlobalCprDecode(trackedAircraft, groundSpeed); newState = TrackedAircraftState.Acquired; break; case TrackedAircraftState.Acquired: position = LocalCprDecode(trackedAircraft); if(position != null && trackedAircraft.AcquisitionCpr != trackedAircraft.EarlierCpr) { var globalPosition = GlobalCprDecode(trackedAircraft, groundSpeed); if(globalPosition != null) { var distance = GreatCircleMaths.Distance(position.Latitude, position.Longitude, globalPosition.Latitude, globalPosition.Longitude); var isValid = true; switch(trackedAircraft.LaterCpr.Cpr.NumberOfBits) { case 19: isValid = distance <= SurfaceResolution; break; default: isValid = distance <= AirborneResolution; break; } if(!isValid) position = ResetPositionTracking(trackedAircraft); else newState = TrackedAircraftState.Tracking; } } break; case TrackedAircraftState.Tracking: position = LocalCprDecode(trackedAircraft); break; default: throw new NotImplementedException(); } } if(!SuppressReceiverRangeCheck && position != null && ReceiverLocation != null) { var distance = GreatCircleMaths.Distance(ReceiverLocation.Latitude, ReceiverLocation.Longitude, position.Latitude, position.Longitude); if(distance > ReceiverRangeKilometres) { position = null; if(_Statistics.Lock != null) { lock(_Statistics.Lock) ++_Statistics.AdsbPositionsOutsideRange; } } } if(position != null) { result.Latitude = position.Latitude; result.Longitude = position.Longitude; trackedAircraft.LastPosition = position; trackedAircraft.LastPositionTime = trackedAircraft.LaterCpr.Time; trackedAircraft.LastPositionIsSurface = trackedAircraft.LaterCpr.Cpr.NumberOfBits == 19; if(newState == TrackedAircraftState.Acquired && trackedAircraft.State != newState) trackedAircraft.AcquisitionCpr = trackedAircraft.LaterCpr; trackedAircraft.State = newState; } }
/// <summary> /// Performs a global decode of the tracked aircraft's position when the last two messages are odd and even. /// </summary> /// <param name="trackedAircraft"></param> /// <param name="groundSpeed"></param> /// <returns></returns> private GlobalCoordinate GlobalCprDecode(TrackedAircraft trackedAircraft, double? groundSpeed) { GlobalCoordinate result = null; var threshold = trackedAircraft.LaterCpr.Cpr.NumberOfBits == 19 ? groundSpeed == null || groundSpeed > 25.0 ? GlobalDecodeFastSurfaceThresholdMilliseconds : GlobalDecodeSlowSurfaceThresholdMilliseconds : GlobalDecodeAirborneThresholdMilliseconds; if((trackedAircraft.LaterCpr.Time - trackedAircraft.EarlierCpr.Time).TotalMilliseconds <= threshold) { result = _CompactPositionReporting.GlobalDecode(trackedAircraft.EarlierCpr.Cpr, trackedAircraft.LaterCpr.Cpr, ReceiverLocation); } return result; }
/// <summary> /// Applies values from the Mode-S message to the BaseStation message being formed. /// </summary> /// <param name="modeSMessage"></param> /// <param name="trackedAircraft"></param> /// <param name="baseStationMessage"></param> private void ApplyModeSValues(ModeSMessage modeSMessage, TrackedAircraft trackedAircraft, BaseStationMessage baseStationMessage) { if(modeSMessage.FlightStatus != null) { var flightStatus = modeSMessage.FlightStatus.Value; baseStationMessage.SquawkHasChanged = flightStatus == FlightStatus.AirborneAlert || flightStatus == FlightStatus.OnGroundAlert || flightStatus == FlightStatus.SpiWithAlert; baseStationMessage.IdentActive = flightStatus == FlightStatus.SpiWithAlert || flightStatus == FlightStatus.SpiWithNoAlert; switch(flightStatus) { case FlightStatus.Airborne: case FlightStatus.AirborneAlert: baseStationMessage.OnGround = false; break; case FlightStatus.OnGround: case FlightStatus.OnGroundAlert: baseStationMessage.OnGround = true; break; } } if(modeSMessage.Identity != null) baseStationMessage.Emergency = modeSMessage.Identity == 7500 || modeSMessage.Identity == 7600 || modeSMessage.Identity == 7700; if(modeSMessage.Capability != null) { if(modeSMessage.Capability == Capability.HasCommACommBAndAirborne) baseStationMessage.OnGround = false; else if(modeSMessage.Capability == Capability.HasCommACommBAndOnGround) baseStationMessage.OnGround = true; } if(modeSMessage.VerticalStatus != null) baseStationMessage.OnGround = modeSMessage.VerticalStatus == VerticalStatus.OnGround; if(!String.IsNullOrEmpty(modeSMessage.PossibleCallsign) && !SuppressCallsignsFromBds20 && !trackedAircraft.SeenAdsbCallsign) { baseStationMessage.Callsign = modeSMessage.PossibleCallsign.Trim(); if(baseStationMessage.Supplementary == null) baseStationMessage.Supplementary = new BaseStationSupplementaryMessage(); baseStationMessage.Supplementary.CallsignIsSuspect = true; } }
/// <summary> /// Creates a BaseStationMessage from the Mode-S and ADS-B message passed across. /// </summary> /// <param name="messageReceivedUtc"></param> /// <param name="modeSMessage"></param> /// <param name="adsbMessage"></param> /// <param name="trackedAircraft"></param> /// <returns></returns> private BaseStationMessage CreateBaseStationMessage(DateTime messageReceivedUtc, ModeSMessage modeSMessage, AdsbMessage adsbMessage, TrackedAircraft trackedAircraft) { var result = new BaseStationMessage() { MessageType = BaseStationMessageType.Transmission, TransmissionType = ConvertToTransmissionType(modeSMessage, adsbMessage), MessageGenerated = messageReceivedUtc, MessageLogged = messageReceivedUtc, Icao24 = modeSMessage.FormattedIcao24, Altitude = modeSMessage.Altitude, Squawk = modeSMessage.Identity, }; ApplyModeSValues(modeSMessage, trackedAircraft, result); if(adsbMessage != null) ApplyAdsbValues(messageReceivedUtc, adsbMessage, trackedAircraft, result); result.GroundSpeed = Round.GroundSpeed(result.GroundSpeed); result.Track = Round.Track(result.Track); result.Latitude = Round.Coordinate(result.Latitude); result.Longitude = Round.Coordinate(result.Longitude); return result; }
/// <summary> /// Applies values from the ADS-B message to the BaseStation message being formed. /// </summary> /// <param name="messageReceivedUtc"></param> /// <param name="adsbMessage"></param> /// <param name="trackedAircraft"></param> /// <param name="baseStationMessage"></param> private void ApplyAdsbValues(DateTime messageReceivedUtc, AdsbMessage adsbMessage, TrackedAircraft trackedAircraft, BaseStationMessage baseStationMessage) { if(adsbMessage.AirbornePosition != null) ApplyAdsbAirbornePosition(messageReceivedUtc, adsbMessage, trackedAircraft, baseStationMessage); if(adsbMessage.AirborneVelocity != null) ApplyAdsbAirborneVelocity(adsbMessage, baseStationMessage); if(adsbMessage.AircraftStatus != null) ApplyAdsbAircraftStatus(adsbMessage, baseStationMessage); if(adsbMessage.IdentifierAndCategory != null) ApplyAdsbIdentifierAndCategory(adsbMessage, trackedAircraft, baseStationMessage); if(adsbMessage.SurfacePosition != null) ApplyAdsbSurfacePosition(messageReceivedUtc, adsbMessage, trackedAircraft, baseStationMessage); if(adsbMessage.TargetStateAndStatus != null) ApplyAdsbTargetStateAndStatus(adsbMessage, baseStationMessage); }
private void ApplyAdsbSurfacePosition(DateTime messageReceivedUtc, AdsbMessage adsbMessage, TrackedAircraft trackedAircraft, BaseStationMessage baseStationMessage) { baseStationMessage.OnGround = true; baseStationMessage.GroundSpeed = (float?)adsbMessage.SurfacePosition.GroundSpeed; baseStationMessage.Track = (float?)adsbMessage.SurfacePosition.GroundTrack; if(adsbMessage.SurfacePosition.IsReversing) { if(baseStationMessage.Supplementary == null) baseStationMessage.Supplementary = new BaseStationSupplementaryMessage(); baseStationMessage.Supplementary.SpeedType = SpeedType.GroundSpeedReversing; } if(adsbMessage.SurfacePosition.CompactPosition != null) { trackedAircraft.RecordMessage(messageReceivedUtc, adsbMessage.SurfacePosition.CompactPosition); DecodePosition(baseStationMessage, trackedAircraft, adsbMessage.SurfacePosition.GroundSpeed); } }
private void ApplyAdsbIdentifierAndCategory(AdsbMessage adsbMessage, TrackedAircraft trackedAircraft, BaseStationMessage baseStationMessage) { if(adsbMessage.IdentifierAndCategory.Identification != null) { trackedAircraft.SeenAdsbCallsign = true; baseStationMessage.Callsign = adsbMessage.IdentifierAndCategory.Identification.Trim(); if(baseStationMessage.Supplementary == null) baseStationMessage.Supplementary = new BaseStationSupplementaryMessage(); baseStationMessage.Supplementary.CallsignIsSuspect = false; } }
private void ApplyAdsbAirbornePosition(DateTime messageReceivedUtc, AdsbMessage adsbMessage, TrackedAircraft trackedAircraft, BaseStationMessage baseStationMessage) { if(adsbMessage.AirbornePosition.BarometricAltitude != null) baseStationMessage.Altitude = adsbMessage.AirbornePosition.BarometricAltitude; else if(adsbMessage.AirbornePosition.GeometricAltitude != null) { baseStationMessage.Altitude = adsbMessage.AirbornePosition.GeometricAltitude; if(baseStationMessage.Supplementary == null) baseStationMessage.Supplementary = new BaseStationSupplementaryMessage(); baseStationMessage.Supplementary.AltitudeIsGeometric = true; } if(adsbMessage.AirbornePosition.CompactPosition != null) { trackedAircraft.RecordMessage(messageReceivedUtc, adsbMessage.AirbornePosition.CompactPosition); DecodePosition(baseStationMessage, trackedAircraft, null); } baseStationMessage.SquawkHasChanged = adsbMessage.AirbornePosition.SurveillanceStatus == SurveillanceStatus.TemporaryAlert; baseStationMessage.IdentActive = adsbMessage.AirbornePosition.SurveillanceStatus == SurveillanceStatus.SpecialPositionIdentification; }
/// <summary> /// See interface docs. /// </summary> /// <param name="messageReceivedUtc"></param> /// <param name="modeSMessage"></param> /// <param name="adsbMessage"></param> /// <returns></returns> public BaseStationMessage Translate(DateTime messageReceivedUtc, ModeSMessage modeSMessage, AdsbMessage adsbMessage) { BaseStationMessage result = null; if(modeSMessage != null && !_Disposed) { var isValidMessage = DetermineWhetherValid(modeSMessage, messageReceivedUtc); if(isValidMessage) { TrackedAircraft trackedAircraft; lock(_TrackedAircraftLock) { if(!_TrackedAircraft.TryGetValue(modeSMessage.Icao24, out trackedAircraft)) { trackedAircraft = new TrackedAircraft() { Icao24 = modeSMessage.FormattedIcao24, LatestMessageUtc = messageReceivedUtc, }; _TrackedAircraft.Add(modeSMessage.Icao24, trackedAircraft); } else { if((messageReceivedUtc - trackedAircraft.LatestMessageUtc).TotalSeconds >= TrackingTimeoutSeconds) { trackedAircraft = new TrackedAircraft() { Icao24 = modeSMessage.FormattedIcao24 }; _TrackedAircraft[modeSMessage.Icao24] = trackedAircraft; } trackedAircraft.LatestMessageUtc = messageReceivedUtc; } } if(isValidMessage) { result = CreateBaseStationMessage(messageReceivedUtc, modeSMessage, adsbMessage, trackedAircraft); if(_Statistics.Lock != null) { lock(_Statistics.Lock) _Statistics.AdsbAircraftTracked = _TrackedAircraft.Count; } } } } return result; }