Skip to content

Instantly share code, notes, and snippets.

@ricobeck
Created June 3, 2024 14:47
Show Gist options
  • Save ricobeck/c730cf0a6d7663fce35d8c1f02bba95a to your computer and use it in GitHub Desktop.
Save ricobeck/c730cf0a6d7663fce35d8c1f02bba95a to your computer and use it in GitHub Desktop.
// 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