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

查看:30
本文介绍了沿 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 函数实现:给定起点和终点以及距离,计算沿线的点(加上修正后来的评论 - 如果我理解正确的话)因为我解决问题的独立尝试使我无处可去,除了头等舱特快票frusterpationland.

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函数:计算沿AB线距A给定距离的一个点?

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) 最新版本包括我迄今为止尝试过的内容,带有指向每次失败情况的图片的 photobucket 链接……而且它们都是相同的.咦?跆拳道?

(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天全站免登陆