From f9cf71ad4e74e7a130f800ed4bf09ff12c19c61d Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 8 Apr 2023 00:34:39 -0700 Subject: [PATCH] Tidy up some location things --- Meshtastic/Helpers/BLEManager.swift | 22 ++-- Meshtastic/Helpers/LocationHelper.swift | 120 ++++++++---------- .../Persistence/PositionEntityExtension.swift | 2 +- .../Persistence/WaypointEntityExtension.swift | 2 +- .../Helpers/Weather/NodeWeatherForecast.swift | 2 +- .../Views/Map/Custom/MapViewSwiftUI.swift | 6 +- Meshtastic/Views/Map/WaypointFormView.swift | 4 +- Meshtastic/Views/Nodes/NodeDetail.swift | 8 +- Meshtastic/Views/Nodes/NodeList.swift | 4 +- Meshtastic/Views/Nodes/NodeMap.swift | 4 +- Meshtastic/Views/Settings/AppSettings.swift | 28 +++- 11 files changed, 109 insertions(+), 93 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index c37d9757..e810febe 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -784,25 +784,25 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { if lastPosition != nil { let connectedNode = getNodeInfo(id: connectedPeripheral?.num ?? 0, context: context!) if connectedNode?.positionConfig?.smartPositionEnabled ?? false { - if lastPosition!.distance(from: LocationHelper.currentLocation) < Double(connectedNode?.positionConfig?.broadcastSmartMinimumDistance ?? 50) { + if lastPosition!.distance(from: LocationHelper.currentLocation.coordinate) < Double(connectedNode?.positionConfig?.broadcastSmartMinimumDistance ?? 50) { return false } } } } - lastPosition = LocationHelper.currentLocation + lastPosition = LocationHelper.currentLocation.coordinate var positionPacket = Position() - positionPacket.latitudeI = Int32(LocationHelper.currentLocation.latitude * 1e7) - positionPacket.longitudeI = Int32(LocationHelper.currentLocation.longitude * 1e7) - positionPacket.time = UInt32(LocationHelper.currentTimestamp.timeIntervalSince1970) - positionPacket.timestamp = UInt32(LocationHelper.currentTimestamp.timeIntervalSince1970) - positionPacket.altitude = Int32(LocationHelper.currentAltitude) + positionPacket.latitudeI = Int32(LocationHelper.currentLocation.coordinate.latitude * 1e7) + positionPacket.longitudeI = Int32(LocationHelper.currentLocation.coordinate.longitude * 1e7) + positionPacket.time = UInt32(LocationHelper.currentLocation.timestamp.timeIntervalSince1970) + positionPacket.timestamp = UInt32(LocationHelper.currentLocation.timestamp.timeIntervalSince1970) + positionPacket.altitude = Int32(LocationHelper.currentLocation.altitude) positionPacket.satsInView = UInt32(LocationHelper.satsInView) - if LocationHelper.currentSpeed >= 0 { - positionPacket.groundSpeed = UInt32(LocationHelper.currentSpeed * 3.6) + if LocationHelper.currentLocation.speed >= 0 { + positionPacket.groundSpeed = UInt32(LocationHelper.currentLocation.speed * 3.6) } - if LocationHelper.currentHeading >= 0 { - positionPacket.groundTrack = UInt32(LocationHelper.currentHeading) + if LocationHelper.currentLocation.course >= 0 { + positionPacket.groundTrack = UInt32(LocationHelper.currentLocation.course) } var meshPacket = MeshPacket() meshPacket.to = UInt32(destNum) diff --git a/Meshtastic/Helpers/LocationHelper.swift b/Meshtastic/Helpers/LocationHelper.swift index b5350426..bb4c168a 100644 --- a/Meshtastic/Helpers/LocationHelper.swift +++ b/Meshtastic/Helpers/LocationHelper.swift @@ -1,58 +1,48 @@ import CoreLocation -class LocationHelper: NSObject, ObservableObject { +class LocationHelper: NSObject, ObservableObject, CLLocationManagerDelegate { + private let locationManager = CLLocationManager() static let shared = LocationHelper() + @Published var lastLocation: CLLocation? + + override init() { + + super.init() + locationManager.delegate = self + locationManager.desiredAccuracy = kCLLocationAccuracyBest + locationManager.pausesLocationUpdatesAutomatically = true + locationManager.allowsBackgroundLocationUpdates = true + locationManager.activityType = .otherNavigation + locationManager.requestWhenInUseAuthorization() + locationManager.distanceFilter = 5 + locationManager.startUpdatingLocation() + } // Apple Park - static let DefaultLocation = CLLocationCoordinate2D(latitude: 37.3346, longitude: -122.0090) - static let DefaultAltitude = CLLocationDistance(integerLiteral: 0) - static let DefaultSpeed = CLLocationSpeed(integerLiteral: 0) - static let DefaultHeading = CLLocationDirection(integerLiteral: 0) + static let DefaultLocation = CLLocation(latitude: 37.3346, longitude: -122.0090) + //static let DefaultAltitude = CLLocationDistance(integerLiteral: 0) + //static let DefaultSpeed = CLLocationSpeed(integerLiteral: 0) + //static let DefaultHeading = CLLocationDirection(integerLiteral: 0) - static var currentLocation: CLLocationCoordinate2D { + static var currentLocation: CLLocation { guard let location = shared.locationManager.location else { return DefaultLocation } - return location.coordinate + return location } - static var currentAltitude: CLLocationDistance { - - guard let altitude = shared.locationManager.location?.altitude else { - return DefaultAltitude - } - return altitude - } - - static var currentSpeed: CLLocationSpeed { - - guard let speed = shared.locationManager.location?.speed else { - return DefaultSpeed - } - return speed - } - - static var currentHeading: CLLocationDirection { - - guard let heading = shared.locationManager.location?.course else { - return DefaultHeading - } - return heading - } - - static var currentTimestamp: Date { - - guard let timestamp = shared.locationManager.location?.timestamp else { - return Date.now - } - return timestamp - } - + /// Sats In View Estimator using horizontal and vertical accuracy since + /// CoreLocation does not have number of sats available static var satsInView: Int { + // Invalid Coordinates + if shared.locationManager.location?.verticalAccuracy ?? 0 < 0 || shared.locationManager.location?.horizontalAccuracy ?? 0 < 0 { + return 0 + } // If we have a position we have a sat var sats = 1 + // If we have a 3D fix we have at least 4 sats if shared.locationManager.location?.verticalAccuracy ?? 0 > 0 { sats = 4 if 0...5 ~= shared.locationManager.location?.horizontalAccuracy ?? 0 { @@ -66,37 +56,37 @@ class LocationHelper: NSObject, ObservableObject { } else if 46...60 ~= shared.locationManager.location?.horizontalAccuracy ?? 0 { sats = 5 } - } else if shared.locationManager.location?.verticalAccuracy ?? 0 < 0 && 60...300 ~= shared.locationManager.location?.horizontalAccuracy ?? 0 { + } else if shared.locationManager.location?.horizontalAccuracy ?? 0 <= 300 { + // Need at least 3 sats to be under 300, over that could be wifi or cell triangulation sats = 3 - } else if shared.locationManager.location?.verticalAccuracy ?? 0 < 0 && shared.locationManager.location?.horizontalAccuracy ?? 0 > 300 { + } else if shared.locationManager.location?.horizontalAccuracy ?? 0 > 300 { sats = 2 } return sats } + + public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { - private let locationManager = CLLocationManager() + guard let mostRecentLocation = locations.last else { + return + } + // Extra Smart positioning logic throwing out bad readings from the phone GPS + let age = -mostRecentLocation.timestamp.timeIntervalSinceNow + print("Location: HA-\(mostRecentLocation.horizontalAccuracy) VA-\(mostRecentLocation.verticalAccuracy) AGE-\(age)") + manager.stopUpdatingLocation() + if age > 10 || mostRecentLocation.horizontalAccuracy < 0 || mostRecentLocation.horizontalAccuracy > 100 { + print("Bad Location: HA-\(mostRecentLocation.horizontalAccuracy) VA-\(mostRecentLocation.verticalAccuracy) AGE-\(age)") + manager.startUpdatingLocation() + } else { + lastLocation = mostRecentLocation + } + } + + public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { + print("Location manager failed with error: \(error.localizedDescription)") + } - private override init() { - - super.init() - locationManager.delegate = self - locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters - locationManager.pausesLocationUpdatesAutomatically = true - locationManager.allowsBackgroundLocationUpdates = true - locationManager.activityType = .otherNavigation - locationManager.requestWhenInUseAuthorization() - locationManager.startUpdatingLocation() - } -} - -extension LocationHelper: CLLocationManagerDelegate { - func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { } - - public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { - print("Location manager failed with error: \(error.localizedDescription)") - } - - public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { - print("Location manager changed the status: \(status)") - } + public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { + print("Location manager changed the status: \(status)") + } } diff --git a/Meshtastic/Persistence/PositionEntityExtension.swift b/Meshtastic/Persistence/PositionEntityExtension.swift index 75ba3f4b..ecfe622f 100644 --- a/Meshtastic/Persistence/PositionEntityExtension.swift +++ b/Meshtastic/Persistence/PositionEntityExtension.swift @@ -58,7 +58,7 @@ extension PositionEntity { } extension PositionEntity: MKAnnotation { - public var coordinate: CLLocationCoordinate2D { nodeCoordinate ?? LocationHelper.DefaultLocation } + public var coordinate: CLLocationCoordinate2D { nodeCoordinate ?? LocationHelper.DefaultLocation.coordinate } public var title: String? { nodePosition?.user?.shortName ?? NSLocalizedString("unknown", comment: "Unknown") } public var subtitle: String? { time?.formatted() } } diff --git a/Meshtastic/Persistence/WaypointEntityExtension.swift b/Meshtastic/Persistence/WaypointEntityExtension.swift index 1744c30e..f7531092 100644 --- a/Meshtastic/Persistence/WaypointEntityExtension.swift +++ b/Meshtastic/Persistence/WaypointEntityExtension.swift @@ -48,7 +48,7 @@ extension WaypointEntity { } extension WaypointEntity: MKAnnotation { - public var coordinate: CLLocationCoordinate2D { waypointCoordinate ?? LocationHelper.DefaultLocation } + public var coordinate: CLLocationCoordinate2D { waypointCoordinate ?? LocationHelper.DefaultLocation.coordinate } public var title: String? { name ?? "Dropped Pin" } public var subtitle: String? { (longDescription ?? "") + diff --git a/Meshtastic/Views/Helpers/Weather/NodeWeatherForecast.swift b/Meshtastic/Views/Helpers/Weather/NodeWeatherForecast.swift index 28dcfeb7..fbac2b1c 100644 --- a/Meshtastic/Views/Helpers/Weather/NodeWeatherForecast.swift +++ b/Meshtastic/Views/Helpers/Weather/NodeWeatherForecast.swift @@ -210,7 +210,7 @@ struct NodeWeatherForecast { struct NodeWeatherForecastView_Previews: PreviewProvider { static var previews: some View { - NodeWeatherForecastView(location: CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude) ) + NodeWeatherForecastView(location: CLLocation(latitude: LocationHelper.currentLocation.coordinate.latitude, longitude: LocationHelper.currentLocation.coordinate.longitude) ) .aspectRatio(2, contentMode: .fit) .padding() .previewLayout(.sizeThatFits) diff --git a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift index 184bab52..ed361a48 100644 --- a/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift +++ b/Meshtastic/Views/Map/Custom/MapViewSwiftUI.swift @@ -39,7 +39,7 @@ struct MapViewSwiftUI: UIViewRepresentable { .filter { $0.latest == true } .sorted { $0.nodePosition?.num ?? 0 > $1.nodePosition?.num ?? -1 } let span = MKCoordinateSpan(latitudeDelta: 0.003, longitudeDelta: 0.003) - let center = (latest.count > 0 && userTrackingMode == MKUserTrackingMode.none) ? latest[0].coordinate : LocationHelper.currentLocation + let center = (latest.count > 0 && userTrackingMode == MKUserTrackingMode.none) ? latest[0].coordinate : LocationHelper.currentLocation.coordinate let region = MKCoordinateRegion(center: center, span: span) mapView.addAnnotations(showNodeHistory ? positions : latest) mapView.setRegion(region, animated: true) @@ -263,7 +263,7 @@ struct MapViewSwiftUI: UIViewRepresentable { annotationView.glyphImage = UIImage(systemName: "flipphone") } if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 { - let metersAway = positionAnnotation.coordinate.distance(from: LocationHelper.currentLocation) + let metersAway = positionAnnotation.coordinate.distance(from: LocationHelper.currentLocation.coordinate) subtitle.text! += NSLocalizedString("distance", comment: "") + ": \(distanceFormatter.string(fromDistance: Double(metersAway))) \n" } subtitle.text! += positionAnnotation.time?.formatted() ?? "Unknown \n" @@ -296,7 +296,7 @@ struct MapViewSwiftUI: UIViewRepresentable { subtitle.text = "" } if LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) > 0.0 { - let metersAway = waypointAnnotation.coordinate.distance(from: LocationHelper.currentLocation) + let metersAway = waypointAnnotation.coordinate.distance(from: LocationHelper.currentLocation.coordinate) let distanceFormatter = MKDistanceFormatter() subtitle.text! += NSLocalizedString("distance", comment: "") + ": \(distanceFormatter.string(fromDistance: Double(metersAway))) \n" } diff --git a/Meshtastic/Views/Map/WaypointFormView.swift b/Meshtastic/Views/Map/WaypointFormView.swift index 0127e6f8..548363dc 100644 --- a/Meshtastic/Views/Map/WaypointFormView.swift +++ b/Meshtastic/Views/Map/WaypointFormView.swift @@ -30,14 +30,14 @@ struct WaypointFormView: View { var body: some View { Form { - let distance = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude).distance(from: CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)) + let distance = CLLocation(latitude: LocationHelper.currentLocation.coordinate.latitude, longitude: LocationHelper.currentLocation.coordinate.longitude).distance(from: CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)) Section(header: Text((waypointId > 0) ? "Editing Waypoint" : "Create Waypoint")) { HStack { Text("Location: \(String(format: "%.5f", latitude) + "," + String(format: "%.5f", longitude))") .textSelection(.enabled) .foregroundColor(Color.gray) .font(.caption2) - if coordinate.latitude != LocationHelper.DefaultLocation.latitude && coordinate.longitude != LocationHelper.DefaultLocation.longitude { + if coordinate.latitude != LocationHelper.DefaultLocation.coordinate.latitude && coordinate.longitude != LocationHelper.DefaultLocation.coordinate.longitude { DistanceText(meters: distance) .foregroundColor(Color.gray) .font(.caption2) diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 8508dfd7..c43b42d4 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -108,7 +108,7 @@ struct NodeDetail: View { .font(.title) .padding() let nodeLocation = node.positions?.lastObject as? PositionEntity - NodeWeatherForecastView(location: CLLocation(latitude: nodeLocation?.nodeCoordinate!.latitude ?? LocationHelper.currentLocation.latitude, longitude: nodeLocation?.nodeCoordinate!.longitude ?? LocationHelper.currentLocation.longitude) ) + NodeWeatherForecastView(location: CLLocation(latitude: nodeLocation?.nodeCoordinate!.latitude ?? LocationHelper.currentLocation.coordinate.latitude, longitude: nodeLocation?.nodeCoordinate!.longitude ?? LocationHelper.currentLocation.coordinate.longitude) ) .frame(height: 250) } #else @@ -117,7 +117,7 @@ struct NodeDetail: View { .font(.title) .padding() let nodeLocation = node.positions?.lastObject as? PositionEntity - NodeWeatherForecastView(location: CLLocation(latitude: nodeLocation?.nodeCoordinate!.latitude ?? LocationHelper.currentLocation.latitude, longitude: nodeLocation?.nodeCoordinate!.longitude ?? LocationHelper.currentLocation.longitude) ).frame(height: 250) + NodeWeatherForecastView(location: CLLocation(latitude: nodeLocation?.nodeCoordinate!.latitude ?? LocationHelper.currentLocation.coordinate.latitude, longitude: nodeLocation?.nodeCoordinate!.longitude ?? LocationHelper.currentLocation.coordinate.longitude) ).frame(height: 250) .presentationDetents([.medium]) .presentationDragIndicator(.automatic) } @@ -211,7 +211,7 @@ struct NodeDetail: View { } .edgesIgnoringSafeArea([.leading, .trailing]) .sheet(isPresented: $presentingWaypointForm ) {// , onDismiss: didDismissSheet) { - WaypointFormView(coordinate: waypointCoordinate ?? LocationHelper.DefaultLocation, waypointId: editingWaypoint) + WaypointFormView(coordinate: waypointCoordinate ?? LocationHelper.DefaultLocation.coordinate, waypointId: editingWaypoint) .presentationDetents([.medium, .large]) .presentationDragIndicator(.automatic) } @@ -250,7 +250,7 @@ struct NodeDetail: View { let mostRecent = node.positions?.lastObject as? PositionEntity - let weather = try await WeatherService.shared.weather(for: mostRecent?.nodeLocation ?? CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude)) + let weather = try await WeatherService.shared.weather(for: mostRecent?.nodeLocation ?? CLLocation(latitude: LocationHelper.currentLocation.coordinate.latitude, longitude: LocationHelper.currentLocation.coordinate.longitude)) condition = weather.currentWeather.condition temperature = weather.currentWeather.temperature humidity = Int(weather.currentWeather.humidity * 100) diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index c2c96d68..3dac2efc 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -52,8 +52,8 @@ struct NodeList: View { if node.positions?.count ?? 0 > 0 && (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral?.num ?? -1 != node.num) { HStack(alignment: .bottom) { let lastPostion = node.positions!.reversed()[0] as! PositionEntity - let myCoord = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude) - if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.latitude { + let myCoord = CLLocation(latitude: LocationHelper.currentLocation.coordinate.latitude, longitude: LocationHelper.currentLocation.coordinate.longitude) + if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.coordinate.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.coordinate.latitude { let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude) let metersAway = nodeCoord.distance(from: myCoord) Image(systemName: "lines.measurement.horizontal") diff --git a/Meshtastic/Views/Nodes/NodeMap.swift b/Meshtastic/Views/Nodes/NodeMap.swift index 650a76a5..9829d09a 100644 --- a/Meshtastic/Views/Nodes/NodeMap.swift +++ b/Meshtastic/Views/Nodes/NodeMap.swift @@ -46,7 +46,7 @@ struct NodeMap: View { @State private var mapType: MKMapType = .standard @State private var userTrackingMode: MKUserTrackingMode = .none - @State var waypointCoordinate: CLLocationCoordinate2D = LocationHelper.DefaultLocation + @State var waypointCoordinate: CLLocationCoordinate2D = LocationHelper.DefaultLocation.coordinate @State var editingWaypoint: Int = 0 @State private var presentingWaypointForm = false @State private var customMapOverlay: MapViewSwiftUI.CustomMapOverlay? = MapViewSwiftUI.CustomMapOverlay( @@ -63,7 +63,7 @@ struct NodeMap: View { MapViewSwiftUI(onLongPress: { coord in waypointCoordinate = coord editingWaypoint = 0 - if waypointCoordinate.distance(from: LocationHelper.DefaultLocation) == 0.0 { + if waypointCoordinate.distance(from: LocationHelper.DefaultLocation.coordinate) == 0.0 { print("Apple Park") } else { presentingWaypointForm = true diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index dae61991..49cc7efc 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -9,6 +9,7 @@ struct AppSettings: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @EnvironmentObject var userSettings: UserSettings + @StateObject var locationHelper = LocationHelper() @State private var isPresentingCoreDataResetConfirm = false @State private var preferredDeviceConnected = false @@ -39,7 +40,32 @@ struct AppSettings: View { } Section(header: Text("phone.gps")) { - + let accuracy = Measurement(value: locationHelper.lastLocation?.horizontalAccuracy ?? 300, unit: UnitLength.meters) + let altitiude = Measurement(value: locationHelper.lastLocation?.altitude ?? 0, unit: UnitLength.meters) + let speed = Measurement(value: locationHelper.lastLocation?.speed ?? 0, unit: UnitSpeed.kilometersPerHour) + Label("Coordinates \(String(format: "%.5f", locationHelper.lastLocation?.coordinate.latitude ?? 0)), \(String(format: "%.5f", locationHelper.lastLocation?.coordinate.longitude ?? 0))", systemImage: "mappin") + .textSelection(.enabled) + HStack { + Label("GPS Accuracy \(accuracy.formatted())", systemImage: "scope") + .font(.caption) + + if LocationHelper.currentLocation.verticalAccuracy > 0 { + Label("Altitude \(altitiude.formatted())", systemImage: "mountain.2") + .font(.caption) + } + } + if locationHelper.lastLocation?.courseAccuracy ?? 0 > 0 || locationHelper.lastLocation?.speedAccuracy ?? 0 > 0 { + HStack { + if locationHelper.lastLocation?.courseAccuracy ?? 0 > 0 { + Label("Bearing \(String(format: "%.2f", locationHelper.lastLocation?.course ?? 0))°", systemImage: "location.circle") + .font(.caption) + } + if locationHelper.lastLocation?.speedAccuracy ?? 0 > 0 { + Label("Speed \(speed.formatted())", systemImage: "speedometer") + .font(.caption) + } + } + } Toggle(isOn: $userSettings.provideLocation) { Label("provide.location", systemImage: "location.circle.fill")