Wpf 获取距离断开点最近的路径或多段线上的点

Wpf 获取距离断开点最近的路径或多段线上的点,wpf,wpf-controls,geometry,line,polyline,Wpf,Wpf Controls,Geometry,Line,Polyline,我有一个点和一条路径、多段线或一组点来创建直线 如何在路径上找到距离另一个断开连接点最近的点 作为任何WPF几何体控件,存储或传输路径/多段线都很容易,但是这些控件中是否有任何一个带有GetDistanceFromtype方法?有什么简单的方法可以实现这个目标吗?这是我作为解决方案实现的算法。如果你花了超过十分钟的时间思考,这里没有什么“不明显的” 我将参考您可以在此处找到的距离算法: 将多段线收集为一组有序线 遍历此集合,测试从目标点到每条线的距离 确定最近的直线后,运行下面的命令以确定直线上

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

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


作为任何WPF几何体控件,存储或传输路径/多段线都很容易,但是这些控件中是否有任何一个带有
GetDistanceFrom
type方法?有什么简单的方法可以实现这个目标吗?

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

我将参考您可以在此处找到的距离算法:

  • 将多段线收集为一组有序线
  • 遍历此集合,测试从目标点到每条线的距离
  • 确定最近的直线后,运行下面的命令以确定直线上最近的点
  • 上面链接的答案使用投影来测试该点是否比任何其他点更靠近间隔的两端。我已经根据这个答案修改了函数,以返回点在投影上的位置。注意,如果你没有阅读链接的答案,这个答案可能没有任何意义

    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;
    }
    
    专用点GetClosestPointOnLine(点开始、点结束、点p)
    {
    变量长度=(开始-结束).LengthSquared;
    如果(长度==0.0)
    返回启动;
    /考虑扩展该段的行,参数化为V+T(W-V)。
    //我们找到点p在直线上的投影。
    //它落在t=[(p-v)。(w-v)]/| w-v |^2处
    var t=(p-开始)*(结束-开始)/长度;
    如果(t<0.0)
    return start;//超出段的“v”结尾
    否则如果(t>1.0)
    return end;//超出段的“w”端
    //投影落在片段上
    var投影=开始+t*(结束-开始);
    回归投影;
    }
    
    以下方法
    GetClosestPointOnPath()
    是@KirkBroadhurst的
    GetClosestPointOnLine()
    方法的推广,即它适用于任何路径几何体,即直线、曲线、椭圆等

    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);
    }
    

    谢谢你详尽的回答。我在为路径和直线使用一组代码时遇到了困难,尤其是将多段线的简单直线集合转换为更复杂的几何图形/路径格式时。我运行了您的代码,它工作得非常出色。但是,当使用边距定位点时,我无法使其工作。如果可以的话,请帮助我。我仔细思考了这个问题,它让我想起了
    PathGeometry
    class:。最奇怪的是我真的需要用它。。。
    <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>
    
    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 ...
        }
    }