4

ラインを作成するためのポイントと、パス、ポリライン、またはポイントのセットがあります。

別の切断されたポイントに最も近いパス上のポイントを見つけるにはどうすればよいですか?

パス/ポリラインを WPF ジオメトリ コントロールとして保存または転送するのは簡単ですが、これらのコントロールにはGetDistanceFromタイプ メソッドが付属していますか? この目標を達成する簡単な方法はありますか?

4

2 に答える 2

3

次のメソッド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 ...
    }
}
于 2014-06-12T19:00:42.700 に答える