4

我有一个点和一条路径、多段线或一组点来创建线。

如何在我的路径上找到最接近另一个断开连接点的点?

将我的路径/折线作为任何 WPF 几何控件存储或传输很容易,但是这些控件中的任何一个都带有GetDistanceFrom类型方法吗?有没有简单的方法来实现这个目标?

4

2 回答 2

3

以下方法GetClosestPointOnPath()是@KirkBroadhurstGetClosestPointOnLine()方法的推广,即它适用于任何路径几何,即直线、曲线、椭圆等。

public Point GetClosestPointOnPath(Point p, Geometry geometry)
{
    PathGeometry pathGeometry = geometry.GetFlattenedPathGeometry();

    var points = pathGeometry.Figures.Select(f => GetClosestPointOnPathFigure(f, p))
        .OrderBy(t => t.Item2).FirstOrDefault();
    return (points == null) ? new Point(0, 0) : points.Item1;
}

private Tuple<Point, double> GetClosestPointOnPathFigure(PathFigure figure, Point p)
{
    List<Tuple<Point, double>> closePoints = new List<Tuple<Point,double>>();
    Point current = figure.StartPoint;
    foreach (PathSegment s in figure.Segments)
    {
        PolyLineSegment segment = s as PolyLineSegment;
        LineSegment line = s as LineSegment;
        Point[] points;
        if (segment != null)
        {
            points = segment.Points.ToArray();
        }
        else if (line != null)
        {
            points = new[] { line.Point };
        }
        else
        {
            throw new InvalidOperationException("Unexpected segment type");
        }
        foreach (Point next in points)
        {
            Point closestPoint = GetClosestPointOnLine(current, next, p);
            double d = (closestPoint - p).LengthSquared;
            closePoints.Add(new Tuple<Point, double>(closestPoint, d));
            current = next;
        }
    }
    return closePoints.OrderBy(t => t.Item2).First();
}

private Point GetClosestPointOnLine(Point start, Point end, Point p)
{
    double length = (start - end).LengthSquared;
    if (length == 0.0)
    {
        return start;
    }
    Vector v = end - start;
    double param = (p - start) * v / length;
    return (param < 0.0) ? start : (param > 1.0) ? end : (start + param * v);
}

这是一个演示如何使用此方法的小示例程序:

截屏

MainWindow.xaml:

<Window x:Class="PathHitTestSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Canvas x:Name="canvas">
        <TextBlock Text="Left-click into this window" Margin="10" Foreground="Gray"/>
        <Path x:Name="path"
              Data="M96,63 C128,122 187,133 275,95 L271,158 C301,224 268,240 187,218 L74,218 95,270 384,268 C345,148 376,106 456,120 494,64 314,60 406,4 A10,10 30 0 1 300,20"
              Stroke="Black" StrokeThickness="1"
              HorizontalAlignment="Left" VerticalAlignment="Top"/>
        <Rectangle x:Name="marker" Fill="Red" Canvas.Left="0" Canvas.Top="0" Width="10" Height="10" Margin="-5,-5,0,0"
                   Visibility="Hidden"/>
    </Canvas>
</Window>

MainWindow.xaml.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;

namespace PathHitTestSample
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            Point p = e.GetPosition(canvas);
            Point pointOnPath = GetClosestPointOnPath(p, path.Data);
            marker.Visibility = Visibility.Visible;
            Canvas.SetLeft(marker, pointOnPath.X);
            Canvas.SetTop(marker, pointOnPath.Y);
        }

        ... add above methods here ...
    }
}
于 2014-06-12T19:00:42.700 回答
2

这是我作为解决方案实施的算法。如果您花了十多分钟的时间思考它,那么这里没有什么“不明显”的。

我将参考您可以在此处找到的距离算法:https ://stackoverflow.com/a/1501725/146077

  1. 将折线收集为一组有序线
  2. 遍历这个集合,测试目标点到每条线的距离
  3. 确定最近的线后,运行以下命令以识别线上的最近点。

上面链接的答案使用投影来测试该点是否比任何其他点最接近区间的任一端。我已经修改了该答案中的函数以返回该点在该投影上的位置。请注意,如果您没有阅读链接的答案,这个答案可能没有任何意义!

private Point GetClosestPointOnLine(Point start, Point end, Point p)
{
    var length = (start - end).LengthSquared;

    if (length == 0.0)
        return start; 

    // Consider the line extending the segment, parameterized as v + t (w - v).
    // We find projection of point p onto the line. 
    // It falls where t = [(p-v) . (w-v)] / |w-v|^2
    var t = (p - start) * (end - start) / length;

    if (t < 0.0)
        return start; // Beyond the 'v' end of the segment
    else if (t > 1.0)
        return end;   // Beyond the 'w' end of the segment

    // Projection falls on the segment
    var projection = start + t * (end - start);  
    return projection;
}
于 2013-09-26T15:00:23.083 回答