沿 A-B 线计算距 A 给定距离的点 [英] Calculate a point along the line A-B at a given distance from A

查看:19
本文介绍了沿 A-B 线计算距 A 给定距离的点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我非常疯狂地试图计算沿给定线 A-B 的点,距离 A 的给定距离,这样我就可以绘制"两个给定点之间的线.一开始听起来很简单,但我似乎无法正确理解.更糟糕的是,我不明白我哪里出错了.几何(和一般的数学)不是我的强项.

I'm going quite mad trying to calculate the point along the given line A-B, at a given distance from A, so that I can "draw" the line between two given points. It sounded simple enough at the outset, but I can't seem to get it right. Worse still, I don't understand where I've gone wrong. Geometry (and math in general) is NOT my strong suite.

我已经阅读了类似的问题,并且在 SO 上有答案.事实上,我直接从 Mads Elvheim 的回答中提升了当前对 CalculatePoint 函数的实现:给定起点和终点以及距离,计算沿线的点(加上修正稍后发表评论 - 如果我理解正确的话)因为我独立尝试解决问题的尝试让我无处可去,除了头等舱特快票令人沮丧的地方.

I have read similar questions and there answers on SO. In fact I lifted my current implementation of CalculatePoint function directly from Mads Elvheim's answer to: Given a start and end point, and a distance, calculate a point along a line (plus a correction in a later comment - if I understand him correctly) because my indepedent attempts to solve the problem were getting me nowhere, except a first class express ticket frusterpationland.

这是我的更新代码(请参阅帖子底部的编辑注释):

Here's my UPDATED code (please see the EDIT notes a bottom of post):

using System;
using System.Drawing;
using System.Windows.Forms;

namespace DrawLines
{
    public class MainForm : Form
    {
        // =====================================================================
        // Here's the part I'm having trouble with. I don't really understand
        // how this is suposed to work, so I can't seem to get it right!
        // ---------------------------------------------------------------------

        // A "local indirector" - Just so I don't have go down and edit the 
        // actual call everytime this bluddy thing changes names.
        private Point CalculatePoint(Point a, Point b, int distance) {
            return CalculatePoint_ByAgentFire(a, b, distance);
        }

        #region CalculatePoint_ByAgentFire
        //AgentFire: Better approach (you can rename the struct if you need):
        struct Vector2
        {
            public readonly double X;
            public readonly double Y;
            public Vector2(double x, double y) {
                this.X = x;
                this.Y = y;
            }
            public static Vector2 operator -(Vector2 a, Vector2 b) {
                return new Vector2(b.X - a.X, b.Y - a.Y);
            }
            public static Vector2 operator *(Vector2 a, double d) {
                return new Vector2(a.X * d, a.Y * d);
            }
            public override string ToString() {
                return string.Format("[{0}, {1}]", X, Y);
            }
        }
        // For getting the midpoint you just need to do the (a - b) * d action:
        //static void Main(string[] args)
        //{
        //    Vector2 a = new Vector2(1, 1);
        //    Vector2 b = new Vector2(3, 1);
        //    float distance = 0.5f; // From 0.0 to 1.0.
        //    Vector2 c = (a - b) * distance;
        //    Console.WriteLine(c);
        //}
        private Point CalculatePoint_ByAgentFire(Point a, Point b, int distance) {
            var vA = new Vector2(a.X, a.Y);
            var vB = new Vector2(b.X, b.Y);
            double lengthOfHypotenuse = LengthOfHypotenuseAsDouble(a,b);
            double portionOfDistanceFromAtoB = distance / lengthOfHypotenuse;
            var vC = (vA - vB) * portionOfDistanceFromAtoB;
            Console.WriteLine("vC="+vC);
            return new Point((int)(vC.X+0.5), (int)(vC.Y+0.5));
        }
        // Returns the length of the hypotenuse rounded to an integer, using
        // Pythagoras' Theorem for right angle triangles: The length of the
        // hypotenuse equals the sum of the square of the other two sides.
        // Ergo: h = Sqrt(a*a + b*b)
        private double LengthOfHypotenuseAsDouble(Point a, Point b) {
            double aSq = Math.Pow(Math.Abs(a.X - b.X), 2); // horizontal length squared
            double bSq = Math.Pow(Math.Abs(b.Y - b.Y), 2); // vertical length  squared
            return Math.Sqrt(aSq + bSq); // length of the hypotenuse
        }

        #endregion

        //dbaseman: I thought something looked strange about the formula ... the question 
        //you linked was how to get the point at a distance after B, whereas you want the
        //distance after A. This should give you the right answer, the start point plus 
        //distance in the vector direction.
        //
        // Didn't work as per: http://s1264.photobucket.com/albums/jj496/corlettk/?action=view&current=DrawLinesAB-broken_zps069161e9.jpg
        //
        private Point CalculatePoint_ByDbaseman(Point a, Point b, int distance) {
            // a. calculate the vector from a to b:
            double vectorX = b.X - a.X;
            double vectorY = b.Y - a.Y;
            // b. calculate the length:
            double magnitude = Math.Sqrt(vectorX * vectorX + vectorY * vectorY);
            // c. normalize the vector to unit length:
            vectorX /= magnitude;
            vectorY /= magnitude;
            // d. calculate and Draw the new vector, which is x1y1 + vxvy * (mag + distance).
            return new Point(
                (int)((double)a.X + vectorX * distance)     // x = col
              , (int)((double)a.Y + vectorY * distance)     // y = row
            );
        }

        // MBo: Try to remove 'magnitude' term in the parentheses both for X and for Y expressions.
        //
        // Didn't work as per: http://s1264.photobucket.com/albums/jj496/corlettk/?action=view&current=DrawLinesAB-broken_zps069161e9.jpg
        //
        //private Point CalculatePoint_ByMBo(Point a, Point b, int distance) {
        //    // a. calculate the vector from a to b:
        //    double vectorX = b.X - a.X;
        //    double vectorY = b.Y - a.Y;
        //    // b. calculate the length:
        //    double magnitude = Math.Sqrt(vectorX * vectorX + vectorY * vectorY);
        //    // c. normalize the vector to unit length:
        //    vectorX /= magnitude;
        //    vectorY /= magnitude;
        //    // d. calculate and Draw the new vector, which is x1y1 + vxvy * (mag + distance).
        //    return new Point(
        //        (int)(  ((double)a.X + vectorX * distance)  +  0.5  )
        //      , (int)(  ((double)a.X + vectorX * distance)  +  0.5  )
        //    );
        //}

        // Didn't work
        //private Point CalculatePoint_ByUser1556110(Point a, Point b, int distance) {
        //    Double magnitude = Math.Sqrt(Math.Pow(b.Y - a.Y, 2) + Math.Pow(b.X - a.X, 2));
        //    return new Point(
        //        (int)(a.X + distance * (b.X - a.X) / magnitude + 0.5)
        //      , (int)(a.Y + distance * (b.Y - a.Y) / magnitude + 0.5)
        //    );
        //}

        // didn't work
        //private static Point CalculatePoint_ByCadairIdris(Point a, Point b, int distance) {
        //    // a. calculate the vector from a to b:
        //    double vectorX = b.X - a.X;
        //    double vectorY = b.Y - a.Y;
        //    // b. calculate the proportion of hypotenuse
        //    double factor = distance / Math.Sqrt(vectorX*vectorX + vectorY*vectorY);
        //    // c. factor the lengths
        //    vectorX *= factor;
        //    vectorY *= factor;
        //    // d. calculate and Draw the new vector,
        //    return new Point((int)(a.X + vectorX), (int)(a.Y + vectorY));
        //}

        // Returns a point along the line A-B at the given distance from A
        // based on Mads Elvheim's answer to:
        // https://stackoverflow.com/questions/1800138/given-a-start-and-end-point-and-a-distance-calculate-a-point-along-a-line
        private Point MyCalculatePoint(Point a, Point b, int distance) {
            // a. calculate the vector from o to g:
            double vectorX = b.X - a.X;
            double vectorY = b.Y - a.Y;
            // b. calculate the length:
            double magnitude = Math.Sqrt(vectorX * vectorX + vectorY * vectorY);
            // c. normalize the vector to unit length:
            vectorX /= magnitude;
            vectorY /= magnitude;
            // d. calculate and Draw the new vector, which is x1y1 + vxvy * (mag + distance).
            return new Point(
                (int)(((double)a.X + vectorX * (magnitude + distance)) + 0.5) // x = col
              , (int)(((double)a.Y + vectorY * (magnitude + distance)) + 0.5) // y = row
            );
        }

        // =====================================================================

        private const int CELL_SIZE = 4; // width and height of each "cell" in the bitmap.

        private readonly Bitmap _bitmap; // to draw on (displayed in picBox1).
        private readonly Graphics _graphics; // to draw with.

        // actual points on _theLineString are painted red.
        private static readonly SolidBrush _thePointBrush = new SolidBrush(Color.Red);
        // ... and are labeled in Red, Courier New, 12 point, Bold
        private static readonly SolidBrush _theLabelBrush = new SolidBrush(Color.Red);
        private static readonly Font _theLabelFont = new Font("Courier New", 12, FontStyle.Bold);

        // the interveening calculated cells on the lines between actaul points are painted Black.
        private static readonly SolidBrush _theLineBrush = new SolidBrush(Color.Black);

        // the points in my line-string.
        private static readonly Point[] _theLineString = new Point[] {
            //          x,   y
            new Point(170,  85), // A
            new Point( 85,  70), // B
            //new Point(209,  66), // C
            //new Point( 98, 120), // D
            //new Point(158,  19), // E
            //new Point(  2,  61), // F
            //new Point( 42, 177), // G
            //new Point(191, 146), // H
            //new Point( 25, 128), // I
            //new Point( 95,  24)  // J
        };

        public MainForm() {
            InitializeComponent();
            // initialise "the graphics system".
            _bitmap = new Bitmap(picBox1.Width, picBox1.Height);
            _graphics = Graphics.FromImage(_bitmap);
            picBox1.Image = _bitmap;
        }

        #region actual drawing on the Grpahics

        private void DrawCell(int x, int y, Brush brush) {
            _graphics.FillRectangle(
                brush
              , x * CELL_SIZE, y * CELL_SIZE    // x, y
              , CELL_SIZE, CELL_SIZE        // width, heigth
            );
        }

        private void DrawLabel(int x, int y, char c) {
            string s = c.ToString();
            _graphics.DrawString(
                s, _theLabelFont, _theLabelBrush
              , x * CELL_SIZE + 5   // x
              , y * CELL_SIZE - 8   // y
            );
        }

        // ... there should be no mention of _graphics or CELL_SIZE below here ...

        #endregion

        #region draw points on form load

        private void MainForm_Load(object sender, EventArgs e) {
            DrawPoints();
        }

        // draws and labels each point in _theLineString
        private void DrawPoints() {
            char c = 'A'; // label text, as a char so we can increment it for each point.
            foreach ( Point p in _theLineString ) {
                DrawCell(p.X, p.Y, _thePointBrush);
                DrawLabel(p.X, p.Y, c++);
            }
        }

        #endregion

        #region DrawLines on button click

        private void btnDrawLines_Click(object sender, EventArgs e) {
            DrawLinesBetweenPointsInTheString();
        }

        // Draws "the lines" between the points in _theLineString.
        private void DrawLinesBetweenPointsInTheString() {
            int n = _theLineString.Length - 1; // one less line-segment than points
            for ( int i = 0; i < n; ++i )
                Draw(_theLineString[i], _theLineString[i + 1]);
            picBox1.Invalidate(); // tell the graphics system that the picture box needs to be repainted.
        }

        // Draws all the cells along the line from Point "a" to Point "b".
        private void Draw(Point a, Point b) {
            int maxDistance = LengthOfHypotenuse(a, b);
            for ( int distance = 1; distance < maxDistance; ++distance ) {
                var point = CalculatePoint(a, b, distance);
                DrawCell(point.X, point.X, _theLineBrush);
            }
        }

        // Returns the length of the hypotenuse rounded to an integer, using
        // Pythagoras' Theorem for right angle triangles: The length of the
        // hypotenuse equals the sum of the square of the other two sides.
        // Ergo: h = Sqrt(a*a + b*b)
        private int LengthOfHypotenuse(Point a, Point b) {
            double aSq = Math.Pow(Math.Abs(a.X - b.X), 2); // horizontal length squared
            double bSq = Math.Pow(Math.Abs(b.Y - b.Y), 2); // vertical length  squared
            return (int)(Math.Sqrt(aSq + bSq) + 0.5); // length of the hypotenuse
        }

        #endregion

        #region Windows Form Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent() {
            this.picBox1 = new System.Windows.Forms.PictureBox();
            this.btnDrawLines = new System.Windows.Forms.Button();
            ((System.ComponentModel.ISupportInitialize)(this.picBox1)).BeginInit();
            this.SuspendLayout();
            // 
            // picBox1
            // 
            this.picBox1.Dock = System.Windows.Forms.DockStyle.Fill;
            this.picBox1.Location = new System.Drawing.Point(0, 0);
            this.picBox1.Name = "picBox1";
            this.picBox1.Size = new System.Drawing.Size(1000, 719);
            this.picBox1.TabIndex = 0;
            this.picBox1.TabStop = false;
            // 
            // btnDrawLines
            // 
            this.btnDrawLines.Location = new System.Drawing.Point(23, 24);
            this.btnDrawLines.Name = "btnDrawLines";
            this.btnDrawLines.Size = new System.Drawing.Size(77, 23);
            this.btnDrawLines.TabIndex = 1;
            this.btnDrawLines.Text = "Draw Lines";
            this.btnDrawLines.UseVisualStyleBackColor = true;
            this.btnDrawLines.Click += new System.EventHandler(this.btnDrawLines_Click);
            // 
            // MainForm
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(1000, 719);
            this.Controls.Add(this.btnDrawLines);
            this.Controls.Add(this.picBox1);
            this.Location = new System.Drawing.Point(10, 10);
            this.MinimumSize = new System.Drawing.Size(1016, 755);
            this.Name = "MainForm";
            this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
            this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
            this.Text = "Draw Lines on a Matrix.";
            this.Load += new System.EventHandler(this.MainForm_Load);
            ((System.ComponentModel.ISupportInitialize)(this.picBox1)).EndInit();
            this.ResumeLayout(false);
        }

        private System.Windows.Forms.PictureBox picBox1;
        private System.Windows.Forms.Button btnDrawLines;
        #endregion
    }

}

对不起,如果有点长,但这是 SSCCE 从我的真实项目中挖掘出来的,它是 A* 最短路径算法 的实现以运行 MazeOfBolton... 即迷宫赛跑者.

Sorry if it's a bit long, but this an is SSCCE exhumed from my real project, which is an implementation of the A* shortest route algorithm to run the MazeOfBolton... i.e. a maze runner.

我真正想做的是预先计算一个栅栏".(即一个缓冲的 MBR)在迷宫中的两个给定点(起点和目标)周围(a矩阵),使得栅栏"内的所有点都距离两点之间的直线"在给定的距离内,以便快速消除远离目标的数十万条可能的路径.

What I actually want to do is pre-calculate a "fence" (i.e. a buffered MBR) around two given points (origin and goal) in the maze (a matrix), such that all points within the "fence" are within a given distance from "the straight line between the two points", in order to quickly eliminate the hundreds-of-thousands of possible paths which are heading away from the goal.

请注意,这个编程挑战赛几年前就结束了,所以竞争性剽窃"没有问题.这里.不,这不是家庭作业,事实上我是一名专业的程序员......我只是离开了我的舒适区,即使是相对简单的几何图形.叹息.

Note that this programming challenge closed years ago, so there's no issue with "competitive plagerism" here. No this is not homework, in fact I'm a professional programmer... I'm just WAAAAY out of my comfort zone here, even with relatively simple geometry. Sigh.

所以...请任何人都可以给我任何指示,以帮助我正确获得CalculatePoint函数:在距A的给定距离处沿线AB计算一个点?

So... Please can anyone give me any pointers to help me get the CalculatePoint function to correctly: Calculate a point along the line A-B at the given distance from A?

提前感谢您的慷慨……即使阅读到这里.

Thanks in advance for your generosity... even in reading this far.

干杯.基思.

我刚刚更新了发布的源代码,因为:

I just updated the posted source code becuase:

(1) 我刚刚意识到它不是自给自足的.我忘记了单独的 MainForm.Designer.cs 文件,我已将其附加到发布的代码的底部.

(1) I just realised that it wasn't self contained. I forgot about the seperate MainForm.Designer.cs file, which I've appended to the bottom of the posted code.

(2) 最新版本包括我迄今为止尝试过的内容,并带有一个照片桶链接,指向每个失败的样子……而且它们都是一样的.休?什么鬼?

(2) The latest version includes what I've tried so far, with a photobucket link to a picture of what each failure looks like... and they're all the same. Huy? WTF?

我想我的问题可能出在其他地方,比如一些以前被其他人错过的时髦的窗口表单设置,因为我忘记发布设计器生成的代码......除了一切(在我的实际项目中)完全绘制在我期望的地方它到,那么为什么计算点会有所不同.我不知道!?!?!?我很沮丧,我变得暴躁,所以我想我会再等一天;-)

I suppose my problem may be elsewhere, like some funky windows form setting that was previously missed by everyone else because I forgot to post the designer-generated code... Except everythingelse (in my actual project) paints exactly where I expect it to, so why should a calculated point be any different. I don't know!?!?!? I'm pretty frustrated and I'm getting cranky, so I think I'll leave this for another day ;-)

这表明我们通常低估了让计算机做任何事情需要付出多少努力......即使只是画一条简单的线......它甚至不是一条曲线,更不用说一个大圆或横向墨卡托或任何花哨的东西......只是一个简单的粗线!?!?!?;-)

Goes to show how much we routinely underestimate how much effort it'll take to make a computer do ANYthing... even just draw a simple line... it's not even a curve, let alone a great circle or a transverse mercator or anything fancy... just a simple bluddy line!?!?!? ;-)

再次欢呼.基思.

推荐答案

计算向量AB

首先定义从点 A(1,-1) 到点 B(2,4) 的向量,从 B 中减去 A.向量将是 Vab(1,5).

First define the vector from point A(1,-1) to point B(2,4) substracting A from B. The vector would be Vab(1,5).

计算AB的长度

用勾股定理计算向量AB的长度.

Use Pythagorean theorem to calculate the length of vector AB.

|Vab| = SQRT(1²+5²)

长度为(四舍五入)5.1

The Length is (rounded) 5.1

计算单位向量

将向量除以其长度得到单位向量(长度为1的向量).

Divide the vector by its length to get the unit vector (the vector with length 1).

V1(1/5.1,5/5.1) = V1(0.2, 0.98)

计算长度为4的向量

现在将 V1 乘以您想要的长度,例如 4,得到 Vt.

Now multiply V1 with the length you want, for example 4, to get Vt.

Vt(0.2*4,0.98*4) = Vt(0.8,3.92)

计算目标点

将向量 Vt 与点 A 相加得到点 T(目标).

Add the vector Vt to point A to get point T (target).

T = A + Vt = T(1.8,2.92)

回答您的编辑

方法 LengthOfHypotenuse 应该是这样的

The method LengthOfHypotenuse should look like that

  • 修复了计算 bSq 时出现的错误
  • 并删除了多余的 Math.Abs​​ 调用,因为 2 的 pow 始终是正数
  • 删除了 0.5 的添加,不知道你为什么需要它
  • 您至少应该使用浮点数作为返回值(双精度或小数也可以)

  • fixed an error on calculating bSq
  • and removed redundant Math.Abs call, because a pow of 2 is always positive
  • removed the addition of 0.5, don't know why you would need that
  • you should at least use a float as return value (double or decimal would work also)

//You should work with Vector2 class instead of Point and use their Length property
private double LengthOfHypotenuse(Point a, Point b) {
    double aSq = Math.Pow(a.X - b.X, 2); // horizontal length squared
    double bSq = Math.Pow(a.Y - b.Y, 2); // vertical length  squared
    return Math.Sqrt(aSq + bSq); // length of the hypotenuse
}

方法 Draw(Point a, Point b) 应该是这样的:

The method Draw(Point a, Point b) should look like that:

  • 更正了 DrawCell() 调用

  • Corrected DrawCell() call

private void Draw(Point a, Point b) {
    double maxDistance = LengthOfHypotenuse(a, b);
    for (int distance = 0; distance < maxDistance; ++distance) {
        var point = CalculatePoint(new Vector2(a), new Vector2(b), distance);
        DrawCell(point.X, point.Y, _theLineBrush);
    }
}

你的CalculatePoint(Point a, Point b, int distance)方法:

Your CalculatePoint(Point a, Point b, int distance) method:

  • 将一些计算移到 Vector2 类中

  • Moved some calculations into Vector2 class

private Point CalculatePoint(Vector2 a, Vector2 b, int distance) {
    Vector2 vectorAB = a - b;

    return a + vectorAB.UnitVector * distance;
}

我为您扩展了 Vector 类以添加缺少的运算符(归功于 AgentFire)

I have extended the Vector class for you to add the missing operators (credits to AgentFire)

    //AgentFire: Better approach (you can rename the struct if you need):
    struct Vector2 {
        public readonly double X;
        public readonly double Y;
        public Vector2(Point p) : this(p.X,p.Y) { 
        }

        public Vector2(double x, double y) {
            this.X = x;
            this.Y = y;
        }
        public static Vector2 operator -(Vector2 a, Vector2 b) {
            return new Vector2(b.X - a.X, b.Y - a.Y);
        }
        public static Vector2 operator +(Vector2 a, Vector2 b) {
            return new Vector2(b.X + a.X, b.Y + a.Y);
        }
        public static Vector2 operator *(Vector2 a, double d) {
            return new Vector2(a.X * d, a.Y * d);
        }
        public static Vector2 operator /(Vector2 a, double d) {
            return new Vector2(a.X / d, a.Y / d);
        }

        public static implicit operator Point(Vector2 a) {
            return new Point((int)a.X, (int)a.Y);
        }

        public Vector2 UnitVector {
            get { return this / Length; }
        }

        public double Length {
            get {
                double aSq = Math.Pow(X, 2);
                double bSq = Math.Pow(Y, 2);
                return Math.Sqrt(aSq + bSq);
            }
        }

        public override string ToString() {
            return string.Format("[{0}, {1}]", X, Y);
        }
    }

这篇关于沿 A-B 线计算距 A 给定距离的点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆