1

I am building a web app (in Qwik) that requires parsing a .xctsk file (paragliding competition tasks), and drawing the task on a map. A task consists of a bunch of circles in a given order.

I have successfully implemented the file parsing, and drawing the circles, and I now need to draw the shortest path that links every circle in the given order. It should end up looking like this:

I can easily connect the circles' centers with polylines, like the screenshot below, but I have no clue on how to find the points that give the shortest path. Any idea would be very much welcome!

Here is my code:

        task?.turnpoints.forEach((tp, index) => {
          circle([tp.waypoint.lat, tp.waypoint.lon], {
            radius: tp.radius,
            color: "orange",
          }).addTo(mapContainer$.value!);

          if (index < task.turnpoints.length - 1) {
            polyline(
              [
                [
                  task.turnpoints[index].waypoint.lat,
                  task.turnpoints[index].waypoint.lon,
                ],
                [
                  task.turnpoints[index + 1].waypoint.lat,
                  task.turnpoints[index + 1].waypoint.lon,
                ],
              ],
              { color: "black" },
            ).addTo(mapContainer$.value!);
          }
        });

I tried looking for information on other forums, as well as math formulas, but did not find any clue.

3
  • Could you provide a Minimal, Reproducible Example? stackoverflow.com/help/minimal-reproducible-example
    – Thomas
    Commented Jun 10 at 18:09
  • Do read en.wikipedia.org/wiki/Travelling_salesman_problem . Commented Jun 10 at 18:11
  • 2
    Also note that real distances between two points on Earth are connected by great circle segments. Drawing straight lines on Mercator map projection is going to both show the wrong path, and the wrong distances, because an east-west line might look shorter than a north-south line while actually being longer. You can't use polygons to represent "shortest distances" when working with map projections. (Consequently, you also can't draw circles around some coordinate to represent "all points at x km from ...". Those would be pear shaped ellipsoids instead) Commented Jun 10 at 18:32

2 Answers 2

0

Since it seems that you want to reach the closest edge of the circle instead of the center, something like this might work:

(note that if radius isn't in degrees - same as lat and lon - then some additional conversion is needed)

This will give you a shorter path, but not the shortest. If you want the true shortest path, then you might consider asking this on the mathematics stack exchange.

This also may not handle traversing from an inner circle to an outer circle too well (since once you're in the inner circle, you should just skip the waypoint to the outer circle as you're already inside it - though since this problem is declared as 'in given order' - that shouldn't be a problem? since it would violate the order) There would need to be additional checks implemented for that - which someone might be happy to help with if you were to post a working example (snippet, fiddle, etc)

This also doesn't take Mike's comment into account about the earth being a sphere and hence a curved surface.

    actualPoints = [{
      x: task.turnpoints[0].waypoint.lat,
      y: task.turnpoints[0].waypoint.lon,
    }];

    task?.turnpoints.forEach((tp, index) => {
        circle([tp.waypoint.lat, tp.waypoint.lon], {
          radius: tp.radius,
          color: "orange",
        }).addTo(mapContainer$.value!);

        if (index < task.turnpoints.length - 1) {

          const pointA = actualPoints[0]

          const pointB = {
            x: task.turnpoints[index + 1].waypoint.lat,
            y: task.turnpoints[index + 1].waypoint.lon,
          }

          const vectorDistance = {
            x: pointA.x - pointB.x,
            y: pointA.y - pointB.y,
          }

          const angle = Math.atan2(vectorDistance.x, vectorDistance.y)

          const normalExtended = {
            x: Math.cos(angle) * tp.radius,
            y: Math.sin(angle) * tp.radius,
          }

          const newPoint = {
            x: normalExtended.x + pointB.x,
            y: normalExtended.y + pointB.y,
          }

          actualPoints[index + 1] = newPoint

          polyline(
            [
              [
                pointA.x,
                pointA.y
              ],
              [
                newPoint.x,
                newPoint.y
              ],
            ],
            { color: "black" },
          ).addTo(mapContainer$.value!);
        }
      });
0

Interesting math problem here. The shortest path has the property that it bounces off the circles like a ball. More mathematically,the normal of the circle at the touching point should be the bisector between the incoming and outgoing path. This makes a very simple algorithm: intersect the bisector of each angle in your inital path connecting the centers (in black in your second image) with the corresponding circle and choose this point as the new corner. It will not be optimal as the new path will not have exactly the bisector property, but quite close. You can enhance by iterating: computing the bisector of the new path, and placing the point where the circle normal has the same direction. To be optimal you also have to check if a circle is intersected by the segment from the previous corner to the next. In this case you can simply remove the corner relative to this circle as after removing the circle will still be touched (intersected).

Edit: I've put a python code that solves the problem here: shortest_path_circle.py Feel free to to use it. Here is also an illustration of the optimization iterations

3
  • 1
    Hi, welcome. Please read how to answer. Try to put some working solution , with appropriate code
    – pierpy
    Commented Jun 27 at 9:41
  • Hi, thanks for the answer. The bisector with iterations is where I'm at too. I'm kinda baffled that there is no trivial optimal solution to such a simple problem though.
    – jules sang
    Commented Jun 27 at 16:11
  • Yes I could only find an iterative solution, but it's really fast, usually converges in a few iterations. The shortest path between two points A and B passing through a line L has an elegant solution: trace the symmetric B' of B across L and join A and B'. It intersects L at the optimal touching point. This can be iterated for multiple lines. But I did not find an equivalent for circles. Commented Jun 30 at 17:10

Not the answer you're looking for? Browse other questions tagged or ask your own question.