Created
June 3, 2024 14:47
-
-
Save ricobeck/c730cf0a6d7663fce35d8c1f02bba95a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Here's the construction of the view | |
// Coordinate is an abstraction in my case – you can just use CLLocationCoordinate2D from the proxy | |
MapReader { proxy in | |
Map(initialPosition: .rect(store.track.bounds.mapRect(insetBy: 0.1)), scope: mapScope) { | |
// map containing polyline | |
} | |
.onTapGesture(perform: { | |
screenCoord in | |
if let tapLocation = proxy.convert(screenCoord, from: .local) { | |
store.send( | |
.mapTapped( | |
Coordinate( | |
latitude: tapLocation.latitude, | |
longitude: tapLocation.longitude | |
) | |
) | |
) | |
} | |
}) | |
} | |
// GameKit's R-tree can only store NSObjects so I need a wrapper around my struct. For convenience it contains the index in the dataset to get back to the original quickly. | |
class TrackLocation: NSObject { | |
let index: Int | |
let latitude: Double | |
let longitude: Double | |
let distance: Double | |
let boundingBoxSize: Float | |
init(index: Int, latitude: Double, longitude: Double, distance: Double, boundingBoxSize: Float = 0.0005) { | |
self.index = index | |
self.latitude = latitude | |
self.longitude = longitude | |
self.distance = distance | |
self.boundingBoxSize = boundingBoxSize | |
} | |
var boundingBoxMin: vector_float2 { | |
vector_float2(Float(latitude) - boundingBoxSize, Float(longitude) - boundingBoxSize) | |
} | |
var boundingBoxMax: vector_float2 { | |
vector_float2(Float(latitude) + boundingBoxSize, Float(longitude) + boundingBoxSize) | |
} | |
} | |
// During initialisation I convert my locations into objects and add them to the tree | |
for (index, trackPoint) in state.track.trackPoints.enumerated() { | |
let location = TrackLocation( | |
index: index, | |
latitude: trackPoint.coordinate.latitude, | |
longitude: trackPoint.coordinate.longitude, | |
distance: state.track.graph.heightMap[index].distance | |
) | |
state.tree.addElement( | |
location, | |
boundingRectMin: location.boundingBoxMin, | |
boundingRectMax: location.boundingBoxMax, | |
splitStrategy: .linear | |
) | |
} | |
// Here's how I conververt to location into a result set. Because elements matching the bounding box are returned in the order they were added I compute the nearest location from the results and return the closest. | |
extension MapDetail.State { | |
func data(for coordinate: Coordinate) -> (index: Int, distance: Double)? { | |
let mapRectSize: Float = 0.001 | |
let rectMin = vector_float2( | |
Float(coordinate.latitude) - mapRectSize, | |
Float(coordinate.longitude) - mapRectSize | |
) | |
let rectMax = vector_float2( | |
Float(coordinate.latitude) + mapRectSize, | |
Float(coordinate.longitude) + mapRectSize | |
) | |
let results = tree.elements(inBoundingRectMin: rectMin, rectMax: rectMax) | |
struct Item { | |
var index: Int | |
var distance: Double | |
var coordinateDistance: Double | |
} | |
let convertedResults = results.map({ | |
Item( | |
index: $0.index, | |
distance: $0.distance, | |
coordinateDistance: Coordinate( | |
latitude: $0.latitude, | |
longitude: $0.longitude | |
) | |
.distance( | |
to: coordinate | |
) | |
) | |
}).sorted(by: \.distance) | |
if let result = convertedResults.first { | |
return (result.index, result.distance) | |
} else { | |
return nil | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment