mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Mesh Map: fuzz imprecise locations so they're distinguishable and clickable at the highest zoom levels (#1478)
Co-authored-by: Garth Vander Houwen <garth@meshtastic.com>
This commit is contained in:
parent
1d49e022be
commit
6c3c02237b
2 changed files with 53 additions and 0 deletions
|
|
@ -96,10 +96,40 @@ extension PositionEntity {
|
|||
}
|
||||
return pointAnn
|
||||
}
|
||||
|
||||
var isPreciseLocation: Bool {
|
||||
precisionBits == 32 || precisionBits == 0
|
||||
}
|
||||
|
||||
var fuzzedNodeCoordinate: CLLocationCoordinate2D? {
|
||||
// With reduced precisionBits, many nodes can overlap on the map, making them unclickable.
|
||||
// Use a hash of the position ID to fuzz coordinate slightly so that these nodes can be distinguished at the higest zoom levels. This allows them to be clicked individually.
|
||||
if latitudeI != 0 && longitudeI != 0 {
|
||||
// Derive two uniform pseudorandom numbers [0,1) from id.hashValue
|
||||
let u1 = Double(id.hashValue & 0xFFFF) / 65536.0
|
||||
let u2 = Double((id.hashValue >> 16) & 0xFFFF) / 65536.0
|
||||
|
||||
// Angle and radius
|
||||
let offsetAngle = 2.0 * .pi * u1
|
||||
let offsetRadius = 0.00001 * sqrt(u2) // 1.0e-5 degrees at equator is about 1.11 m or 4 ft
|
||||
|
||||
let dLat = sin(offsetAngle) * offsetRadius
|
||||
let dLon = cos(offsetAngle) * offsetRadius
|
||||
|
||||
let coord = CLLocationCoordinate2D(
|
||||
latitude: latitude! + dLat,
|
||||
longitude: longitude! + dLon
|
||||
)
|
||||
return coord
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PositionEntity: MKAnnotation {
|
||||
public var coordinate: CLLocationCoordinate2D { nodeCoordinate ?? LocationsHandler.DefaultLocation }
|
||||
public var fuzzedCoordinate: CLLocationCoordinate2D { fuzzedNodeCoordinate ?? LocationsHandler.DefaultLocation }
|
||||
public var title: String? { nodePosition?.user?.shortName ?? "Unknown".localized }
|
||||
public var subtitle: String? { time?.formatted() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,29 @@ struct MeshMapContent: MapContent {
|
|||
ForEach(positions, id: \.id) { position in
|
||||
/// Apply favorits filter and don't show ignored nodes
|
||||
if (!showFavorites || (position.nodePosition?.favorite == true)) && !(position.nodePosition?.ignored == true) {
|
||||
|
||||
let nodeColor = UIColor(hex: UInt32(position.nodePosition?.num ?? 0))
|
||||
let positionName = position.nodePosition?.user?.longName ?? "?"
|
||||
// Use a hash of the position ID to stagger animation delays for each node, preventing synchronized animations and improving visual distinction.
|
||||
let calculatedDelay = Double(position.id.hashValue % 100) / 100.0 * 0.5
|
||||
|
||||
let coordinateForNodePin: CLLocationCoordinate2D = if position.isPreciseLocation {
|
||||
// Precise location: place node pin at actual location.
|
||||
position.coordinate
|
||||
} else {
|
||||
// Imprecise location: fuzz slightly so overlapping nodes are visible and clickable at highest zoom levels.
|
||||
position.fuzzedCoordinate
|
||||
}
|
||||
|
||||
Annotation(positionName, coordinate: coordinateForNodePin) {
|
||||
LazyVStack {
|
||||
AnimatedNodePin(
|
||||
nodeColor: nodeColor,
|
||||
shortName: position.nodePosition?.user?.shortName,
|
||||
hasDetectionSensorMetrics: position.nodePosition?.hasDetectionSensorMetrics ?? false,
|
||||
isOnline: position.nodePosition?.isOnline ?? false,
|
||||
calculatedDelay: calculatedDelay
|
||||
)
|
||||
if 12...15 ~= position.precisionBits || position.precisionBits == 32 {
|
||||
|
||||
let nodeColor = UIColor(hex: UInt32(position.nodePosition?.num ?? 0))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue