Calculate a point which is perpendicular to a line - java

I have two points store in two variable, which forms a line. I want to find a point which is perpendicular to that line from one end point in that line.
Suppose I have two points P1(x1,y1) and P2(x2,y2) then i want to find a third point P3 such that line(P1-P2) is perpendicular to line(P2,P3) and intersect at P2.

First, the angle:
public static double angle (double x1, double y1, double x2, double y2) {
double xdiff = x1 - x2;
double ydiff = y1 - y2;
//double tan = xdiff / ydiff;
double atan = Math.atan2(ydiff, xdiff);
return atan;
}
To get the perpendicular, you must add PI/2 to the angle of the line defined by your two points.
Once you have that angle, the formula is:
x = interceptPt.x + sin(perp_angle) * distance;
y = interceptPt.y + cos(perp_angle) * distance;

If you want to use Java I can recommend to use JTS. Create a LineSegment and use the pointAlongOffset method. Given Points p1 and p2 the code would look like that:
// create LineSegment
LineSegment ls = new LineSegment(p1.getX(), p1.getY(), p2.getX(), p2.getY());
// perpendicular distance to line
double offsetDistance = 10;
// calculate Point right to start point
Coordinate startRight = ls.pointAlongOffset(0, offsetDistance);
// calculate Point left to start point
Coordinate startLeft = ls.pointAlongOffset(0, -offsetDistance);
// calculate Point right to end point
Coordinate endRight = ls.pointAlongOffset(1, offsetDistance);
// calculate Point left to end point
Coordinate endLeft = ls.pointAlongOffset(1, -offsetDistance);

ControlAltDel is already answered but he did a mistake, replaced cos to sin
x = interceptPt.x + cos(angle + 90) * distance;
y = interceptPt.y + sin(angle + 90) * distance;
x,y is point away from (interceptPt.x,interceptPt.y) at (distance) .
(interceptPt.x,interceptPt.y) is point in your line where perpendicular start to drawn.
angle = your line angle with horizontal axis

I got the answer at http://jsfiddle.net/eLxcB/2/
// Start and end point
var startX = 120
var startY = 150
var endX = 180
var endY = 130
R.circle(startX,startY,2);
// Calculate how far above or below the control point should be
var centrePointX = startX
var centrePointY = startY;
// Calculate slopes and Y intersects
var lineSlope = (endY - startY) / (endX - startX);
var perpendicularSlope = -1 / lineSlope;
var yIntersect = centrePointY - (centrePointX * perpendicularSlope);
// Draw a line between the two original points
R.path('M '+startX+' '+startY+', L '+endX+' '+endY);
// Plot some test points to show the perpendicular line has been found
R.circle(100, (perpendicularSlope * 100) + yIntersect, 2);

You can store your points in vec2d, then use some mathematical equations to get the perpendicular point.
vec2d getPerpendicularPoint(vec2d A, vec2d B, float distance)
{
vec2d M = (A + B) / 2;
vec2d p = A - B;
vec2d n = (-p.y, p.x);
int norm_length = sqrt((n.x * n.x) + (n.y * n.y));
n.x /= norm_length;
n.y /= norm_length;
return (M + (distance * n));
}

Related

Find arc center point if start point, end point and offset is given

I want to find the center point of an arc.
I have start point, end point, and offset of an arc.
I have tried the below code. With this code, I got the center point of arc in a 90% case. But for certain data its fail to find center point.
I have this definition of arc drawing
X169290Y2681101I90207J39371*
X267716Y2779527J98426*
X169290Y2877952I98425*
X79082Y2818897J98425*
The answer for all above four arc center coordinate is (4.299940599999999, 70.5999858)
This is an arc image, This arc is made from four single-quadrant arcs.
private Coordinate findCenter(Coordinate start, Coordinate end, Coordinate offset) {
double twoPi = 2 * Math.PI;
if (quadrantMode.equals("single-quadrant")) {
// The Gerber spec says single quadrant only has one possible center,
// and you can detect it based on the angle. But for real files, this
// seems to work better - there is usually only one option that makes
// sense for the center (since the distance should be the same
// from start and end). We select the center with the least error in
// radius from all the options with a valid sweep angle.
double sqdistDiffMin = Double.MAX_VALUE;
Coordinate center = null;
List<Coordinate> qFactors = new ArrayList<Coordinate>();
qFactors.add(new Coordinate(1, 1));
qFactors.add(new Coordinate(1, -1));
qFactors.add(new Coordinate(-1, 1));
qFactors.add(new Coordinate(-1, -1));
for (Coordinate factors : qFactors) {
Coordinate testCenter = new Coordinate(start.getX() + offset.getX() * factors.getX(),
start.getY() + offset.getY() * factors.getY());
// Find angle from center to start and end points
double startAngleX = start.getX() - testCenter.getX();
double startAngleY = start.getY() - testCenter.getY();
double startAngle = Math.atan2(startAngleY, startAngleX);
double endAngleX = end.getX() - testCenter.getX();
double endAngleY = end.getY() - testCenter.getY();
double endAngle = Math.atan2(endAngleY, endAngleX);
// # Clamp angles to 0, 2pi
double theta0 = (startAngle + twoPi) % twoPi;
double theta1 = (endAngle + twoPi) % twoPi;
// # Determine sweep angle in the current arc direction
double sweepAngle;
if (direction.equals("counterclockwise") ) {
theta1 += twoPi;
// sweepAngle = Math.abs(theta1 - theta0);
sweepAngle = Math.abs(theta1 - theta0) % twoPi;
} else {
theta0 += twoPi;
sweepAngle = Math.abs(theta0 - theta1) % twoPi;
}
// # Calculate the radius error
double sqdistStart = sqDistance(start, testCenter);
double sqdistEnd = sqDistance(end, testCenter);
double sqdistDiff = Math.abs(sqdistStart - sqdistEnd);
// Take the option with the lowest radius error from the set of
// options with a valid sweep angle
// In some rare cases, the sweep angle is numerically (10**-14) above pi/2
// So it is safer to compare the angles with some tolerance
boolean isLowestRadiusError = sqdistDiff < sqdistDiffMin;
boolean isValidSweepAngle = sweepAngle >= 0 && sweepAngle <= Math.PI / 2.0 + 1e-6;
if (isLowestRadiusError && isValidSweepAngle) {
center = testCenter;
sqdistDiffMin = sqdistDiff;
}
}
return center;
}
else {
return new Coordinate(start.getX() + offset.getX(), start.getY() + offset.getY());
}
}
public static double sqDistance(Coordinate point1, Coordinate point2) {
double diff1 = point1.getX() - point2.getX();
double diff2 = point1.getY() - point2.getY();
return diff1 * diff1 + diff2 * diff2;
}
Explaination graph for arc drawing
You are dealing with an accuracy problem, caused by the value 1e-6. I'd advise you to do two things:
Check what happens when you replace this value by zero (any side-effects which might cause problems?)
In case zero value does not work, try to replace by a value, smaller than 1e-6, like 1e-9, 1e-12, ...
But most of all: you need to do some exhaustive testing while modifying that value (do you have a testlist that needs to be covered + invent some realistic testing cases).

Stroking lines and computing line joins

does anyone have any tips/ideas on how to calculate Round line joins?
The device I'm working on only supports single width lines.
I am trying to implement basic stroking with only round line joins.
Some things I'm messing with are below.
It's not much, but I hope to get some ideas on how to handle the different cases when two lines join, based on any replies.
Thanks in advance for any help.
I have had some progress with the outer join:
a. Get clockwise order vertices (I get these from flattened glyphs)
b. Grab 3 vertices
c. Compute normal of line A (prevX, prevY) -> (currentX, currentY)
d. Compute normal of line B (currentX, currentY) -> (nextX, nextY)
I compute normals using a left turn on the current clockwise vertices
normal = (deltaY, -deltaX) // Thanks Andreas
Vec2[] computeNormals(float prevX, float prevY, float x, float y, float nextX, float nextY) {
float dx1 = x - prevX;
float dy1 = y - prevY;
float dx2 = x - nextX;
float dy2 = y - nextY;
Vec2 normal1 = new Vec2(dy1, -dx1).normalize();
Vec2 normal2 = new Vec2(dy2, -dx2).normalize();
if (normal1.angleDeg() > normal2.angleDeg()) {
normal2.rot((float) Math.PI);
}
return (new Vec2[] { normal1, normal2 });
}
e. Determine outer join arc angle from atan2(deltaY, -deltaX)
void computeArc(VertexBuffer dest, float x, float y, float arcRadius, Vec2 normal1, Vec2 normal2) {
// Angle from Vecto2D is atan2(y, x)
float angleStart = normal1.angle();
float angleEnd = normal2.angle();
float angleInc = (float) Math.PI / 4f; // Temporary, need to find a way to determine numVertices for a Pen of a given width
while (angleStart > angleEnd) {
angleStart -= (float) (2f * Math.PI);
}
for (float a = angleStart; a <= angleEnd; a += angleInc) {
float vx = x + ((float) Math.cos(a) * arcRadius);
float vy = y + ((float) Math.sin(a) * arcRadius);
dest.addVertex(vx, vy);
}
}
If your device can draw filled circles, you could put a filled circle at the 2 end points, and one at every line joint.

How to draw a smooth line through a set of points using Bezier curves?

I need to draw a smooth line through a set of vertices. The set of vertices is compiled by a user dragging their finger across a touch screen, the set tends to be fairly large and the distance between the vertices is fairly small. However, if I simply connect each vertex with a straight line, the result is very rough (not-smooth).
I found solutions to this which use spline interpolation (and/or other things I don't understand) to smooth the line by adding a bunch of additional vertices. These work nicely, but because the list of vertices is already fairly large, increasing it by 10x or so has significant performance implications.
It seems like the smoothing should be accomplishable by using Bezier curves without adding additional vertices.
Below is some code based on the solution here:
http://www.antigrain.com/research/bezier_interpolation/
It works well when the distance between the vertices is large, but doesn't work very well when the vertices are close together.
Any suggestions for a better way to draw a smooth curve through a large set of vertices, without adding additional vertices?
Vector<PointF> gesture;
protected void onDraw(Canvas canvas)
{
if(gesture.size() > 4 )
{
Path gesturePath = new Path();
gesturePath.moveTo(gesture.get(0).x, gesture.get(0).y);
gesturePath.lineTo(gesture.get(1).x, gesture.get(1).y);
for (int i = 2; i < gesture.size() - 1; i++)
{
float[] ctrl = getControlPoint(gesture.get(i), gesture.get(i - 1), gesture.get(i), gesture.get(i + 1));
gesturePath.cubicTo(ctrl[0], ctrl[1], ctrl[2], ctrl[3], gesture.get(i).x, gesture.get(i).y);
}
gesturePath.lineTo(gesture.get(gesture.size() - 1).x, gesture.get(gesture.size() - 1).y);
canvas.drawPath(gesturePath, mPaint);
}
}
}
private float[] getControlPoint(PointF p0, PointF p1, PointF p2, PointF p3)
{
float x0 = p0.x;
float x1 = p1.x;
float x2 = p2.x;
float x3 = p3.x;
float y0 = p0.y;
float y1 = p1.y;
float y2 = p2.y;
float y3 = p3.y;
double xc1 = (x0 + x1) / 2.0;
double yc1 = (y0 + y1) / 2.0;
double xc2 = (x1 + x2) / 2.0;
double yc2 = (y1 + y2) / 2.0;
double xc3 = (x2 + x3) / 2.0;
double yc3 = (y2 + y3) / 2.0;
double len1 = Math.sqrt((x1-x0) * (x1-x0) + (y1-y0) * (y1-y0));
double len2 = Math.sqrt((x2-x1) * (x2-x1) + (y2-y1) * (y2-y1));
double len3 = Math.sqrt((x3-x2) * (x3-x2) + (y3-y2) * (y3-y2));
double k1 = len1 / (len1 + len2);
double k2 = len2 / (len2 + len3);
double xm1 = xc1 + (xc2 - xc1) * k1;
double ym1 = yc1 + (yc2 - yc1) * k1;
double xm2 = xc2 + (xc3 - xc2) * k2;
double ym2 = yc2 + (yc3 - yc2) * k2;
// Resulting control points. Here smooth_value is mentioned
// above coefficient K whose value should be in range [0...1].
double k = .1;
float ctrl1_x = (float) (xm1 + (xc2 - xm1) * k + x1 - xm1);
float ctrl1_y = (float) (ym1 + (yc2 - ym1) * k + y1 - ym1);
float ctrl2_x = (float) (xm2 + (xc2 - xm2) * k + x2 - xm2);
float ctrl2_y = (float) (ym2 + (yc2 - ym2) * k + y2 - ym2);
return new float[]{ctrl1_x, ctrl1_y, ctrl2_x, ctrl2_y};
}
Bezier Curves are not designed to go through the provided points! They are designed to shape a smooth curve influenced by the control points.
Further you don't want to have your smooth curve going through all data points!
Instead of interpolating you should consider filtering your data set:
Filtering
For that case you need a sequence of your data, as array of points, in the order the finger has drawn the gesture:
You should look in wiki for "sliding average".
You should use a small averaging window. (try 5 - 10 points). This works as follows: (look for wiki for a more detailed description)
I use here an average window of 10 points:
start by calculation of the average of points 0 - 9, and output the result as result point 0
then calculate the average of point 1 - 10 and output, result 1
And so on.
to calculate the average between N points:
avgX = (x0+ x1 .... xn) / N
avgY = (y0+ y1 .... yn) / N
Finally you connect the resulting points with lines.
If you still need to interpolate between missing points, you should then use piece - wise cubic splines.
One cubic spline goes through all 3 provided points.
You would need to calculate a series of them.
But first try the sliding average. This is very easy.
Nice question. Your (wrong) result is obvious, but you can try to apply it to a much smaller dataset, maybe by replacing groups of close points with an average point. The appropriate distance in this case to tell if two or more points belong to the same group may be expressed in time, not space, so you'll need to store the whole touch event (x, y and timestamp). I was thinking of this because I need a way to let users draw geometric primitives (rectangles, lines and simple curves) by touch
What is this for? Why do you need to be so accurate? I would assume you only need something around 4 vertices stored for every inch the user drags his finger. With that in mind:
Try using one vertex out of every X to actually draw between, with the middle vertex used for specifying the weighted point of the curve.
int interval = 10; //how many points to skip
gesture.moveTo(gesture.get(0).x, gesture.get(0).y);
for(int i =0; i +interval/2 < gesture.size(); i+=interval)
{
Gesture ngp = gesture.get(i+interval/2);
gesturePath.quadTo(ngp.x,ngp.y, gp.x,gp.y);
}
You'll need to adjust this to actually work but the idea is there.

Calculating the angle between two lines without having to calculate the slope? (Java)

I have two Lines: L1 and L2. I want to calculate the angle between the two lines. L1 has points: {(x1, y1), (x2, y2)} and L2 has points: {(x3, y3), (x4, y4)}.
How can I calculate the angle formed between these two lines, without having to calculate the slopes? The problem I am currently having is that sometimes I have horizontal lines (lines along the x-axis) and the following formula fails (divide by zero exception):
arctan((m1 - m2) / (1 - (m1 * m2)))
where m1 and m2 are the slopes of line 1 and line 2 respectively. Is there a formula/algorithm that can calculate the angles between the two lines without ever getting divide-by-zero exceptions? Any help would be highly appreciated.
This is my code snippet:
// Calculates the angle formed between two lines
public static double angleBetween2Lines(Line2D line1, Line2D line2)
{
double slope1 = line1.getY1() - line1.getY2() / line1.getX1() - line1.getX2();
double slope2 = line2.getY1() - line2.getY2() / line2.getX1() - line2.getX2();
double angle = Math.atan((slope1 - slope2) / (1 - (slope1 * slope2)));
return angle;
}
Thanks.
The atan2 function eases the pain of dealing with atan.
It is declared as double atan2(double y, double x) and converts rectangular coordinates (x,y) to the angle theta from the polar coordinates (r,theta)
So I'd rewrite your code as
public static double angleBetween2Lines(Line2D line1, Line2D line2)
{
double angle1 = Math.atan2(line1.getY1() - line1.getY2(),
line1.getX1() - line1.getX2());
double angle2 = Math.atan2(line2.getY1() - line2.getY2(),
line2.getX1() - line2.getX2());
return angle1-angle2;
}
Dot product is probably more useful in this case. Here you can find a geometry package for Java which provides some useful helpers. Below is their calculation for determining the angle between two 3-d points. Hopefully it will get you started:
public static double computeAngle (double[] p0, double[] p1, double[] p2)
{
double[] v0 = Geometry.createVector (p0, p1);
double[] v1 = Geometry.createVector (p0, p2);
double dotProduct = Geometry.computeDotProduct (v0, v1);
double length1 = Geometry.length (v0);
double length2 = Geometry.length (v1);
double denominator = length1 * length2;
double product = denominator != 0.0 ? dotProduct / denominator : 0.0;
double angle = Math.acos (product);
return angle;
}
Good luck!
dx1 = x2-x1;
dy1 = y2-y1;
dx2 = x4-x3;
dy2 = y4-y3;
d = dx1*dx2 + dy1*dy2; // dot product of the 2 vectors
l2 = (dx1*dx1+dy1*dy1)*(dx2*dx2+dy2*dy2) // product of the squared lengths
angle = acos(d/sqrt(l2));
The dot product of 2 vectors is equal to the cosine of the angle time the length of both vectors. This computes the dot product, divides by the length of the vectors and uses the inverse cosine function to recover the angle.
Maybe my approach for Android coordinates system will be useful for someone (used Android PointF class to store points)
/**
* Calculate angle between two lines with two given points
*
* #param A1 First point first line
* #param A2 Second point first line
* #param B1 First point second line
* #param B2 Second point second line
* #return Angle between two lines in degrees
*/
public static float angleBetween2Lines(PointF A1, PointF A2, PointF B1, PointF B2) {
float angle1 = (float) Math.atan2(A2.y - A1.y, A1.x - A2.x);
float angle2 = (float) Math.atan2(B2.y - B1.y, B1.x - B2.x);
float calculatedAngle = (float) Math.toDegrees(angle1 - angle2);
if (calculatedAngle < 0) calculatedAngle += 360;
return calculatedAngle;
}
It return positive value in degrees for any quadrant: 0 <= x < 360
You can checkout my utility class here
The formula for getting the angle is tan a = (slope1-slope2)/(1+slope1*slope2)
You are using:
tan a = (slope1 - slope2) / (1 - slope1 * slope2)
So it should be:
double angle = Math.atan((slope1 - slope2) / (1 + slope1 * slope2));
First, are you sure the brackets are in the right order? I think (could be wrong) it should be this:
double slope1 = (line1.getY1() - line1.getY2()) / (line1.getX1() - line1.getX2());
double slope2 = (line2.getY1() - line2.getY2()) / (line2.getX1() - line2.getX2());
Second, there are two things you could do for the div by zero: you could catch the exception and handle it
double angle;
try
{
angle = Math.atan((slope1 - slope2) / (1 - (slope1 * slope2)));
catch (DivideByZeroException dbze)
{
//Do something about it!
}
...or you could check that your divisors are never zero before you attempt the operation.
if ((1 - (slope1 * slope2))==0)
{
return /*something meaningful to avoid the div by zero*/
}
else
{
double angle = Math.atan((slope1 - slope2) / (1 - (slope1 * slope2)));
return angle;
}
Check this Python code:
import math
def angle(x1,y1,x2,y2,x3,y3):
if (x1==x2==x3 or y1==y2==y3):
return 180
else:
dx1 = x2-x1
dy1 = y2-y1
dx2 = x3-x2
dy2 = y3-y2
if x1==x2:
a1=90
else:
m1=dy1/dx1
a1=math.degrees(math.atan(m1))
if x2==x3:
a2=90
else:
m2=dy2/dx2
a2=math.degrees(math.atan(m2))
angle = abs(a2-a1)
return angle
print angle(0,4,0,0,9,-6)
dx1=x2-x1 ; dy1=y2-y1 ; dx2=x4-x3 ;dy2=y4-y3.
Angle(L1,L2)=pi()/2*((1+sign(dx1))* (1-sign(dy1^2))-(1+sign(dx2))*(1-sign(dy2^2)))
+pi()/4*((2+sign(dx1))*sign(dy1)-(2+sign(dx2))*sign(dy2))
+sign(dx1*dy1)*atan((abs(dx1)-abs(dy1))/(abs(dx1)+abs(dy1)))
-sign(dx2*dy2)*atan((abs(dx2)-abs(dy2))/(abs(dx2)+abs(dy2)))

Snap point to a line

I have two GPS coordinates which link together to make a line. I also have a GPS point which is near to, but never exactly on, the line. My question is, how do I find the nearest point along the line to the given point?
Game Dev has an answer to this, it is in C++ but it should be easy to port over. Which CarlG has kindly done (hopefully he does not mind me reposting):
public static Point2D nearestPointOnLine(double ax, double ay, double bx, double by, double px, double py,
boolean clampToSegment, Point2D dest) {
// Thanks StackOverflow!
// https://stackoverflow.com/questions/1459368/snap-point-to-a-line-java
if (dest == null) {
dest = new Point2D.Double();
}
double apx = px - ax;
double apy = py - ay;
double abx = bx - ax;
double aby = by - ay;
double ab2 = abx * abx + aby * aby;
double ap_ab = apx * abx + apy * aby;
double t = ap_ab / ab2;
if (clampToSegment) {
if (t < 0) {
t = 0;
} else if (t > 1) {
t = 1;
}
}
dest.setLocation(ax + abx * t, ay + aby * t);
return dest;
}
Try this:
ratio = (((x1-x0)^2+(y1-y0)^2)*((x2-x1)^2 + (y2-y1)^2) - ((x2-x1)(y1-y0) - (x1-x0)(y2-y1))^2)^0.5
-----------------------------------------------------------------------------------------
((x2-x1)^2 + (y2-y1)^2)
xc = x1 + (x2-x1)*ratio;
yc = y1 + (y2-y1)*ratio;
Where:
x1,y1 = point#1 on the line
x2,y2 = point#2 on the line
x0,y0 = Another point near the line
xc,yx = The nearest point of x0,y0 on the line
ratio = is the ratio of distance of x1,y1 to xc,yc and distance of x1,y1 to x2,y2
^2 = square
^0.5 = square root
The formular is derived after we find the distant from point x0,y0 to line (x1,y1 -> x2,y3).
See here
I've test this code here (this particular one I gave you above) but I've used it similar method years ago and it work so you may try.
You can use JTS for that.
Create a LineSegment (your line)
Create a Coordinate (the point you want to snap to the line)
Get Point on the line by using the closestPoint method
Very simple code example:
// create Line: P1(0,0) - P2(0,10)
LineSegment ls = new LineSegment(0, 0, 0, 10);
// create Point: P3(5,5)
Coordinate c = new Coordinate(5, 5);
// create snapped Point: P4(0,5)
Coordinate snappedPoint = ls.closestPoint(c);

Categories