Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
812 views
in Technique[技术] by (71.8m points)

wpf - Get point on a path or polyline which is closest to a disconnected point

I have a point and a path, polyline, or set of points to create lines.

How can I find the point on my path which closest to another disconnected point?

It's easy to store or transfer my path / polyline as any of the WPF geometry controls, but do any of these controls come with GetDistanceFrom type methods? Is there any simply way to achieve this goal?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

The following method GetClosestPointOnPath() is a generalization of @KirkBroadhurst's GetClosestPointOnLine() method, i.e. it works with any path geometry, i.e. lines, curves, ellipses, etc.

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

Here is a small sample program that demonstrates how to use this method:

screen shot

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...