diff options
Diffstat (limited to 'LearnMapKit/Repositories')
| -rw-r--r-- | LearnMapKit/Repositories/AircraftRepository.swift | 265 |
1 files changed, 265 insertions, 0 deletions
diff --git a/LearnMapKit/Repositories/AircraftRepository.swift b/LearnMapKit/Repositories/AircraftRepository.swift new file mode 100644 index 0000000..6a6bc7e --- /dev/null +++ b/LearnMapKit/Repositories/AircraftRepository.swift @@ -0,0 +1,265 @@ +// +// AircraftRepository.swift +// LearnMapKit +// +// Aircraft data management and state tracking +// + +import Foundation +import Combine + +// MARK: - Aircraft Model +struct Aircraft: Identifiable, Equatable { + let id: Int // ICAO address + let icaoAddress: Int + let icaoName: String? + let position: Position? + let altitude: Int? + let lastSeen: Date + let flightInfo: FlightInfo? + + struct Position: Equatable { + let latitude: Double + let longitude: Double + let accuracy: PositionAccuracy + let timestamp: Date + + enum PositionAccuracy { + case unknown, low, medium, high + } + } + + struct FlightInfo { + let from: String? + let to: String? + let route: String? + } + + // Computed properties for UI + var isActive: Bool { + Date().timeIntervalSince(lastSeen) < 300 // 5 minutes + } + + var displayName: String { + icaoName ?? "Unknown (\(String(format: "%06X", icaoAddress)))" + } +} + +// MARK: - Aircraft Repository Protocol +protocol AircraftRepository: AnyObject { + var aircraftStream: AnyPublisher<[Aircraft], Never> { get } + var aircraftCount: AnyPublisher<Int, Never> { get } + + func getAllAircraft() -> [Aircraft] + func getAircraft(withId id: Int) -> Aircraft? + func updateAircraft(with event: ADSBDataEvent) + func removeInactiveAircraft() + func clearAll() +} + +// MARK: - Default Implementation +@MainActor +class DefaultAircraftRepository: AircraftRepository { + + // MARK: - Published Properties + var aircraftStream: AnyPublisher<[Aircraft], Never> { + aircraftSubject.eraseToAnyPublisher() + } + + var aircraftCount: AnyPublisher<Int, Never> { + aircraftSubject + .map { $0.count } + .removeDuplicates() + .eraseToAnyPublisher() + } + + // MARK: - Private Properties + private let aircraftSubject = CurrentValueSubject<[Aircraft], Never>([]) + private var aircraftCache: [Int: Aircraft] = [:] + + // Dependencies + private let tracker: AircraftTracker + + init(tracker: AircraftTracker = DefaultAircraftTracker()) { + self.tracker = tracker + startCleanupTimer() + } + + // MARK: - AircraftRepository Implementation + func getAllAircraft() -> [Aircraft] { + Array(aircraftCache.values) + .filter { $0.isActive } + .sorted { $0.lastSeen > $1.lastSeen } + } + + func getAircraft(withId id: Int) -> Aircraft? { + aircraftCache[id] + } + + func updateAircraft(with event: ADSBDataEvent) { + switch event { + case .aircraftIdentification(let address, let icaoName): + updateIdentification(address: address, icaoName: icaoName) + + case .aircraftPosition(let address, let latitude, let longitude): + updatePosition(address: address, latitude: latitude, longitude: longitude) + + case .aircraftAltitude(let address, let altitude): + updateAltitude(address: address, altitude: altitude) + + case .rawMessage, .error: + break // Handle these separately if needed + } + } + + func removeInactiveAircraft() { + let activeAircraft = aircraftCache.filter { _, aircraft in + aircraft.isActive + } + + if activeAircraft.count != aircraftCache.count { + aircraftCache = activeAircraft + publishUpdate() + } + } + + func clearAll() { + aircraftCache.removeAll() + publishUpdate() + } + + // MARK: - Private Update Methods + private func updateIdentification(address: Int, icaoName: String) { + let aircraft = getOrCreateAircraft(address: address) + let updated = Aircraft( + id: aircraft.id, + icaoAddress: aircraft.icaoAddress, + icaoName: icaoName, + position: aircraft.position, + altitude: aircraft.altitude, + lastSeen: Date(), + flightInfo: aircraft.flightInfo + ) + + aircraftCache[address] = updated + publishUpdate() + } + + private func updatePosition(address: Int, latitude: Double, longitude: Double) { + Task { + // Update the tracker for position calculation + await tracker.updatePosition(address: address, latitude: latitude, longitude: longitude) + + // Get calculated position from tracker + if let calculatedPosition = await tracker.getPosition(address: address) { + let aircraft = getOrCreateAircraft(address: address) + let position = Aircraft.Position( + latitude: calculatedPosition.0, + longitude: calculatedPosition.1, + accuracy: .medium, // TODO: Determine accuracy from tracker + timestamp: Date() + ) + + let updated = Aircraft( + id: aircraft.id, + icaoAddress: aircraft.icaoAddress, + icaoName: aircraft.icaoName, + position: position, + altitude: aircraft.altitude, + lastSeen: Date(), + flightInfo: aircraft.flightInfo + ) + + aircraftCache[address] = updated + publishUpdate() + } + } + } + + private func updateAltitude(address: Int, altitude: Int) { + let aircraft = getOrCreateAircraft(address: address) + let updated = Aircraft( + id: aircraft.id, + icaoAddress: aircraft.icaoAddress, + icaoName: aircraft.icaoName, + position: aircraft.position, + altitude: altitude, + lastSeen: Date(), + flightInfo: aircraft.flightInfo + ) + + aircraftCache[address] = updated + publishUpdate() + } + + private func getOrCreateAircraft(address: Int) -> Aircraft { + if let existing = aircraftCache[address] { + return existing + } + + let newAircraft = Aircraft( + id: address, + icaoAddress: address, + icaoName: nil, + position: nil, + altitude: nil, + lastSeen: Date(), + flightInfo: nil + ) + + return newAircraft + } + + private func publishUpdate() { + let currentAircraft = getAllAircraft() + aircraftSubject.send(currentAircraft) + } + + private func startCleanupTimer() { + Timer.scheduledTimer(withTimeInterval: 60.0, repeats: true) { [weak self] _ in + self?.removeInactiveAircraft() + } + } +} + +// MARK: - Aircraft Tracker Protocol +protocol AircraftTracker: Actor { + func updateIdentification(address: Int, icaoName: String) async + func updatePosition(address: Int, latitude: Double, longitude: Double) async + func updateAltitude(address: Int, altitude: Int) async + func getPosition(address: Int) async -> (Double, Double)? + func getAltitude(address: Int) async -> Int? + func getICAOName(address: Int) async -> String? +} + +// MARK: - Default Aircraft Tracker (Adapter for existing AirPlaneTracker) +actor DefaultAircraftTracker: AircraftTracker { + private let airplaneTracker = AirPlaneTracker() + + func updateIdentification(address: Int, icaoName: String) async { + airplaneTracker.addDF17Indentification(address, icaoName) + } + + func updatePosition(address: Int, latitude: Double, longitude: Double) async { + // Note: The original tracker expects CPR coordinates, not lat/long + // This is a simplified adapter - you might need to adjust based on your data flow + airplaneTracker.addDF17AirBornPosition(address, latitude, longitude, 0, true) + } + + func updateAltitude(address: Int, altitude: Int) async { + // Altitude is typically updated with position in the original tracker + // This might need adjustment based on your specific needs + } + + func getPosition(address: Int) async -> (Double, Double)? { + return airplaneTracker.getPosition(address) + } + + func getAltitude(address: Int) async -> Int? { + return airplaneTracker.getAltitude(address) + } + + func getICAOName(address: Int) async -> String? { + return airplaneTracker.getICAOname(address) + } +}
\ No newline at end of file |
