[Swift] 두 좌표 사이를 잇는 선에서 나와 가까운 가상의 좌표 찾기

출처: https://stackoverflow.com/a/28028023/1025379

두 좌표를 잇는 직선에서 나와 가장 가까운 좌표를 찾아야 한다. CoreLocation이나 MapKit에서는 따로 지원하지는 않는 것 같다.

출처의 코드를 플레이 그라운드에서 테스트 해봤다.

import MapKit
import PlaygroundSupport

func makeLocation(lat: Double, lon: Double) -> CLLocationCoordinate2D {
  return CLLocationCoordinate2DMake(lat, lon)
}

func nearestPointToPoint(_ origin: CGPoint, _ pointA: CGPoint, _ pointB: CGPoint) -> (CGPoint, Double) {
  let dAP = CGPoint(x: origin.x - pointA.x, y: origin.y - pointA.y)
  let dAB = CGPoint(x: pointB.x - pointA.x, y: pointB.y - pointA.y)
  let dot = dAP.x * dAB.x + dAP.y * dAB.y
  let squareLength = dAB.x * dAB.x + dAB.y * dAB.y
  let param = dot / squareLength

  // 경도 180도 근처에서 오류 발생
//  var nearestPoint = CGPoint()
//  if param < 0 || (pointA.x == pointB.x && pointA.y == pointB.y) {
//    nearestPoint.x = pointA.x
//    nearestPoint.y = pointA.y
//  } else if param > 1 {
//    nearestPoint.x = pointB.x
//    nearestPoint.y = pointB.y
//  } else {
//    nearestPoint.x = pointA.x + param * dAB.x
//    nearestPoint.y = pointA.y + param * dAB.y
//  }
  let nearestPoint = CGPoint(x: pointA.x + param * dAB.x, y: pointA.y + param * dAB.y)

  let dx = origin.x - nearestPoint.x
  let dy = origin.y - nearestPoint.y
  let distance = sqrtf(Float(dx * dx + dy * dy))
  
  return (nearestPoint, Double(distance))
}

extension CLLocationCoordinate2D {
  func toCGPoint() -> CGPoint {
    return CGPoint(x: longitude, y: latitude)
  }
}

// Now let's create a MKMapView
let mapView = MKMapView(frame: CGRect(x: 0, y: 0, width: 800, height: 800))

let location1 = makeLocation(lat: 52, lon: -2)
let myLocation = makeLocation(lat: 53, lon: -1)
let location2 = makeLocation(lat: 52, lon: 0)
let locations = [location1, location2]

// Define a region for our map view
var mapRegion = MKCoordinateRegion()
let mapRegionSpan = 5.0
mapRegion.center = myLocation
mapRegion.span.latitudeDelta = mapRegionSpan
mapRegion.span.longitudeDelta = mapRegionSpan
mapView.setRegion(mapRegion, animated: true)

locations.map {
  let pin = MKPointAnnotation()
  pin.title = "(\($0.latitude), \($0.longitude))"
  pin.coordinate = $0
  return pin
}
.map { mapView.addAnnotation($0) }

let me = MKPointAnnotation()
me.coordinate = myLocation
me.title = "me(\(myLocation.latitude), \(myLocation.longitude))"
mapView.addAnnotation(me)

let (near, distance) = nearestPointToPoint(me.coordinate.toCGPoint(), location1.toCGPoint(), location2.toCGPoint())

let nearLocation = makeLocation(lat: Double(near.y), lon: Double(near.x))
print("near: \(nearLocation), distance: \(distance)")
let nearPoint = MKPointAnnotation()
nearPoint.title = "near (\(nearLocation.latitude),\(nearLocation.longitude))"
nearPoint.coordinate = nearLocation
mapView.addAnnotation(nearPoint)

// Add the created mapView to our Playground Live View
PlaygroundPage.current.liveView = mapView

여기까지는 정상적으로 동작하는데, 문제는 경도 180도 근처에서 발생한다. 이건 모르겠다. 보니까 경도 180 근처에서는 나라도 없어서 그냥 써도 괜찮을 것 같다.

나: (0, -179), A(0, 179), B(1, -179) 일 때 결과는 (0,999, -178.99)로 나온다.