florianreinhart / Geodesy

Geodesy functions in Swift 🌎
MIT License
23 stars 2 forks source link

Calculation of perpendicular point of a line (p1, p2) and a point (p3) near the line #2

Open phranck opened 5 years ago

phranck commented 5 years ago

Hey @florianreinhart!

First: Thank you very much for this great collection of useful geodesy functions!

This isn't a real issue but more a question. I'm currently struggling on calculating the perpendicular point of a line (p1, p2) and a point (p3) near the line.

Background: We are using MKPolyLine to draw lines on the map. The user should be able to tap (p3) on (or near to) a line segment (p1, p2). Now I need the closest point in the line (the perpendicular point) to the touch point, to show a popup with detailed informations at this line point. It should look like this:

screen shot 2018-10-03 at 17 42 24

Now I'm asking myself if this is possible with your functions? Maybe I'm missing something, unfortunately I don't find a solution...

Question: Is this calculation possible with your library or is an entire new function needed to get the result?

Thanks in advance!

florianreinhart commented 5 years ago

Hi @phranck,

This should be possible with func alongTrackDistance(toPath path: (start: Coordinate, end: Coordinate)) -> Distance.

However, This function returns the distance from start on your path. You could then calculate the initial bearing for your path and travel the calculated distance with this bearing from your start point to get a point on your path. Some sample code:

let p1, p2, p3: Coordinate

let distance = p3.alongTrackDistance(toPath: (start: p1, end: p2))
let bearing = p1.bearing(to: p2)
let pointOnPath = destination(with: distance, bearing: bearing)
phranck commented 5 years ago

@florianreinhart Yeah, that was my initial thought, too. However, this is more an approximation method than a direct calculation. (-;

florianreinhart commented 5 years ago

It should be quite accurate on a sphere (which is the model that is used for the calculation).

You could try doing the coordinate calculation with MKMapPoints and calculate the "Lotfußpunkt".

phranck commented 5 years ago

Yeah, using MKMapPoint's was my first attempt. Unfortunately, MapKit fails when it comes to calculation around the anti meridian. It can't deal with that and provides one with weird results! Anyways, I think I'll go with the approximation method. (-;

phranck commented 5 years ago

should be quite accurate on a sphere

This is key! So, while handling with MKMapPoint's which represents points on a flattened earth with parallel longitudes I think I can't use your functions, can I? 🤔

florianreinhart commented 5 years ago

Oh yeah! You need to convert your MKMapPoints to CLLocationCoordinate2D. The Coordinate type of this library is equivalent to CLLocationCoordinate2D.

phranck commented 5 years ago

Just to illustrate - This is the code I use for getting the perpendicular point. As mentioned, it works very well, but for lines crossing -180/180.

    private func closestPointInLine(withStartPoint p1: MKMapPoint, endPoint p2: MKMapPoint, touchPoint pt: MKMapPoint, allowedPerimeter: Double) -> MKMapPoint? {
        let lineLength = distance(from: p1, to: p2)

        var u = ((pt.x - p1.x) * (p2.x - p1.x)) + ((pt.y - p1.y) * (p2.y - p1.y))
        u = u / pow(lineLength, 2)

        if u >= 0 && u <= 1 {
            let px = p1.x + u * (p2.x - p1.x)
            let py = p1.y + u * (p2.y - p1.y)

            let p = MKMapPoint(x: px, y: py)
            let touchDistance = distance(from: pt, to: p)
            if touchDistance <= allowedPerimeter {
                return p
            }
        }

        return nil
    }

    private func distance(from p1: MKMapPoint, to p2: MKMapPoint) -> Double {
        return sqrt(pow(p2.x - p1.x, 2) + pow(p2.y - p1.y, 2))
    }