Move node map into map content view

Dont change channel for node infos directed at other nodes
This commit is contained in:
Garth Vander Houwen 2024-03-14 23:05:05 -07:00
parent 7464f8d4cd
commit 550add228c
4 changed files with 177 additions and 117 deletions

View file

@ -110,6 +110,7 @@
DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8ED9C7289CE4B900B3B0AB /* RoutingError.swift */; };
DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90860D26F69BAE00DC5189 /* NodeMap.swift */; };
DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */; };
DD93800B2BA3F968008BEC06 /* NodeMapContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD93800A2BA3F968008BEC06 /* NodeMapContent.swift */; };
DD94B7402ACCE3BE00DCD1D1 /* MapSettingsForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD94B73F2ACCE3BE00DCD1D1 /* MapSettingsForm.swift */; };
DD964FBD296E6B01007C176F /* EmojiOnlyTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */; };
DD964FBF296E76EF007C176F /* WaypointFormMapKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD964FBE296E76EF007C176F /* WaypointFormMapKit.swift */; };
@ -360,6 +361,7 @@
DD90860A26F645B700DC5189 /* Meshtastic.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Meshtastic.entitlements; sourceTree = "<group>"; };
DD90860D26F69BAE00DC5189 /* NodeMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMap.swift; sourceTree = "<group>"; };
DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationManager.swift; sourceTree = "<group>"; };
DD93800A2BA3F968008BEC06 /* NodeMapContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMapContent.swift; sourceTree = "<group>"; };
DD94B73F2ACCE3BE00DCD1D1 /* MapSettingsForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapSettingsForm.swift; sourceTree = "<group>"; };
DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiOnlyTextField.swift; sourceTree = "<group>"; };
DD964FBE296E76EF007C176F /* WaypointFormMapKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaypointFormMapKit.swift; sourceTree = "<group>"; };
@ -748,6 +750,7 @@
DDAD49EB2AFAE82500B4425D /* Map */ = {
isa = PBXGroup;
children = (
DD93800A2BA3F968008BEC06 /* NodeMapContent.swift */,
DD94B73F2ACCE3BE00DCD1D1 /* MapSettingsForm.swift */,
DDB6CCFA2AAF805100945AF6 /* NodeMapSwiftUI.swift */,
DD13AA482AB73BF400BA0C98 /* PositionPopover.swift */,
@ -1365,6 +1368,7 @@
DD5E5205298EE33B00D21B61 /* mesh.pb.swift in Sources */,
DDF6B2482A9AEBF500BA6931 /* StoreForwardConfig.swift in Sources */,
DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */,
DD93800B2BA3F968008BEC06 /* NodeMapContent.swift in Sources */,
DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */,
DD1925B728CDA5A400720036 /* CannedMessagesConfigEnums.swift in Sources */,
DDDB444429F8A8DD00EE2349 /* Float.swift in Sources */,

View file

@ -144,9 +144,10 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
newNode.snr = packet.rxSnr
newNode.rssi = packet.rxRssi
newNode.viaMqtt = packet.viaMqtt
newNode.channel = Int32(packet.channel)
if packet.to == 4294967295 || packet.to == UserDefaults.preferredPeripheralNum {
newNode.channel = Int32(packet.channel)
}
if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) {
newNode.channel = Int32(nodeInfoMessage.channel)
newNode.hopsAway = Int32(truncatingIfNeeded: nodeInfoMessage.hopsAway)
}
if let newUserMessage = try? User(serializedData: packet.decoded.payload) {
@ -212,7 +213,9 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext)
fetchedNode[0].snr = packet.rxSnr
fetchedNode[0].rssi = packet.rxRssi
fetchedNode[0].viaMqtt = packet.viaMqtt
fetchedNode[0].channel = Int32(packet.channel)
if packet.to == 4294967295 || packet.to == UserDefaults.preferredPeripheralNum {
fetchedNode[0].channel = Int32(packet.channel)
}
if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) {
fetchedNode[0].channel = Int32(nodeInfoMessage.channel)

View file

@ -0,0 +1,166 @@
//
// RouteLines.swift
// Meshtastic
//
// Created by Garth Vander Houwen on 3/14/24.
//
import SwiftUI
import MapKit
@available(iOS 17.0, macOS 14.0, *)
struct NodeMapContent: MapContent {
@ObservedObject var node: NodeInfoEntity
@State var showUserLocation: Bool = false
@State var positions: [PositionEntity] = []
/// Map State User Defaults
@AppStorage("meshMapShowNodeHistory") private var showNodeHistory = false
@AppStorage("meshMapShowRouteLines") private var showRouteLines = false
@AppStorage("enableMapConvexHull") private var showConvexHull = false
@AppStorage("enableMapTraffic") private var showTraffic: Bool = false
@AppStorage("enableMapPointsOfInterest") private var showPointsOfInterest: Bool = false
@AppStorage("mapLayer") private var selectedMapLayer: MapLayer = .hybrid
// Map Configuration
@Namespace var mapScope
@State var mapStyle: MapStyle = MapStyle.hybrid(elevation: .realistic, pointsOfInterest: .all, showsTraffic: true)
@State var position = MapCameraPosition.automatic
@State var scene: MKLookAroundScene?
@State var isLookingAround = false
@State var isShowingAltitude = false
@State var isEditingSettings = false
@State var selectedPosition: PositionEntity?
@State var showWaypoints = false
@State var selectedWaypoint: WaypointEntity?
@State var isMeshMap = false
//let region: MKCoordinateRegion
@MapContentBuilder
var nodeMap: some MapContent {
let positionArray = node.positions?.array as? [PositionEntity] ?? []
let lineCoords = positionArray.compactMap({(position) -> CLLocationCoordinate2D in
return position.nodeCoordinate ?? LocationsHandler.DefaultLocation
})
/// Node Color from node.num
let nodeColor = UIColor(hex: UInt32(node.num))
/// Node Annotations
ForEach(positionArray, id: \.id) { position in
let pf = PositionFlags(rawValue: Int(position.nodePosition?.metadata?.positionFlags ?? 771))
let headingDegrees = Angle.degrees(Double(position.heading))
/// Reduced Precision Map Circle
if position.latest && 11...16 ~= position.precisionBits {
let pp = PositionPrecision(rawValue: Int(position.precisionBits))
let radius : CLLocationDistance = pp?.precisionMeters ?? 0
if radius > 0.0 {
MapCircle(center: position.coordinate, radius: radius)
.foregroundStyle(Color(nodeColor).opacity(0.25))
.stroke(.white, lineWidth: 2)
}
}
if showConvexHull {
if lineCoords.count > 0 {
let hull = lineCoords.getConvexHull()
MapPolygon(coordinates: hull)
.stroke(Color(nodeColor.darker()), lineWidth: 3)
.foregroundStyle(Color(nodeColor).opacity(0.4))
}
}
/// Route Lines
if showRouteLines {
let gradient = LinearGradient(
colors: [Color(nodeColor.lighter().lighter().lighter()), Color(nodeColor.lighter()), Color(nodeColor)],
startPoint: .leading, endPoint: .trailing
)
let dashed = StrokeStyle(
lineWidth: 3,
lineCap: .round, lineJoin: .round, dash: [10, 10]
)
MapPolyline(coordinates: lineCoords)
.stroke(gradient, style: dashed)
}
/// Node Annotations
ForEach(positionArray, id: \.id) { position in
Annotation(position.latest ? node.user?.shortName ?? "?": "", coordinate: position.coordinate) {
LazyVStack {
if position.latest {
ZStack {
Circle()
.fill(Color(nodeColor.lighter()).opacity(0.4).shadow(.drop(color: Color(nodeColor).isLight() ? .black : .white, radius: 5)))
.foregroundStyle(Color(nodeColor.lighter()).opacity(0.3))
.frame(width: 50, height: 50)
if pf.contains(.Heading) {
Image(systemName: pf.contains(.Speed) && position.speed > 1 ? "location.north" : "octagon")
.symbolEffect(.pulse.byLayer)
.padding(5)
.foregroundStyle(Color(nodeColor).isLight() ? .black : .white)
.background(Color(nodeColor.darker()))
.clipShape(Circle())
.rotationEffect(headingDegrees)
.onTapGesture {
selectedPosition = (selectedPosition == position ? nil : position)
}
.popover(item: $selectedPosition) { selection in
PositionPopover(position: selection)
.padding()
.opacity(0.8)
.presentationCompactAdaptation(.popover)
}
} else {
Image(systemName: "flipphone")
.symbolEffect(.pulse.byLayer)
.padding(5)
.foregroundStyle(Color(nodeColor).isLight() ? .black : .white)
.background(Color(UIColor(hex: UInt32(node.num)).darker()))
.clipShape(Circle())
.onTapGesture {
selectedPosition = (selectedPosition == position ? nil : position)
}
.popover(item: $selectedPosition) { selection in
PositionPopover(position: selection)
.padding()
.opacity(0.8)
.presentationCompactAdaptation(.popover)
}
}
}
} else {
if showNodeHistory {
if pf.contains(.Heading) {
Image(systemName: "location.north.circle")
.resizable()
.scaledToFit()
.foregroundStyle(Color(UIColor(hex: UInt32(node.num))).isLight() ? .black : .white)
.background(Color(UIColor(hex: UInt32(node.num))))
.clipShape(Circle())
.rotationEffect(headingDegrees)
.frame(width: 16, height: 16)
} else {
Circle()
.fill(Color(UIColor(hex: UInt32(node.num))))
.strokeBorder(Color(UIColor(hex: UInt32(node.num))).isLight() ? .black : .white ,lineWidth: 2)
.frame(width: 12, height: 12)
}
}
}
}
}
.tag(position.time)
.annotationTitles(.automatic)
.annotationSubtitles(.automatic)
}
}
}
@MapContentBuilder
var body: some MapContent {
if node.positions?.count ?? 0 > 0 {
nodeMap
}
}
}

View file

@ -57,120 +57,7 @@ struct NodeMapSwiftUI: View {
ZStack {
MapReader { reader in
Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) {
/// Node Color from node.num
let nodeColor = UIColor(hex: UInt32(node.num))
/// Convex Hull
if showConvexHull {
if lineCoords.count > 0 {
let hull = lineCoords.getConvexHull()
MapPolygon(coordinates: hull)
.stroke(Color(nodeColor.darker()), lineWidth: 3)
.foregroundStyle(Color(nodeColor).opacity(0.4))
}
}
/// Route Lines
if showRouteLines {
let gradient = LinearGradient(
colors: [Color(nodeColor.lighter().lighter().lighter()), Color(nodeColor.lighter()), Color(nodeColor)],
startPoint: .leading, endPoint: .trailing
)
let dashed = StrokeStyle(
lineWidth: 3,
lineCap: .round, lineJoin: .round, dash: [10, 10]
)
MapPolyline(coordinates: lineCoords)
.stroke(gradient, style: dashed)
}
/// Node Annotations
ForEach(positionArray, id: \.id) { position in
let pf = PositionFlags(rawValue: Int(position.nodePosition?.metadata?.positionFlags ?? 771))
let headingDegrees = Angle.degrees(Double(position.heading))
// /// Reduced Precision Map Circle
// if position.latest && 11...16 ~= position.precisionBits {
// let pp = PositionPrecision(rawValue: Int(position.precisionBits))
// let radius : CLLocationDistance = pp?.precisionMeters ?? 0
// if radius > 0.0 {
// MapCircle(center: position.coordinate, radius: radius)
// .foregroundStyle(Color(nodeColor).opacity(0.25))
// .stroke(.white, lineWidth: 2)
// }
// }
// Annotation(position.latest ? node.user?.shortName ?? "?": "", coordinate: position.coordinate) {
// LazyVStack {
// if position.latest {
// ZStack {
// Circle()
// .fill(Color(nodeColor.lighter()).opacity(0.4).shadow(.drop(color: Color(nodeColor).isLight() ? .black : .white, radius: 5)))
// .foregroundStyle(Color(nodeColor.lighter()).opacity(0.3))
// .frame(width: 50, height: 50)
// if pf.contains(.Heading) {
// Image(systemName: pf.contains(.Speed) && position.speed > 1 ? "location.north" : "octagon")
// .symbolEffect(.pulse.byLayer)
// .padding(5)
// .foregroundStyle(Color(nodeColor).isLight() ? .black : .white)
// .background(Color(nodeColor.darker()))
// .clipShape(Circle())
// .rotationEffect(headingDegrees)
// .onTapGesture {
// selectedPosition = (selectedPosition == position ? nil : position)
// }
// .popover(item: $selectedPosition) { selection in
// PositionPopover(position: selection)
// .padding()
// .opacity(0.8)
// .presentationCompactAdaptation(.popover)
// }
//
// } else {
// Image(systemName: "flipphone")
// .symbolEffect(.pulse.byLayer)
// .padding(5)
// .foregroundStyle(Color(nodeColor).isLight() ? .black : .white)
// .background(Color(UIColor(hex: UInt32(node.num)).darker()))
// .clipShape(Circle())
// .onTapGesture {
// selectedPosition = (selectedPosition == position ? nil : position)
// }
// .popover(item: $selectedPosition) { selection in
// PositionPopover(position: selection)
// .padding()
// .opacity(0.8)
// .presentationCompactAdaptation(.popover)
// }
//
// }
// }
// } else {
// if showNodeHistory {
// if pf.contains(.Heading) {
// Image(systemName: "location.north.circle")
// .resizable()
// .scaledToFit()
// .foregroundStyle(Color(UIColor(hex: UInt32(node.num))).isLight() ? .black : .white)
// .background(Color(UIColor(hex: UInt32(node.num))))
// .clipShape(Circle())
// .rotationEffect(headingDegrees)
// .frame(width: 16, height: 16)
//
// } else {
// Circle()
// .fill(Color(UIColor(hex: UInt32(node.num))))
// .strokeBorder(Color(UIColor(hex: UInt32(node.num))).isLight() ? .black : .white ,lineWidth: 2)
// .frame(width: 12, height: 12)
// }
// }
// }
}
// }
// .tag(position.time)
// .annotationTitles(.automatic)
// .annotationSubtitles(.automatic)
// }
NodeMapContent(node: node)
}
.mapScope(mapScope)
.mapStyle(mapStyle)