Meshtastic-Apple/Meshtastic/Views/Nodes/Helpers/Map/MapContent/NodeMapContent.swift

173 lines
6.1 KiB
Swift
Raw Normal View History

//
// RouteLines.swift
// Meshtastic
//
// Created by Garth Vander Houwen on 3/14/24.
//
import SwiftUI
import MapKit
2024-03-25 19:20:36 -07:00
import CoreData
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
2025-05-05 17:21:08 -07:00
@AppStorage("enableMapWaypoints") private var showWaypoints = true
@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 isMeshMap = false
@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
2024-03-29 10:54:30 -07:00
ForEach(node.positions?.array as? [PositionEntity] ?? [], 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 && 12...15 ~= 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)
}
}
2024-05-24 18:15:53 -07:00
let loraNodes = positions.filter { $0.nodePosition?.viaMqtt ?? true == false }
let loraCoords = Array(loraNodes).compactMap({(position) -> CLLocationCoordinate2D in
return position.nodeCoordinate ?? LocationsHandler.DefaultLocation
})
2024-03-29 10:54:30 -07:00
/// Convex Hull
if showConvexHull {
2024-05-24 18:15:53 -07:00
if loraCoords.count > 0 {
let hull = loraCoords.getConvexHull()
MapPolygon(coordinates: hull)
2024-05-24 18:15:53 -07:00
.stroke(.blue, lineWidth: 3)
.foregroundStyle(.indigo.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)
}
2024-03-29 10:54:30 -07:00
/// Lastest Position Pin
if position.latest {
/// Node Annotations
Annotation(position.latest ? node.user?.shortName ?? "?": "", coordinate: position.coordinate) {
LazyVStack {
ZStack {
Two Column Node List (#1425) * Restore BLE State * Log privacy * AccessoryManager to handle restored connection * Comment task out * Switch the node list to a two column layout * Keep asian translations of channel details string * Update restore state function based on conversation with jake * Update Meshtastic/Accessory/Transports/Bluetooth Low Energy/BLETransport.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Meshtastic/Accessory/Transports/Bluetooth Low Energy/BLETransport.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * always show node list search bar * Update auto correct modifier * Dont show online animations for ios 17, remove online animation from node map, remove online circle from position popover * Work in progress. * Update detents * Gate the discovery process while restoring * Use geometry reader to size weather tiles on node details * Update BLE Transport * Update location weather condistion styles * Log privacy in didReceive * Remove extra dividers from admin key config, fix onboarding typo * Bump minimum catalyst target * Bump mac target version * Use @FetchRequest for UserList to try and use less memory on ios 17 * Revert change to @fetchrequest * Stab in the dark for Devices crash * Updated UserList (back?) to @FetchRequest * Set mac minimum to 15 * Nil out continuation after use * Use @FetchRequest for the node list to stop crashes on iOS 17 * Handle failed connections during restoration --------- Co-authored-by: Jake-B <jake-b@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-24 20:55:42 -07:00
if pf.contains(.Heading) {
Image(systemName: pf.contains(.Speed) && position.speed > 1 ? "location.north" : "octagon")
.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)
}
Two Column Node List (#1425) * Restore BLE State * Log privacy * AccessoryManager to handle restored connection * Comment task out * Switch the node list to a two column layout * Keep asian translations of channel details string * Update restore state function based on conversation with jake * Update Meshtastic/Accessory/Transports/Bluetooth Low Energy/BLETransport.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Meshtastic/Accessory/Transports/Bluetooth Low Energy/BLETransport.swift Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * always show node list search bar * Update auto correct modifier * Dont show online animations for ios 17, remove online animation from node map, remove online circle from position popover * Work in progress. * Update detents * Gate the discovery process while restoring * Use geometry reader to size weather tiles on node details * Update BLE Transport * Update location weather condistion styles * Log privacy in didReceive * Remove extra dividers from admin key config, fix onboarding typo * Bump minimum catalyst target * Bump mac target version * Use @FetchRequest for UserList to try and use less memory on ios 17 * Revert change to @fetchrequest * Stab in the dark for Devices crash * Updated UserList (back?) to @FetchRequest * Set mac minimum to 15 * Nil out continuation after use * Use @FetchRequest for the node list to stop crashes on iOS 17 * Handle failed connections during restoration --------- Co-authored-by: Jake-B <jake-b@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-24 20:55:42 -07:00
} 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)
}
}
}
2024-03-29 10:54:30 -07:00
}
}
.tag(position.time)
2024-03-29 10:54:30 -07:00
.annotationTitles(.automatic)
.annotationSubtitles(.automatic)
}
/// Node History
if showNodeHistory {
if position.latest == false && position.nodePosition?.favorite ?? false {
2024-03-29 10:54:30 -07:00
let pf = PositionFlags(rawValue: Int(position.nodePosition?.metadata?.positionFlags ?? 771))
let headingDegrees = Angle.degrees(Double(position.heading))
Annotation("", coordinate: position.coordinate) {
LazyVStack {
if pf.contains(.Heading) {
Image(systemName: "location.north.circle")
.resizable()
.scaledToFit()
.foregroundStyle(Color(UIColor(hex: UInt32(position.nodePosition?.num ?? 0))).isLight() ? .black : .white)
.background(Color(UIColor(hex: UInt32(position.nodePosition?.num ?? 0))))
.clipShape(Circle())
.rotationEffect(headingDegrees)
.frame(width: 16, height: 16)
2024-03-29 10:54:30 -07:00
} else {
Circle()
.fill(Color(UIColor(hex: UInt32(position.nodePosition?.num ?? 0))))
.strokeBorder(Color(UIColor(hex: UInt32(position.nodePosition?.num ?? 0))).isLight() ? .black : .white, lineWidth: 2)
2024-03-29 10:54:30 -07:00
.frame(width: 12, height: 12)
}
}
}
2024-03-29 10:54:30 -07:00
.annotationTitles(.hidden)
.annotationSubtitles(.hidden)
}
}
}
}
@MapContentBuilder
var body: some MapContent {
if node.positions?.count ?? 0 > 0 {
nodeMap
}
}
}