Assorted cleanup

This commit is contained in:
Garth Vander Houwen 2023-09-19 17:06:47 -07:00
parent 13451323bd
commit 0b11f8ed7d
11 changed files with 115 additions and 103 deletions

View file

@ -166,6 +166,7 @@
DDDE5A1329AFEAB900490C6C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDDE5A1229AFEAB900490C6C /* Assets.xcassets */; };
DDDE5A1429AFEAB900490C6C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDDE5A1229AFEAB900490C6C /* Assets.xcassets */; };
DDE0F7C5295F77B700B8AAB3 /* AppSettingsEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */; };
DDE179302ABA2482005777A8 /* LocationDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1792F2ABA2482005777A8 /* LocationDataManager.swift */; };
DDF6B2482A9AEBF500BA6931 /* StoreForward.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF6B2472A9AEBF500BA6931 /* StoreForward.swift */; };
DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; };
DDFEB3BB29900C1200EE7472 /* CurrentConditionsCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */; };
@ -389,6 +390,7 @@
DDDE5A1229AFEAB900490C6C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
DDDEE5E229DBE43E00A8E078 /* MeshtasticDataModelV11.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV11.xcdatamodel; sourceTree = "<group>"; };
DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsEnums.swift; sourceTree = "<group>"; };
DDE1792F2ABA2482005777A8 /* LocationDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationDataManager.swift; sourceTree = "<group>"; };
DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV4.xcdatamodel; sourceTree = "<group>"; };
DDF6B2462A9AEB9E00BA6931 /* MeshtasticDataModelV17.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV17.xcdatamodel; sourceTree = "<group>"; };
DDF6B2472A9AEBF500BA6931 /* StoreForward.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreForward.swift; sourceTree = "<group>"; };
@ -791,6 +793,7 @@
DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */,
DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */,
DDDB443C29F6592F00EE2349 /* NetworkManager.swift */,
DDE1792F2ABA2482005777A8 /* LocationDataManager.swift */,
);
path = Helpers;
sourceTree = "<group>";
@ -1180,6 +1183,7 @@
DD5E520E298EE33B00D21B61 /* mqtt.pb.swift in Sources */,
DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */,
DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */,
DDE179302ABA2482005777A8 /* LocationDataManager.swift in Sources */,
DDDB443D29F6592F00EE2349 /* NetworkManager.swift in Sources */,
DDB6CCFB2AAF805100945AF6 /* NodeMapSwiftUI.swift in Sources */,
DD73FD1128750779000852D6 /* PositionLog.swift in Sources */,

View file

@ -85,7 +85,6 @@ enum UserTrackingModes: Int, CaseIterable, Identifiable {
}
enum LocationUpdateInterval: Int, CaseIterable, Identifiable {
case fiveSeconds = 5
case tenSeconds = 10
case fifteenSeconds = 15
case thirtySeconds = 30
@ -97,8 +96,6 @@ enum LocationUpdateInterval: Int, CaseIterable, Identifiable {
var id: Int { self.rawValue }
var description: String {
switch self {
case .fiveSeconds:
return "interval.five.seconds".localized
case .tenSeconds:
return "interval.ten.seconds".localized
case .fifteenSeconds:

View file

@ -18,3 +18,45 @@ extension CLLocationCoordinate2D {
return from.distance(from: to)
}
}
extension [CLLocationCoordinate2D] {
/// Get Convex Hull For an array of CLLocationCoordinate2D positions
/// - Returns: A smaller CLLocationCoordinate2D array containing only the points necessary to create a convex hull polygon
func getConvexHull() -> [CLLocationCoordinate2D] {
/// X = longitude
/// Y = latitude
/// 2D cross product of OA and OB vectors, i.e. z-component of their 3D cross product.
/// Returns a positive value, if OAB makes a counter-clockwise turn,
/// negative for clockwise turn, and zero if the points are collinear.
func cross(P: CLLocationCoordinate2D, A: CLLocationCoordinate2D, B: CLLocationCoordinate2D) -> Double {
let part1 = (A.longitude - P.longitude) * (B.latitude - P.latitude)
let part2 = (A.latitude - P.latitude) * (B.longitude - P.longitude)
return part1 - part2;
}
// Sort points lexicographically
let points = self.sorted() {
$0.longitude == $1.longitude ? $0.latitude < $1.latitude : $0.longitude < $1.longitude
}
// Build the lower hull
var lower: [CLLocationCoordinate2D] = []
for p in points {
while lower.count >= 2 && cross(P: lower[lower.count - 2], A: lower[lower.count - 1], B: p) <= 0 {
lower.removeLast()
}
lower.append(p)
}
// Build upper hull
var upper: [CLLocationCoordinate2D] = []
for p in points.reversed() {
while upper.count >= 2 && cross(P: upper[upper.count-2], A: upper[upper.count-1], B: p) <= 0 {
upper.removeLast()
}
upper.append(p)
}
// Last point of upper list is omitted because it is repeated at the
// beginning of the lower list.
upper.removeLast()
// Concatenation of the lower and upper hulls gives the convex hull.
return (upper + lower)
}
}

View file

@ -42,6 +42,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
var lastPosition: CLLocationCoordinate2D?
let emptyNodeNum: UInt32 = 4294967295
let mqttManager = MqttClientProxyManager.shared
let locationHelper = LocationHelper.shared
var wantRangeTestPackets = false
/* Meshtastic Service Details */
var TORADIO_characteristic: CBCharacteristic!
@ -834,27 +835,28 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
return success
}
public func sendPosition(destNum: Int64, wantResponse: Bool, smartPosition: Bool) -> Bool {
public func sendPosition(destNum: Int64, wantResponse: Bool) -> Bool {
var success = false
let fromNodeNum = connectedPeripheral.num
if fromNodeNum <= 0 || LocationHelper.currentLocation.distance(from: LocationHelper.DefaultLocation) == 0.0 {
return false
}
if smartPosition {
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) {
return false
}
}
}
}
lastPosition = LocationHelper.currentLocation
// if smartPosition {
// 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) {
// return false
// }
// }
// }
// }
// lastPosition = LocationHelper.currentLocation
// var locationHelper = LocationHelper()
var positionPacket = Position()
positionPacket.latitudeI = Int32(LocationHelper.currentLocation.latitude * 1e7)
positionPacket.longitudeI = Int32(LocationHelper.currentLocation.longitude * 1e7)
positionPacket.latitudeI = Int32((locationFetcher.lastKnownLocation?.latitude ?? 0) * 1e7)
positionPacket.longitudeI = Int32((locationFetcher.manager.location?.coordinate.longitude ?? 0) * 1e7)
positionPacket.time = UInt32(LocationHelper.currentTimestamp.timeIntervalSince1970)
positionPacket.timestamp = UInt32(LocationHelper.currentTimestamp.timeIntervalSince1970)
positionPacket.altitude = Int32(LocationHelper.currentAltitude)
@ -892,7 +894,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate
if connectedPeripheral != nil {
// Send a position out to the mesh if "share location with the mesh" is enabled in settings
if UserDefaults.provideLocation {
let _ = sendPosition(destNum: connectedPeripheral.num, wantResponse: false, smartPosition: true)
let _ = sendPosition(destNum: connectedPeripheral.num, wantResponse: false)
}
}
}

View file

@ -1,9 +1,12 @@
import Foundation
import CoreLocation
import MapKit
class LocationHelper: NSObject, ObservableObject, CLLocationManagerDelegate {
static let shared = LocationHelper()
var locationManager = CLLocationManager()
//@Published var region = MKCoordinateRegion()
@Published var authorizationStatus: CLAuthorizationStatus?
override init() {
super.init()
@ -89,6 +92,13 @@ class LocationHelper: NSObject, ObservableObject, CLLocationManagerDelegate {
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// locationManager.stopUpdatingLocation()
// locations.last.map {
// region = MKCoordinateRegion(
// center: $0.coordinate,
// span: .init(latitudeDelta: 0.01, longitudeDelta: 0.01)
// )
// }
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Location manager error: \(error.localizedDescription)")

View file

@ -384,7 +384,7 @@ struct ChannelMessageList: View {
focusedField = nil
replyMessageId = 0
if sendPositionWithMessage {
if bleManager.sendPosition(destNum: Int64(channel.index), wantResponse: false, smartPosition: false) {
if bleManager.sendPosition(destNum: Int64(channel.index), wantResponse: false) {
print("Location Sent")
}
}
@ -401,7 +401,7 @@ struct ChannelMessageList: View {
focusedField = nil
replyMessageId = 0
if sendPositionWithMessage {
if bleManager.sendPosition(destNum: Int64(channel.index), wantResponse: false, smartPosition: false) {
if bleManager.sendPosition(destNum: Int64(channel.index), wantResponse: false) {
print("Location Sent")
}
}

View file

@ -335,7 +335,7 @@ struct UserMessageList: View {
focusedField = nil
replyMessageId = 0
if sendPositionWithMessage {
if bleManager.sendPosition(destNum: user.num, wantResponse: true, smartPosition: false) {
if bleManager.sendPosition(destNum: user.num, wantResponse: true) {
print("Location Sent")
}
}
@ -352,7 +352,7 @@ struct UserMessageList: View {
focusedField = nil
replyMessageId = 0
if sendPositionWithMessage {
if bleManager.sendPosition(destNum: user.num, wantResponse: true, smartPosition: false) {
if bleManager.sendPosition(destNum: user.num, wantResponse: true) {
print("Location Sent")
}
}

View file

@ -18,30 +18,26 @@ struct NodeMapSwiftUI: View {
/// Parameters
@ObservedObject var node: NodeInfoEntity
@State var showUserLocation: Bool = false
/// Map State
@Namespace var mapScope
@State var positions: [PositionEntity] = []
@State var waypoints: [WaypointEntity] = []
/// Map State User Defaults
@AppStorage("meshMapShowNodeHistory") private var showNodeHistory = false
@AppStorage("meshMapShowRouteLines") private var showRouteLines = false
@AppStorage("meshMapShowConvexHull") private var showConvexHull = true
@AppStorage("enableMapTraffic") private var showTraffic: Bool = true
@AppStorage("enableMapPointsOfInterest") private var showPointsOfInterest: Bool = true
@AppStorage("mapLayer") private var selectedMapLayer: MapLayer = .hybrid
// Map Configuration
@Namespace var mapScope
@State private var mapStyle: MapStyle = MapStyle.hybrid(elevation: .realistic, pointsOfInterest: .all, showsTraffic: true)
@State private var position = MapCameraPosition.automatic
@State private var scene: MKLookAroundScene?
@State private var isLookingAround = false
@State private var isEditingSettings = false
@State private var showConvexHull = true
@State private var selected: PositionEntity?
@State private var showingPopover = false
/// Data
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)],
predicate: NSPredicate(
format: "expire == nil || expire >= %@", Date() as NSDate
), animation: .none)
private var waypoints: FetchedResults<WaypointEntity>
@State private var showingPositionPopover = false
var body: some View {
let nodeColor = UIColor(hex: UInt32(node.num))
let positionArray = node.positions?.array as? [PositionEntity] ?? []
let mostRecent = node.positions?.lastObject as? PositionEntity
let lineCoords = positionArray.compactMap({(position) -> CLLocationCoordinate2D in
@ -51,6 +47,8 @@ struct NodeMapSwiftUI: View {
if node.hasPositions {
ZStack {
Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) {
/// Node Color from node.num
let nodeColor = UIColor(hex: UInt32(node.num))
/// Route Lines
if showRouteLines {
if showRouteLines {
@ -68,7 +66,7 @@ struct NodeMapSwiftUI: View {
}
/// Convex Hull
if showConvexHull {
let hull = getConvexHull(input: lineCoords)
let hull = lineCoords.getConvexHull()
MapPolygon(coordinates: hull)
.stroke(Color(nodeColor.darker()), lineWidth: 5)
.foregroundStyle(Color(nodeColor).opacity(0.4))
@ -78,6 +76,7 @@ struct NodeMapSwiftUI: View {
let pf = PositionFlags(rawValue: Int(position.nodePosition?.metadata?.positionFlags ?? 3))
let formatter = MeasurementFormatter()
let speedText = formatter.string(from: Measurement(value: Double(position.speed), unit: UnitSpeed.kilometersPerHour))
let headingDegrees = Angle.degrees(Double(position.heading))
Annotation(position.latest ? node.user?.shortName ?? "?" : (pf.contains(.Speed) && position.speed > 2) ? speedText : "", coordinate: position.coordinate) {
ZStack {
if position.latest {
@ -85,18 +84,18 @@ struct NodeMapSwiftUI: View {
.foregroundStyle(Color(nodeColor.lighter()).opacity(0.4))
.frame(width: 60, height: 60)
if pf.contains(.Heading) {
Image(systemName: pf.contains(.Speed) && position.speed > 1 ? "location.north.fill" : "location.north")
Image(systemName: pf.contains(.Speed) && position.speed > 1 ? "location.north" : "hexagon")
.symbolEffect(.pulse.byLayer)
.padding(5)
.foregroundStyle(Color(nodeColor).isLight() ? .black : .white)
.background(Color(UIColor(hex: UInt32(node.num)).darker()))
.clipShape(Circle())
.rotationEffect(.degrees(Double(position.heading)))
.rotationEffect(headingDegrees)
.onTapGesture {
showingPopover = true
showingPositionPopover = true
selected = (selected == position ? nil : position) // <-- here
}
.popover(isPresented: $showingPopover, arrowEdge: .bottom) {
.popover(isPresented: $showingPositionPopover, arrowEdge: .bottom) {
PositionPopover(position: position)
.padding()
.opacity(0.8)
@ -110,10 +109,10 @@ struct NodeMapSwiftUI: View {
.background(Color(UIColor(hex: UInt32(node.num)).darker()))
.clipShape(Circle())
.onTapGesture {
showingPopover = true
showingPositionPopover = true
selected = (selected == position ? nil : position) // <-- here
}
.popover(isPresented: $showingPopover, arrowEdge: .bottom) {
.popover(isPresented: $showingPositionPopover, arrowEdge: .bottom) {
PositionPopover(position: position)
.padding()
.opacity(0.8)
@ -123,12 +122,12 @@ struct NodeMapSwiftUI: View {
} else {
if showNodeHistory {
if pf.contains(.Heading) {
Image(systemName: pf.contains(.Speed) && position.speed > 0 ? "location.north.fill" : "hexagon")
Image(systemName: pf.contains(.Speed) && position.speed > 1 ? "location.north" : "hexagon")
.padding(2)
.foregroundStyle(Color(UIColor(hex: UInt32(node.num)).lighter()).isLight() ? .black : .white)
.background(Color(UIColor(hex: UInt32(node.num)).lighter()))
.clipShape(Circle())
.rotationEffect(.degrees(Double(position.heading)))
.rotationEffect(headingDegrees)
} else {
Image(systemName: "mappin.circle")
.padding(2)
@ -141,6 +140,8 @@ struct NodeMapSwiftUI: View {
}
}
.tag(position.time)
.annotationTitles(.automatic)
.annotationSubtitles(.automatic)
}
}
.mapScope(mapScope)
@ -339,49 +340,4 @@ struct NodeMapSwiftUI: View {
let lookAroundScene = MKLookAroundSceneRequest(coordinate: coordinate)
return try await lookAroundScene.scene
}
func getConvexHull(input: [CLLocationCoordinate2D]) -> [CLLocationCoordinate2D] {
// X = longitude
// Y = latitudeß
// 2D cross product of OA and OB vectors, i.e. z-component of their 3D cross product.
// Returns a positive value, if OAB makes a counter-clockwise turn,
// negative for clockwise turn, and zero if the points are collinear.
func cross(P: CLLocationCoordinate2D, A: CLLocationCoordinate2D, B: CLLocationCoordinate2D) -> Double {
let part1 = (A.longitude - P.longitude) * (B.latitude - P.latitude)
let part2 = (A.latitude - P.latitude) * (B.longitude - P.longitude)
return part1 - part2;
}
// Sort points lexicographically
let points = input.sorted() {
$0.longitude == $1.longitude ? $0.latitude < $1.latitude : $0.longitude < $1.longitude
}
// Build the lower hull
var lower: [CLLocationCoordinate2D] = []
for p in points {
while lower.count >= 2 && cross(P: lower[lower.count - 2], A: lower[lower.count - 1], B: p) <= 0 {
lower.removeLast()
}
lower.append(p)
}
// Build upper hull
var upper: [CLLocationCoordinate2D] = []
for p in points.reversed() {
while upper.count >= 2 && cross(P: upper[upper.count-2], A: upper[upper.count-1], B: p) <= 0 {
upper.removeLast()
}
upper.append(p)
}
// Last point of upper list is omitted because it is repeated at the
// beginning of the lower list.
upper.removeLast()
// Concatenation of the lower and upper hulls gives the convex hull.
return (upper + lower)
}
}

View file

@ -79,20 +79,20 @@ struct PositionPopover: View {
.padding(.bottom, 5)
}
/// Heading
// if pf.contains(.Heading) {
// let degrees = Angle.degrees(Double(position.heading))
// Label {
// let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees)
// Text("Heading: \(heading.formatted())")
// .foregroundColor(.primary)
// } icon: {
// Image(systemName: "location.north")
// .symbolRenderingMode(.hierarchical)
// .frame(width: 35)
// .rotationEffect(degrees)
// }
// .padding(.bottom, 5)
// }
if pf.contains(.Heading) {
let degrees = Angle.degrees(Double(position.heading))
Label {
let heading = Measurement(value: degrees.degrees, unit: UnitAngle.degrees)
Text("Heading: \(heading.formatted())")
.foregroundColor(.primary)
} icon: {
Image(systemName: "location.north")
.symbolRenderingMode(.hierarchical)
.frame(width: 35)
.rotationEffect(degrees)
}
.padding(.bottom, 5)
}
/// Speed
if pf.contains(.Speed) {
let formatter = MeasurementFormatter()

View file

@ -50,7 +50,8 @@ struct PositionLog: View {
Text(speed.formatted())
}
TableColumn("Heading") { position in
Text("\(position.heading)°")
let heading = Measurement(value: Double(position.heading), unit: UnitAngle.degrees)
Text("\(heading.formatted())")
}
TableColumn("SNR") { position in
Text("\(String(format: "%.2f", position.snr)) dB")

View file

@ -277,7 +277,7 @@ struct PositionConfig: View {
Button(buttonText) {
if fixedPosition {
_ = bleManager.sendPosition(destNum: node!.num, wantResponse: true, smartPosition: false)
_ = bleManager.sendPosition(destNum: node!.num, wantResponse: true)
}
let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context)