2024-03-14 23:05:05 -07:00
|
|
|
//
|
|
|
|
|
// 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
|
2024-03-14 23:05:05 -07:00
|
|
|
|
|
|
|
|
struct NodeMapContent: MapContent {
|
2024-05-29 16:40:07 -05:00
|
|
|
|
2024-03-14 23:05:05 -07:00
|
|
|
@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
|
2024-03-14 23:05:05 -07:00
|
|
|
@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
|
2024-05-29 16:40:07 -05:00
|
|
|
|
2024-03-14 23:05:05 -07:00
|
|
|
// 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
|
2024-05-29 16:40:07 -05:00
|
|
|
|
2024-03-14 23:05:05 -07:00
|
|
|
@MapContentBuilder
|
|
|
|
|
var nodeMap: some MapContent {
|
|
|
|
|
let positionArray = node.positions?.array as? [PositionEntity] ?? []
|
|
|
|
|
let lineCoords = positionArray.compactMap({(position) -> CLLocationCoordinate2D in
|
|
|
|
|
return position.nodeCoordinate ?? LocationsHandler.DefaultLocation
|
|
|
|
|
})
|
2024-05-29 16:40:07 -05:00
|
|
|
|
2024-03-14 23:05:05 -07:00
|
|
|
/// Node Color from node.num
|
|
|
|
|
let nodeColor = UIColor(hex: UInt32(node.num))
|
2024-05-29 16:40:07 -05:00
|
|
|
|
2024-03-14 23:05:05 -07:00
|
|
|
/// Node Annotations
|
2024-03-29 10:54:30 -07:00
|
|
|
ForEach(node.positions?.array as? [PositionEntity] ?? [], id: \.id) { position in
|
2024-05-29 16:40:07 -05:00
|
|
|
|
2024-03-14 23:05:05 -07:00
|
|
|
let pf = PositionFlags(rawValue: Int(position.nodePosition?.metadata?.positionFlags ?? 771))
|
|
|
|
|
let headingDegrees = Angle.degrees(Double(position.heading))
|
|
|
|
|
/// Reduced Precision Map Circle
|
2025-05-22 11:46:06 -07:00
|
|
|
if position.latest && 12...15 ~= position.precisionBits {
|
2024-03-14 23:05:05 -07:00
|
|
|
let pp = PositionPrecision(rawValue: Int(position.precisionBits))
|
2024-05-29 16:40:07 -05:00
|
|
|
let radius: CLLocationDistance = pp?.precisionMeters ?? 0
|
2024-03-14 23:05:05 -07:00
|
|
|
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
|
2024-03-14 23:05:05 -07:00
|
|
|
if showConvexHull {
|
2024-05-24 18:15:53 -07:00
|
|
|
if loraCoords.count > 0 {
|
|
|
|
|
let hull = loraCoords.getConvexHull()
|
2024-03-14 23:05:05 -07:00
|
|
|
MapPolygon(coordinates: hull)
|
2024-05-24 18:15:53 -07:00
|
|
|
.stroke(.blue, lineWidth: 3)
|
|
|
|
|
.foregroundStyle(.indigo.opacity(0.4))
|
2024-03-14 23:05:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/// Route Lines
|
2024-05-29 16:40:07 -05:00
|
|
|
if showRouteLines {
|
2024-03-14 23:05:05 -07:00
|
|
|
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
|
2024-03-14 23:05:05 -07:00
|
|
|
Annotation(position.latest ? node.user?.shortName ?? "?": "", coordinate: position.coordinate) {
|
|
|
|
|
LazyVStack {
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-03-29 10:54:30 -07:00
|
|
|
}
|
|
|
|
|
}
|
2024-04-01 10:51:06 -07:00
|
|
|
.tag(position.time)
|
2024-03-29 10:54:30 -07:00
|
|
|
.annotationTitles(.automatic)
|
|
|
|
|
.annotationSubtitles(.automatic)
|
|
|
|
|
}
|
|
|
|
|
/// Node History
|
|
|
|
|
if showNodeHistory {
|
2024-04-01 10:51:06 -07:00
|
|
|
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-05-29 16:40:07 -05:00
|
|
|
|
2024-03-29 10:54:30 -07:00
|
|
|
} else {
|
|
|
|
|
Circle()
|
|
|
|
|
.fill(Color(UIColor(hex: UInt32(position.nodePosition?.num ?? 0))))
|
2024-05-29 16:40:07 -05:00
|
|
|
.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-14 23:05:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-03-29 10:54:30 -07:00
|
|
|
.annotationTitles(.hidden)
|
|
|
|
|
.annotationSubtitles(.hidden)
|
2024-03-14 23:05:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-05-29 16:40:07 -05:00
|
|
|
|
2024-03-14 23:05:05 -07:00
|
|
|
@MapContentBuilder
|
|
|
|
|
var body: some MapContent {
|
|
|
|
|
if node.positions?.count ?? 0 > 0 {
|
|
|
|
|
nodeMap
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|