mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Assorted cleanup
This commit is contained in:
parent
13451323bd
commit
0b11f8ed7d
11 changed files with 115 additions and 103 deletions
|
|
@ -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 */,
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue