Find point on ellipse given the angle, then reverse the process - c#

My first task is simple: find the points of the ellipse to be drawn on screen. I made an Ellipse class below with a method that takes in an angle between 0 to 2*PI and returns the point.
public class Ellipse
{
public PointF Center { get; set; }
public float A { get; set; } /* horizontal semiaxis */
public float B { get; set; } /* vertical semiaxis */
public Ellipse(PointF center, float a, float b)
{
this.Center = center;
this.A = a;
this.B = b;
}
public PointF GetXYWhenT(float t_rad)
{
float x = this.Center.X + (this.A * (float)Math.Cos(t_rad));
float y = this.Center.Y + (this.B * (float)Math.Sin(t_rad));
return new PointF(x, y);
}
}
I use the parametric equation of the ellipse as it is convenient for this task. Parameter t is the angle. X and Y values are calculated and put together as a point on the ellipse. By increasing parameter t, I can obtain the points in the order that makes drawing the ellipse as simple as connecting the dots.
private void RunTest1()
{
PointF center = new PointF(0, 0);
float a = 3; /* horizontal semiaxis */
float b = 4; /* vertical semiaxis */
Ellipse ellipse = new Ellipse(center, a, b);
List<PointF> curve = new List<PointF>(); /* collects all points needed to draw the ellipse */
float start = 0;
float end = (float)(2 * Math.PI); /* 360 degrees */
float step = 0.0174533f; /* 1 degree */
for (float t_rad = start; t_rad <= end; t_rad += step)
{
PointF point = ellipse.GetXYWhenT(t_rad);
curve.Add(point);
}
}
RunTestX are methods I run in Main. The first will give me the points I need to draw this ellipse. The points are correct. I have visual confirmation of the ellipse being drawn to specification using a draw method that I will not be including here. Drawing it is NOT the issue. The takeaway here is that for every value of t_rad, I have a corresponding point on the curve.
Now I need to perform a different task after I draw the ellipse. To do this, I need to reverse the process in that I take an arbitrary point on the ellipse and convert it back to the t_rad. Math.Atan2 should do the trick. The method is called GetTWhenPoint. It's an extension method in MyMath class.
public static class MyMath
{
public static float GetTWhenPoint(this PointF center, PointF point)
{
float x = point.X - center.X;
float y = point.Y - center.Y;
float retval = (float)Math.Atan2(y, x);
if (retval < 0)
{
retval += (float)(2 * Math.PI);
}
return retval;
}
}
Simple trigonometry, right? However...
private void RunTest2()
{
PointF center = new PointF(0, 0);
float a = 3; /* horizontal semiaxis */
float b = 4; /* vertical semiaxis */
Ellipse ellipse = new Ellipse(center, a, b);
string debug = "TEST 2\r\n";
float start = 0;
float end = (float)(2 * Math.PI);
float step = 0.0174533f;
for (float t_rad = start; t_rad <= end; t_rad += step)
{
PointF point = ellipse.GetXYWhenT(t_rad);
double t_rad2 = center.GetTWhenPoint(point);
debug += t_rad.ToString() + "\t" + t_rad2.ToString() + "\r\n";
}
Clipboard.SetText(debug);
}
When I use it to convert the point back to t_rad2, I expect it to be equal to or pretty darn close to the original t_rad.
TEST 2
0 0
0.0174533 0.0232692267745733
0.0349066 0.0465274415910244
0.0523599 0.0697636753320694
0.0698132 0.0929670184850693
0.0872665 0.116126760840416
...
6.178444 6.14392471313477
6.195897 6.1670298576355
6.21335 6.19018936157227
6.230803 6.21339273452759
6.248257 6.23662853240967
6.26571 6.25988674163818
6.283163 6.28315591812134
What am I missing here? All my numbers so far have been in radians (as far as I can tell). Now here's where it gets weirder...
private void RunTest3()
{
PointF center = new PointF(0, 0);
float a = 4; /* horizontal semiaxis */
float b = 4; /* vertical semiaxis */
Ellipse ellipse = new Ellipse(center, a, b);
string debug = "TEST 3\r\n";
float start = 0;
float end = (float)(2 * Math.PI);
float step = 0.0174533f;
for (float t_rad = start; t_rad <= end; t_rad += step)
{
PointF point = ellipse.GetXYWhenT(t_rad);
double t_rad2 = center.GetTWhenPoint(point);
debug += t_rad.ToString() + "\t" + t_rad2.ToString() + "\r\n";
}
Clipboard.SetText(debug);
}
If I set a and b equal to make the ellipse a perfect circle then everything looks normal!
TEST 3
0 0
0.0174533 0.0174532998353243
0.0349066 0.0349065996706486
0.0523599 0.0523599050939083
0.0698132 0.0698131918907166
0.0872665 0.0872664898633957
...
6.178444 6.17844390869141
6.195897 6.19589710235596
6.21335 6.21335029602051
6.230803 6.23080348968506
6.248257 6.24825668334961
6.26571 6.26570987701416
6.283163 6.28316307067871
What this tells me is that when I convert the point back to t_rad2 it is somehow affected by the dimensions of the ellipse. But how? Apart from the center adjustment of the ellipse with respect to the Cartesian origin (0,0), GetTWhenPoint method does not make use of any other information from the Ellipse class specifically the semi-axes. Math.Atan2 only needs the x and y values of the point to find the angle it makes with the 0-degree vector. That's basic trigonometry.
It shouldn't even care that it's a point on the ellipse. From the context of the method, it's just a point just like infinitely any other. How is my extension method somehow being affected by the dimensions of my ellipse?
Is it my math that's wrong? I mean it's been a while since I used trig but I think I remember the simple ones correctly.
Thanks in advance!

I think this is what you want.
public class Ellipse
{
public PointF Center { get; set; }
public float A { get; set; } /* horizontal semiaxis */
public float B { get; set; } /* vertical semiaxis */
public Ellipse(PointF center, float a, float b)
{
this.Center=center;
this.A=a;
this.B=b;
}
public PointF GetXYWhenT(float t_rad)
{
float x = this.Center.X+(this.A*(float)Math.Cos(t_rad));
float y = this.Center.Y+(this.B*(float)Math.Sin(t_rad));
return new PointF(x, y);
}
public float GetParameterFromPoint(PointF point)
{
var x = point.X-Center.X;
var y = point.Y-Center.Y;
// Since x=a*cos(t) and y=b*sin(t), then
// tan(t) = sin(t)/cos(t) = (y/b) / (x/a)
return (float)Math.Atan2(A*y, B*x);
}
}
class Program
{
static readonly Random rng = new Random();
static void Main(string[] args)
{
var center = new PointF(35.5f, -12.2f);
var ellipse = new Ellipse(center, 18f, 44f);
// Get t between -π and +π
var t = (float)(2*Math.PI*rng.NextDouble()-Math.PI);
var point = ellipse.GetXYWhenT(t);
var t_check = ellipse.GetParameterFromPoint(point);
Debug.WriteLine($"t={t}, t_check={t_check}");
// t=-0.7434262, t_check=-0.7434263
}
}
I would consider the proper parametrization of a curve as one with a parameter that spans between 0 and 1. Hence the need to specify radians goes away
x = A*Cos(2*Math.PI*t)
y = B*Sin(2*Math.PI*t)
and the reverse
t = Atan2(A*y, B*x)/(2*PI)
Also, consider what the ellipse looks like in polar coordinates relative to the center.
x = A*Cos(t) = R*Cos(θ) | TAN(θ) = B/A*TAN(t)
y = B*Sin(t) = R*Sin(θ) |
| R = Sqrt(B^2+(A^2-B^2)*Cos(t)^2)
A*B
R(θ) = ----------------------------
Sqrt(A^2+(B^2-A^2)*Cos(θ)^2)
Also, consider the following helper functions that wrap angles around the desired quadrants (radian versions)
/// <summary>
/// Wraps angle between 0 and 2π
/// </summary>
/// <param name="angle">The angle</param>
/// <returns>A bounded angle value</returns>
public static double WrapTo2PI(this double angle)
=> angle-(2*Math.PI)*Math.Floor(angle/(2*Math.PI));
/// <summary>
/// Wraps angle between -π and π
/// </summary>
/// <param name="angle">The angle</param>
/// <returns>A bounded angle value</returns>
public static double WrapBetweenPI(this double angle)
=> angle+(2*Math.PI)*Math.Floor((Math.PI-angle)/(2*Math.PI));
and the degree versions
/// <summary>
/// Wraps angle between 0 and 360
/// </summary>
/// <param name="angle">The angle</param>
/// <returns>A bounded angle value</returns>
public static double WrapTo360(this double angle)
=> angle-360*Math.Floor(angle/360);
/// <summary>
/// Wraps angle between -180 and 180
/// </summary>
/// <param name="angle">The angle</param>
/// <returns>A bounded angle value</returns>
/// <remarks>see: http://stackoverflow.com/questions/7271527/inconsistency-with-math-round</remarks>
public static double WrapBetween180(this double angle)
=> angle+360*Math.Floor((180-angle)/360);

Related

Determine if a point is inside a 4-winged star shape via it's formula extracted from the drawing algorithm

This code draws a star shape :
public void draw(double radius, Color color)
{
int x = 0, y = Convert.ToInt32(radius);
double d = (5 / 4) - radius;
circlePoint(x, y, color);
while (x < y)
{
if (d < 0)
{
d += Math.Pow(x, 2) + 3;
x++;
y--;
}
else
{
d += (x - y) * 2 + 5;
y--;
}
circlePoint(x, y, color);
}
}
The drawn shape is shown below
The Actual Question:
I want to write a method (bool hasPoint(Point p) ) to check if p is inside this shape. I know for other shapes like a circle or an ellipse, we can check the point's x and y in relative to the object's formula. My Question is "How can I find the formula using the algorithm in draw() ?
Might be helpful:
I was messing around with the formula of circle and ellipse and I encountered this shape. It's obvious that it has a relation with those shapes formula.
EDIT:
This is not a polygon. You can see this as a deformed circle (an ellipse actually). In a circle, if p.X-x0^2 + p.Y-y0^2 is less than radius^2, then p is a point inside. You can easily tell that it is the formula of a cirlce: x^2 + y^2 = r^2 .
EDIT 2:
( (x0,y0) is the center point )
void circlePoint(int x, int y, Color foreColor)
{
putPixel(x + x0, y + y0);
putPixel(y + x0, x + y0);
putPixel(-x + x0, y + y0);
putPixel(-y + x0, x + y0);
putPixel(x + x0, -y + y0);
putPixel(y + x0, -x + y0);
putPixel(-x + x0, -y + y0);
putPixel(-y + x0, -x + y0);
}
void putPixel(int x, int y, Color color){ bitmap.SetPixel(x, y, color); }
EDIT3:
It appears that this curve is mirrored by 8 lines (2 horizontal, 2 vertical and 4 diagonal):
EDIT4:
Based on emrgee's answer I've added these 2 functions, I don't know where I am wrong but it seems hasPointInside() never returns true;
public double contour(double x)
{
double x2 = Math.Pow(x,2);
return -0.190983 + 0.427051 * Math.Sqrt(0.923607 + (0.647214 * x) - (1.37082 * x2));
}
public bool hasPointInside(Point p)
{
int min = Math.Min(Math.Abs(p.X), Math.Abs(p.Y));
int max = Math.Max(Math.Abs(p.X), Math.Abs(p.Y));
return min <= radius * contour(max / radius);
}
The draw method computes the star contour iteratively, starting from its tip at (0, radius). For understanding and maintaining the code, as well as for the point-in-star test you need, a closed formula of the contour would be preferable. Trying to extract such a formula from the code, let's first analyze the true branch of the if, which is the only branch taken for the first few iterations. The accumulation of d can be transformed into a closed form for d:
While this already doesn't look like anything related to an ellipse, d gets accumulated with a different formula when in the else branch. I claim it is not possible to transform d into a closed form for the entire algorithm.
Other observations: the slope of the contour can only get -1 or steeper. And there are magic numbers in the formulas not computed from the radius argument. So there are strong indications that the contour resembles an ellipse only accidentally.
Now let's assume you want a star with an ellipsoid contour, with 90° tips and with 90° corners. Let's start with the blue ellipse with major radius 1 in x and minor radius 1/2 in y direction:
We scale and move it so that the green point ends up on the diagonal and the red point, where the slope is -1, ends up at (1,0). With a bit of formula massage, the contour becomes:
or, numerically:
The offset on the x axis is -2 + Sqrt(5) or 0.236068.
Pseudo code for your hasPoint method would then look like this:
with this result for radius 30:
In C#, you would provide these methods:
/// <summary>
/// Calculates a contour point of the four-pointed star.
/// </summary>
/// <param name="x">The abscissa of the contour point.</param>
/// <returns>Returns the ordinate of the contour point.</returns>
private static double StarContour(double x)
{
return -0.190983d + (0.427051d * Math.Sqrt(0.923607d + (0.647214d * x) - (1.37082d * x * x)));
}
/// <summary>
/// Tests whether a point is inside the four-pointed star of the given radius.
/// </summary>
/// <param name="radius">The radius of the star.</param>
/// <param name="x">The abscissa of the point to test.</param>
/// <param name="y">The ordinate of the point to test.</param>
/// <returns>Returns <see langword="true" /> if the point (<paramref name="x" />, <paramref name="y" />
/// is inside or on the contour of the star with the given <paramref name="radius" />;
/// otherwise, if the point is outside the star, returns <see langword="false" />.</returns>
private static bool InFourStar(double radius, double x, double y)
{
double min = Math.Abs(x);
double max = Math.Abs(y);
if (min > max)
{
// swap min and max
double h = min;
min = max;
max = h;
}
return min <= radius * StarContour(max / radius);
}
and then use them like this:
/// <summary>
/// Draws the outline of a four-pointed star of given radius and color.
/// </summary>
/// <param name="radius">The radius of the star.</param>
/// <param name="color">The color of the outline.</param>
private void DrawFourStar(double radius, Color color)
{
const double Offset = 0.236068d;
for (int x = (int)(Offset * radius); x < radius; ++x)
{
int y = (int)(radius * StarContour(x / radius));
this.CirclePoint(x, y, color);
}
}
/// <summary>
/// Draws sample points inside or on a four-pointed star contour of given radius.
/// </summary>
/// <param name="radius">The radius of the star.</param>
/// <param name="color">The color of the sample points.</param>
private void DrawSamplesInStar(double radius, Color color)
{
const int SamplingStep = 10;
for (int x = (int)(-radius); x < radius; x += SamplingStep)
{
for (int y = (int)(-radius); y < radius; y += SamplingStep)
{
if (InFourStar(radius, x, y))
{
this.bitmap.SetPixel(x + this.x0, y + this.y0, color);
}
}
}
}
which looks like this for radius 200:

Drawing a straight line from points

I am trying to make a simple chart application and I have stumbled upon a problem. I have an array of points which represent a 45 degree line (y = x) of 100 points. I then try to draw the line by converting each point's value to its pixel value and then draw lines from one point to another using graphic.DrawLines().
The the basic code:
struct MyPoint
{
double X { set; get; }
double Y { set; get; }
}
List<MyPoint> Values;
List<System.Drawing.Point> Points;
for (int i = 0; i < Values.Count; i++)
{
// convert point value to pixel coordinate
int x = (int)((Values[i].X - MinimumX) * (double)Control.Width / (MaximumX - MinimumX) + 0.5);
int y = (int)((Values[i].Y - MinimumY) * (double)Control.Height / (MaximumY - MinimumY)) + 0.5;
Points.Add(new System.Drawing.Point(x, y));
}
graphic.DrawLines(pen, Points.ToArray());
Note: MaximumX and MaximumY are 100 (number of points) and minimums are 0.
The thing is that this kind of line is not very straight :) So my question is, how would I draw a straight line through those points, so that it would look the same as
graphic.DrawLine(pen, 0, 0, Control.Width-1, Control.Height-1);
Thanks in advance!
Since you are doing a graph, I have a basic framework for fitting values within a control like this example:
public struct MyPoint
{
public double X { set; get; }
public double Y { set; get; }
public PointF ToPoint()
{
return new PointF((float)X, (float)Y);
}
}
/// <summary>
/// For http://stackoverflow.com/questions/19459519/drawing-a-straight-line-from-points
/// </summary>
public partial class Form1 : Form
{
List<MyPoint> points;
public Form1()
{
InitializeComponent();
// Initialize points to draw
points=new List<MyPoint>(100);
for (int i=0; i<=100; i++)
{
double θ=2*Math.PI*(i/100.0);
double x=(1+θ/Math.PI)*Math.Cos(θ);
double y=(1+θ/Math.PI)*Math.Sin(θ);
points.Add(new MyPoint() { X=x, Y=y });
}
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
// smooth graphics
e.Graphics.SmoothingMode=SmoothingMode.AntiAlias;
// set margins inside the control client area in pixels
var margin=new System.Drawing.Printing.Margins(16, 16, 16, 16);
// set the domain of (x,y) values
var range=new RectangleF(-3, -3, 6, 6);
// scale graphics
ScaleGraphics(e.Graphics, pictureBox1, range, margin);
// convert points to pixels
PointF[] pixels=points.Select((v) => v.ToPoint()).ToArray();
// draw arrow axes
using (var pen=new Pen(Color.Black, 0))
{
pen.EndCap=System.Drawing.Drawing2D.LineCap.ArrowAnchor;
e.Graphics.DrawLine(pen, range.Left, 0.0f, range.Right, 0.0f);
e.Graphics.DrawLine(pen, 0.0f, range.Top, 0.0f, range.Bottom);
}
// draw bounding rectangle (on margin)
using (var pen=new Pen(Color.LightGray, 0))
{
pen.DashStyle=DashStyle.Dash;
e.Graphics.DrawRectangle(pen, Rectangle.Round(range));
}
// draw curve
using (var pen = new Pen(Color.Blue, 0))
{
//e.Graphics.DrawLines(pen, pixels);
e.Graphics.DrawCurve(pen, pixels);
}
}
/// <summary>
/// Scales the Graphics to fit a Control, given a domain of x,y values and side margins in pixels
/// </summary>
/// <param name="g">The Graphics object</param>
/// <param name="control">The Control</param>
/// <param name="domain">The value domain</param>
/// <param name="margin">The margin</param>
void ScaleGraphics(Graphics g, Control control, RectangleF domain, Margins margin)
{
// Find the drawable area in pixels (control-margins)
int W=control.Width-margin.Left-margin.Right;
int H=control.Height-margin.Bottom-margin.Top;
// Ensure drawable area is at least 1 pixel wide
W=Math.Max(1, W);
H=Math.Max(1, H);
// Find the origin (0,0) in pixels
float OX=margin.Left-W*(domain.Left/domain.Width);
float OY=margin.Top+H*(1+domain.Top/domain.Height);
// Find the scale to fit the control
float SX=W/domain.Width;
float SY=H/domain.Height;
// Transform the Graphics
g.TranslateTransform(OX, OY);
g.ScaleTransform(SX, -SY);
}
private void pictureBox1_SizeChanged(object sender, EventArgs e)
{
// redraw on resize
pictureBox1.Refresh();
}
}
I think the problem is because your points are Integers. Precisions are lost.
Trying using
float x = (float)((Values[i] ...
List<System.Drawing.PointF> PointFs;
instead of
int x = (int)((Values[i] ...
List<System.Drawing.Point> Points;
You probably need something like this:
private void DrawLine()
{
int xInitial = 0, yInitial = 0, xFinal = Control.Width - 1, yFinal = Control.Height - 1;
int dx = xFinal - xInitial, dy = yFinal - yInitial, steps, k, xf, yf;
float xIncrement, yIncrement, x = xInitial, y = yInitial;
if (Math.Abs(dx) > Math.Abs(dy))
steps = Math.Abs(dx);
else
steps = Math.Abs(dy);
xIncrement = dx / (float)steps;
yIncrement = dy / (float)steps;
for (k = 0; k < steps; k++)
{
x += xIncrement;
xf = (int)x;
y += yIncrement;
yf = (int)y;
Points.Add(new System.Drawing.Point(xf, yf));
}
graphic.DrawLines(pen, Points.ToArray());
}
For more details see:
Bresenham's line algorithm
If your points already calculated correctly by algorithm, than simple:
int x = (int) Values[i].X;
int y = (int) Values[i].Y;
Should be enough.
here is my solution:
System.Drawing.Pen myPen;
myPen = new System.Drawing.Pen(System.Drawing.Color.Red);
System.Drawing.Graphics formGraphics = this.CreateGraphics();
formGraphics.DrawLine(myPen, 0, 0, 200, 200);
myPen.Dispose();
formGraphics.Dispose();

C# GMap.Net calculate surface of polygon

I am searching for a way to calculate the surface under a polygon.
The thing I want to accomplish is that a user that uses my program, can create a polygon to mark out his property. Now I want to know what the surface area is so I can tell the user how big his property is.
Unit m² or km² or hectare.
The points of the polygon have a latitude and longitude.
I am using C# with WPF and GMap.NET. The map is in a WindowsFormHost so I can use the Winforms thing from GMap.Net because this provoides overlays etc.
I hope that someone can help me or show me a post where this is explained that I didn't found.
Using a 2D vector space approximation (local tangent space)
In this section, I can detail how I come to these formulas.
Let's note Points the points of the polygon (where Points[0] == Points[Points.Count - 1] to close the polygon).
The idea behind the next methods is to split the polygon into triangles (the area is the sum of all triangle areas). But, to support all polygon types with a simple decomposition (not only star-shaped polygon), some triangle contributions are negative (we have a "negative" area). The triangles decomposition I use is : {(O, Points[i], Points[i + 1]} where O is the origin of the affine space.
The area of a non-self-intersecting polygon (in euclidian geometry) is given by:
In 2D:
float GetArea(List<Vector2> points)
{
float area2 = 0;
for (int numPoint = 0; numPoint < points.Count - 1; numPoint++)
{
MyPoint point = points[numPoint];
MyPoint nextPoint = points[numPoint + 1];
area2 += point.x * nextPoint.y - point.y * nextPoint.x;
}
return area2 / 2f;
}
In 3D, given normal, the unitary normal of the polygon (which is planar):
float GetArea(List<Vector3> points, Vector3 normal)
{
Vector3 vector = Vector3.Zero;
for (int numPoint = 0; numPoint < points.Count - 1; numPoint++)
{
MyPoint point = points[numPoint];
MyPoint nextPoint = points[numPoint + 1];
vector += Vector3.CrossProduct(point, nextPoint);
}
return (1f / 2f) * Math.Abs(Vector3.DotProduct(vector, normal));
}
In the previous code I assumed you have a Vector3 struct with Add, Subtract, Multiply, CrossProduct and DotProduct operations.
In your case, you have a lattitude and longitude. Then, you are not in an 2D euclidean space. It is a spheric space where computing the area of any polygon is much more complex.
However, it is locally homeomorphic to a 2D vector space (using the tangent space).
Then, if the area you try to measure is not too wide (few kilometers), the above formula should work.
Now, you just have to find the normal of the polygon. To do so, and to reduce the error (because we are approximating the area), we use the normal at the centroid of the polygon. The centroid is given by:
Vector3 GetCentroid(List<Vector3> points)
{
Vector3 vector = Vector3.Zero;
Vector3 normal = Vector3.CrossProduct(points[0], points[1]); // Gets the normal of the first triangle (it is used to know if the contribution of the triangle is positive or negative)
normal = (1f / normal.Length) * normal; // Makes the vector unitary
float sumProjectedAreas = 0;
for (int numPoint = 0; numPoint < points.Count - 1; numPoint++)
{
MyPoint point = points[numPoint];
MyPoint nextPoint = points[numPoint + 1];
float triangleProjectedArea = Vector3.DotProduct(Vector3.CrossProduct(point, nextPoint), normal);
sumProjectedAreas += triangleProjectedArea;
vector += triangleProjectedArea * (point + nextPoint);
}
return (1f / (6f * sumProjectedAreas)) * vector;
}
I've added a new property to Vector3 : Vector3.Length
Finally, to convert latitude and longitude into a Vector3:
Vector3 GeographicCoordinatesToPoint(float latitude, float longitude)
{
return EarthRadius * new Vector3(Math.Cos(latitude) * Math.Cos(longitude), Math.Cos(latitude) * Math.Sin(longitude), Math.Sin(latitude));
}
To sum up:
// Converts the latitude/longitude coordinates to 3D coordinates
List<Vector3> pointsIn3D = (from point in points
select GeographicCoordinatesToPoint(point.Latitude, point.Longitude))
.ToList();
// Gets the centroid (to have the normal of the vector space)
Vector3 centroid = GetCentroid(pointsIn3D );
// As we are on a sphere, the normal at a given point is the colinear to the vector going from the center of the sphere to the point.
Vector3 normal = (1f / centroid.Length) * centroid; // We want a unitary normal.
// Finally the area is computed using:
float area = GetArea(pointsIn3D, normal);
The Vector3 struct
public struct Vector3
{
public static readonly Vector3 Zero = new Vector3(0, 0, 0);
public readonly float X;
public readonly float Y;
public readonly float Z;
public float Length { return Math.Sqrt(X * X + Y * Y + Z * Z); }
public Vector3(float x, float y, float z)
{
X = x;
Y = y;
Z = z;
}
public static Vector3 operator +(Vector3 vector1, Vector3 vector2)
{
return new Vector3(vector1.X + vector2.X, vector1.Y + vector2.Y, vector1.Z + vector2.Z);
}
public static Vector3 operator -(Vector3 vector1, Vector3 vector2)
{
return new Vector3(vector1.X - vector2.X, vector1.Y - vector2.Y, vector1.Z - vector2.Z);
}
public static Vector3 operator *(float scalar, Vector3 vector)
{
return new Vector3(scalar * vector.X, scalar * vector.Y, scalar * vector.Z);
}
public static float DotProduct(Vector3 vector1, Vector3 vector2)
{
return vector1.X * vector2.X + vector1.Y * vector2.Y + vector1.Z * vector2.Z;
}
public static Vector3 CrossProduct(Vector3 vector1, Vector3 vector2)
{
return return new Vector3(vector1.Y * vector2.Z - vector1.Z * vector2.Y,
vector1.Z * vector2.X - vector1.X * vector2.Z,
vector1.X * vector2.Y - vector1.Y * vector2.X);
}
}
I fixed it with part of the code from Cédric and code from the internet.
I fixed it by using poly.Points and poly.LocalPoints.
The poly.Points are the latitude and longitude while the LocalPoints are points see to the center of the map on the screen.
the C# library has a function to calculate the distance (km) so I calculted the distance and then I calculated the distance in LocalPoints. Dived the localPoints throug the length in km and then you know how long 1 Localpoint is in km.
Code:
List<PointLatLng> firstTwoPoints = new List<PointLatLng>();
firstTwoPoints.Add(poly.Points[0]);
firstTwoPoints.Add(poly.Points[1]);
GMapPolygon oneLine = new GMapPolygon(firstTwoPoints,"testesddfsdsd"); //Create new polygone from messuring the distance.
double lengteLocalLine =
Math.Sqrt(((poly.LocalPoints[1].X - poly.LocalPoints[0].X)*(poly.LocalPoints[1].X - poly.LocalPoints[0].X)) +
((poly.LocalPoints[1].Y - poly.LocalPoints[0].Y)*(poly.LocalPoints[1].Y - poly.LocalPoints[0].Y))); //This calculates the length of the line in LocalPoints.
double pointInKm = oneLine.Distance / lengteLocalLine; //This gives me the length of 1 LocalPoint in km.
List<Carthesian> waarden = new List<Carthesian>();
//Here we fill the list "waarden" with the points.
//NOTE: the last value is NOT copied because this is handled in calculation method.
foreach (GPoint localPoint in poly.LocalPoints)
{
waarden.Add(new Carthesian(){X = (localPoint.X * pointInKm), Y = (localPoint.Y * pointInKm)});
}
MessageBox.Show("" + GetArea(waarden)*1000000);
}
//Method for calculating area
private double GetArea(IList<Carthesian> points)
{
if (points.Count < 3)
{
return 0;
}
double area = GetDeterminant(points[points.Count - 1].X , points[points.Count - 1].Y, points[0].X, points[0].Y);
for (int i = 1; i < points.Count; i++)
{
//Debug.WriteLine("Lng: " + points[i].Lng + " Lat:" + points[i].Lat);
area += GetDeterminant(points[i - 1].X, points[i - 1].Y , points[i].X, points[i].Y);
}
return Math.Abs(area / 2);
}
//Methode for getting the Determinant
private double GetDeterminant(double x1, double y1, double x2, double y2)
{
return x1 * y2 - x2 * y1;
}
//This class is just to make it nicer to show in code and it also was from previous tries
class Carthesian
{
public double X { get; set; }
public double Y { get; set; }
public double Z { get; set; }
}
Because we calculate the surface using 2D there is a small error, but for my application this is acceptable.
And thanks to Cédric for answering my question and helping me to fix the problem I had.
Its much easier to just use a backend database like SQL Server 2008 or MySql, sending the points of the polygon to the server in a query and return area, length, parimeter, intersection...etc.
If this is viable, search STArea() or STIntersect on Sql Server geography/geometry datatypes.
here is an example of something I have been working on.
using Microsoft.SqlServer.Types;
using System.Data.SqlClient;
GMap.NET.WindowsForms.GMapOverlay o = new GMapOverlay();
GMap.NET.WindowsForms.GMapOverlay o1 = new GMapOverlay();
List<PointLatLng> p = new List<PointLatLng>();
List<string> p1 = new List<string>();
//below adds a marker to the map upon each users click. At the same time adding that Lat/Long to a <PointLatLng> list
private void gMapControl1_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right )
{
p.Add(new PointLatLng(Convert.ToDouble(gMapControl2.FromLocalToLatLng(e.X, e.Y).Lat), Convert.ToDouble(gMapControl2.FromLocalToLatLng(e.X, e.Y).Lng)));
p1.Add(gMapControl2.FromLocalToLatLng(e.X, e.Y).Lng + " " + gMapControl2.FromLocalToLatLng(e.X, e.Y).Lat);
GMarkerGoogle marker = new GMarkerGoogle(gMapControl2.FromLocalToLatLng(e.X, e.Y), GMarkerGoogleType.red_small);
// marker.Tag =(gMapControl1.FromLocalToLatLng(e.X, e.Y).Lng + " " + gMapControl1.FromLocalToLatLng(e.X, e.Y).Lat);
o.Markers.Add(marker);
gMapControl2.Overlays.Add(o);
}
}
//Then the below code will add that <PointLatLng> List to a SQL query and return Area and Centoid of polygon. Area is returned in Acres
private void gMapControl1_MouseDoubleClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
try
{
o.Clear();
n = new GMapPolygon(p, "polygon");
n.Fill = new SolidBrush(Color.Transparent);
n.Stroke = new Pen(Color.Red, 1);
o.Polygons.Add(n);
gMapControl2.Overlays.Add(o);
StringBuilder a = new StringBuilder();
StringBuilder b = new StringBuilder();
p1.ToArray();
for (int i = 0; i != p1.Count; i++)
{
a.Append(p1[i].ToString() + ",");
}
a.Append(p1[0].ToString());
cs.Open();
SqlCommand cmd = new SqlCommand("Declare #g geography; set #g = 'Polygon((" + a + "))'; Select Round((#g.STArea() *0.00024711),3) As Area", cs);
SqlCommand cmd1 = new SqlCommand("Declare #c geometry; set #c =geometry::STGeomFromText('Polygon((" + a + "))',0); Select Replace(Replace(#c.STCentroid().ToString(),'POINT (',''),')','')AS Center", cs);
poly = "Polygon((" + a + "))";
SqlDataReader sdr = cmd.ExecuteReader();
while (sdr.Read())
{
txtArea.Text = sdr["Area"].ToString();
}
sdr.Close();
SqlDataReader sdr1 = cmd1.ExecuteReader();
while (sdr1.Read())
{
center = sdr1["Center"].ToString();
lat = center.Substring(center.IndexOf(" ") + 1);
lat = lat.Remove(9);
lon = center.Substring(0, (center.IndexOf(" ")));
lon = lon.Remove(10);
txtCenter.Text = lat + ", " + lon;
}
sdr1.Close();
}
catch (Exception ex)
{
MessageBox.Show("Please start the polygon over, you must create polygon in a counter-clockwise fasion","Counter-Clockwise Only!",MessageBoxButtons.OK,MessageBoxIcon.Error);
}
finally
{
p.Clear();
p1.Clear();
cs.Close();
o.Markers.Clear();
}
}
}

Calculate a point along the line A-B at a given distance from 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.
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
}
}
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.
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.
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.
Cheers. Keith.
EDIT: I just updated the posted source code becuase:
(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) 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!?!?!? ;-)
A BIG Thank You to everyone who posted!
Cheers again. Keith.
Calculate the vector AB
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).
Calculate the length of AB
Use Pythagorean theorem to calculate the length of vector AB.
|Vab| = SQRT(1²+5²)
The Length is (rounded) 5.1
Calculate the unit vector
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)
Calculate the vector with length 4
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)
Calculate target point
Add the vector Vt to point A to get point T (target).
T = A + Vt = T(1.8,2.92)
EDIT: Answer to your edit
The method LengthOfHypotenuse should look like that
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
}
The method Draw(Point a, Point b) should look like that:
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);
}
}
Your CalculatePoint(Point a, Point b, int distance) method:
Moved some calculations into Vector2 class
private Point CalculatePoint(Vector2 a, Vector2 b, int distance) {
Vector2 vectorAB = a - b;
return a + vectorAB.UnitVector * distance;
}
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);
}
}
Better approach (you can rename the struct if you need):
struct Vector2
{
public readonly float X;
public readonly float Y;
public Vector2(float x, float 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(a.X + b.X, a.Y + b.Y);
}
public static Vector2 operator *(Vector2 a, float 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 + a action:
class Program
{
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 + a;
Console.WriteLine(c);
}
}
This will give you the point:
output:\> [2, 1]
All you need after that is to for(the distance; up toone; d += step) from 0.0 to 1.0 and draw your pixels.
private static Point CalculatePoint(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 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));
}
Try to remove 'magnitude' term in the parentheses both for X and for Y expressions:
(int)( ((double)a.X + vectorX * distance) + 0.5 )
private Point CalculatePoint(Point a, Point b, int distance) {
Point newPoint = new Point(10,10);
Double Magnitude = Math.Sqrt(Math.Pow((b.Y - a.Y),2) + Math.Pow((b.X - a.X),2));
newPoint.X = (int)(a.X + (distance * ((b.X - a.X)/magnitude)));
newPoint.Y = (int)(a.Y + (distance * ((b.Y - a.Y)/magnitude)));
return newPoint;
}
OK guys, I found my major bug. It was a classic Doh! My Draw method was painting at p.X, p.X
So, I finally got something that works. Please note that I am not saying that this a "good solution", or "the only working solution" I'm just saying that it does what I want it to do ;-)
Here's my UPDATED working code: (complete and selfcontained this time ;-)
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Diagnostics;
namespace DrawLines
{
public class MainForm : Form
{
#region constants and readonly attributes
private const int CELL_SIZE = 4; // width and height of each "cell" in the bitmap.
private readonly Bitmap _myBitmap; // to draw on (displayed in picBox1).
private readonly Graphics _myGraphics; // to draw with.
// actual points on _theLineString are painted red.
private static readonly SolidBrush _thePointBrush = new SolidBrush(Color.Red);
// ... and are labeled in /*Bold*/ Black, 16 point Courier New
private static readonly SolidBrush _theLabelBrush = new SolidBrush(Color.Black);
private static readonly Font _theLabelFont = new Font("Courier New", 16); //, FontStyle.Bold);
// the interveening calculated cells on the lines between actaul points are painted Silver.
private static readonly SolidBrush _theLineBrush = new SolidBrush(Color.Silver);
// the points in my line-string.
private static readonly Point[] _thePoints = new Point[] {
// x, y c i
new Point(170, 85), // A 0
new Point( 85, 70), // B 1
new Point(209, 66), // C 2
new Point( 98, 120), // D 3
new Point(158, 19), // E 4
new Point( 2, 61), // F 5
new Point( 42, 177), // G 6
new Point(191, 146), // H 7
new Point( 25, 128), // I 8
new Point( 95, 24) // J 9
};
#endregion
public MainForm() {
InitializeComponent();
// initialise "the graphics system".
_myBitmap = new Bitmap(picBox1.Width, picBox1.Height);
_myGraphics = Graphics.FromImage(_myBitmap);
picBox1.Image = _myBitmap;
}
#region DrawPoints upon MainForm_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 _thePoints ) {
DrawCell(p.X, p.Y, _thePointBrush);
DrawLabel(p.X, p.Y, c++);
}
}
#endregion
#region DrawLines on button click
// =====================================================================
// Here's the interesting bit. DrawLine was called Draw
// Draws a line from A to B, by using X-values to calculate the Y values.
private void DrawLine(Point a, Point b)
{
if ( a.Y > b.Y ) // A is below B
Swap(ref a, ref b); // make A the topmost point (ergo sort by Y)
Debug.Assert(a.Y < b.Y, "A is still below B!");
var left = Math.Min(a.X, b.X);
var right = Math.Max(a.X, b.X);
int width = right - left;
Debug.Assert(width >= 0, "width is negative!");
var top = a.Y;
var bottom = b.Y;
int height = bottom - top;
Debug.Assert(height >= 0, "height is negative!");
if ( width > height ) {
// use given X values to calculate the Y values,
// otherwise it "skips" some X's
double slope = (double)height / (double)width;
Debug.Assert(slope >= 0, "slope is negative!");
if (a.X <= b.X) // a is left-of b, so draw left-to-right.
for ( int x=1; x<width; ++x ) // xOffset
DrawCell( (left+x), (a.Y + ((int)(slope*x + 0.5))), _theLineBrush);
else // a is right-of b, so draw right-to-left.
for ( int x=1; x<width; ++x ) // xOffset
DrawCell( (right-x), (a.Y + ((int)(slope*x + 0.5))), _theLineBrush);
} else {
// use given Y values to calculate the X values,
// otherwise it "skips" some Y's
double slope = (double)width/ (double)height;
Debug.Assert(slope >= 0, "slope is negative!");
if (a.X <= b.X) { // a is left-of b, so draw left-to-right. (FG)
for ( int y=1; y<height; ++y ) // yOffset
DrawCell( (a.X + ((int)(slope*y + 0.5))), (top+y), _theLineBrush);
} else { // a is right-of b, so draw right-to-left. (DE,IJ)
for ( int y=1; y<height; ++y ) // yOffset
DrawCell( (b.X + ((int)(slope*y + 0.5))), (bottom-y), _theLineBrush);
}
}
}
private void btnDrawLines_Click(object sender, EventArgs e) {
DrawLines(); // join the points
DrawPoints(); // redraw the labels over the lines.
}
// Draws a line between each point in _theLineString.
private void DrawLines() {
int n = _thePoints.Length - 1; // one less line-segment than points
for ( int i=0; i<n; ++i )
DrawLine(_thePoints[i], _thePoints[i+1]);
picBox1.Invalidate(); // tell the graphics system that the picture box needs to be repainted.
}
private void Swap(ref Point a, ref Point b) {
Point tmp = a;
a = b;
b = tmp;
}
#endregion
#region actual drawing on _myGraphics
// there should be no calls to Draw or Fill outside of this region
private void DrawCell(int x, int y, Brush brush) {
_myGraphics.FillRectangle(
brush
, x*CELL_SIZE
, y*CELL_SIZE
, CELL_SIZE // width
, CELL_SIZE // heigth
);
}
private void DrawLabel(int x, int y, char c) {
string s = c.ToString();
_myGraphics.DrawString(
s, _theLabelFont, _theLabelBrush
, x * CELL_SIZE + 5 // x
, y * CELL_SIZE - 10 // y
);
}
#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
}
}
EDIT - UPDATED ABOVE CODE: This version draws "solid" lines. The previously posted version skipped cells in nearly vertical lines, so I inverted the algorithm to calculate the X value (instead of the Y value) in these cases... now I can use it to set (and draw) a "solid fence" around a "navigable area" ;-)
Here's an UPDATED picture of the correct results.
Thanks again to everybody who helped... and you did help ;-)
Cheers. Keith.

How to scale and draw a plot in C#

I am trying to use the CSV below for drawing a plot:
2.364258,3.005366
2.723633,3.009784
3.083008,3.012145
3.442383,3.012705
3.801758,3.010412
4.160156,3.010703
4.518555,3.011985
4.876953,3.012547
5.235352,3.009941
5.592773,3.011252
5.951172,3.010596
6.30957,3.011951
6.667969,3.010613
7.026367,3.008634
7.384766,3.009744
7.743164,3.01062
8.101563,3.00942
8.459961,3.009438
8.818359,3.009478
9.177734,3.010827
What I did so far is that I tried to make a Class to do this! this is the part when I try to draw the curve:
class Plotter
{
#region Fields and variables
private Bitmap plot;
private Graphics g;
public string PlotType {get; set;}
private int iWidth; //Width of the box
private int iHeight; //
private float xMax; //maximum range on X axis
private float yMax; //maximum range on Y axis
private PointF[] points;
#endregion
#region Constructors
/// <summary>
/// Constructor of class
/// </summary>
/// <param name="iWidth">Width of image in pixels</param>
/// <param name="iHeight">Height of image in pixels</param>
/// <param name="xMax">Maximum value of the values on X</param>
/// <param name="yMax">Maximum value of the values on Y</param>
/// <param name="pairs">Pairs of data in an array of PointF[] this is raw data!!</param>
public Plotter(int iWidth, int iHeight, float xMax, float yMax, PointF[] points)
{
this.iWidth = iWidth;
this.iHeight = iHeight;
this.xMax = xMax;
this.yMax = yMax;
this.points = points;
plot = new Bitmap(iWidth, iHeight);
}
public Bitmap DrawPlot()
{
Pen blackPen = new Pen(Color.Black, 1);
g = Graphics.FromImage(plot);
PointF[] p = new PointF[points.GetLength(0)];
//Try to scale input data to pixel coordinates
foreach (PointF point in points)
{
int i = 0;
p[i].X = point.X * iWidth;
p[1].X = point.Y * iHeight;
}
g.DrawCurve(blackPen, p, 0);
return plot;
}
What I get at the end is jsut a stright line! that I think has been drawn on X{0,0} and Y{0,0} to X{0, 400} and Y{0,0}
Can you help me correct the mistakes please?
P.S: http://itools.subhashbose.com/grapher/index.php this site can draw the plot I need pretty good from the CSV data I have (if you need to check).
Thanks!
This seems to be your problem:
foreach (PointF point in points)
{
int i = 0;
p[i].X = point.X * iWidth;
p[1].X = point.Y * iHeight;
}
i is always zero and you are never assigning Y. The "second" assignment isn't even using i, but the 1 index.
Quick fix without error checking:
int i = 0;
foreach (PointF point in points)
{
p[i].X = point.X * iWidth;
p[i].Y = point.Y * iHeight;
i++;
}
Your assigning the x both times.
p[i].X = point.X * iWidth;
p[1].X = point.Y * iHeight;
And as #LarsTech points out you need to fix the counter

Categories