【问题标题】:Get point on a path or polyline which is closest to a disconnected point获取最接近断开点的路径或多段线上的点
【发布时间】:2014-06-13 03:00:42
【问题描述】:

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

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

很容易将我的路径/多段线存储或传输为任何 WPF 几何控件,但是这些控件是否带有 GetDistanceFrom 类型的方法?有没有什么简单的方法可以实现这个目标?

【问题讨论】:

  • 我偶然发现了这个问题,它让我想起了PathGeometry 类的这种奇怪方法:GetPointAtFractionLength()。最奇怪的是我居然需要用它……

标签: wpf wpf-controls geometry line polyline


【解决方案1】:

以下方法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);
}

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

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 ...
    }
}

【讨论】:

  • 感谢您的详尽回答。我在为路径和线使用一组代码时遇到了困难,尤其是将折线的简单线集合转换为更复杂的几何/路径格式。
  • 我运行了您的代码,它运行良好。但是,当使用边距定位点时,我无法让它工作。如果可以的话,请帮助我stackoverflow.com/questions/32221174/…
【解决方案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;
}

【讨论】:

    猜你喜欢
    • 2016-04-04
    • 2018-05-08
    • 1970-01-01
    • 2015-03-17
    • 2015-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-20
    相关资源
    最近更新 更多