package com.tilusnet.josm.plugins.alignways.geometry; import java.util.Objects; /** * @author tilusnet <tilusnet@gmail.com> * */ public class AlignWaysGeomLine { double coef_a, coef_b, coef_c; public enum IntersectionStatus { UNDEFINED, INTERSECT_POINT, LINES_PARALLEL, LINES_OVERLAP } IntersectionStatus isectStat = IntersectionStatus.UNDEFINED; /** * Constructor defining the line with the coordinates of two points. * @param x1 x coordinate of point 1. * @param y1 y coordinate of point 1. * @param x2 x coordinate of point 2. * @param y2 y coordinate of point 2. */ public AlignWaysGeomLine(double x1, double y1, double x2, double y2) { // ax + by + c = 0 // // y2 - y1 // a = ------- (the slope); b = -1; c = y1 - a*x1 // x2 - x1 // // See conversion guidelines: http://www.webmath.com/equline1.html if (x1 == x2) { // Vertical line (would result div by zero if equation applied) coef_a = 1; coef_b = 0; coef_c = -x1; } else { coef_a = ((y2 - y1)/(x2 - x1)); coef_b = -1; coef_c = y1 - coef_a * x1; } } /** * Constructor defining the line with the 3 coefficients of its equation, i.e. ax + by + c = 0. * @param a Coefficient a. * @param b Coefficient b. * @param c Coefficient c. */ public AlignWaysGeomLine(double a, double b, double c) { coef_a = a; coef_b = b; coef_c = c; } /** * Constructor defining the line with the slope m and the y-intercept b, i.e. y = mx + b; * @param m Slope. * @param b Y-intercept. */ public AlignWaysGeomLine(double m, double b) { coef_a = m; coef_b = -1; coef_c = b; } public AlignWaysGeomLine(AlignWaysGeomLine line) { this(line.coef_a, line.coef_b, line.coef_c); } public AlignWaysGeomLine(AlignWaysGeomPoint awPt1, AlignWaysGeomPoint awPt2) { this(awPt1.getX(), awPt1.getY(), awPt2.getX(), awPt2.getY()); } /** * Returns the intersection point of the line with another line. * If the lines are parallel or overlap, returns null. * Use getIntersectionStatus() to determine the case. * @param other_line The other line. * @return The intersection point of the lines. */ public AlignWaysGeomPoint getIntersection(AlignWaysGeomLine other_line) { AlignWaysGeomPoint result = null; // Use Cramer-rule, i.e.: // - if (det1 != 0), there is an intersection in a point // - if (det1 == 0, det2 == 0, det3 == 0), the lines overlap // - if (det1 == 0) and any of det2 or det3 != 0, the lines are parallel // See: http://www.mathwizz.com/algebra/help/help21.htm // and https://en.wikipedia.org/wiki/Cramer%27s_rule double det1 = (coef_a * other_line.coef_b) - (other_line.coef_a * coef_b); double det2 = (-coef_c * other_line.coef_b) - (-other_line.coef_c * coef_b); double det3 = (coef_a * -other_line.coef_c) - (other_line.coef_a * -coef_c); if (Math.abs(det1) < 0.01) { if ((Math.abs(det2) < 0.01) && (Math.abs(det3) < 0.01)) { // Lines overlap isectStat = IntersectionStatus.LINES_OVERLAP; } else { // Lines are parallel isectStat = IntersectionStatus.LINES_PARALLEL; } } else { // Lines intersect in a point result = new AlignWaysGeomPoint(det2/det1, det3/det1); isectStat = IntersectionStatus.INTERSECT_POINT; } return result; } /** * Return the last result of getIntersection(), therefore use in conjuction with getIntersection(). * If getIntersection() was never called before, it returns IntersectionStatus.UNDEFINED. * @return The last result of getIntersection(). */ public IntersectionStatus getIntersectionStatus() { return isectStat; } /** * Get the Y coordinate on the line of a point with the given X coordinate. * * @param x The x-coordinate of the given point. * @return The calculated y-coordinate or Double.NaN if the line is vertical. */ public Double getYonLine(double x) { Double y = Double.valueOf((-coef_a*x - coef_c)/coef_b); if (y.isInfinite() || y.isNaN()) // Vertical line return Double.NaN; else return y; } /** * Get the X coordinate on the line of a point with the given Y coordinate. * * @param y The y-coordinate of the given point. * @return The calculated x-coordinate or Double.NaN if the line is horizontal. */ public Double getXonLine(double y) { Double x = Double.valueOf((-coef_b*y - coef_c)/coef_a); if (x.isInfinite() || x.isNaN()) // Horizontal line return Double.NaN; else return x; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof AlignWaysGeomLine)) return false; AlignWaysGeomLine other = (AlignWaysGeomLine) obj; if (Math.abs(this.coef_a - other.coef_a) < 0.01 && Math.abs(this.coef_b - other.coef_b) < 0.01 && Math.abs(this.coef_c - other.coef_c) < 0.01) return true; else return false; } @Override public int hashCode() { return Objects.hash(coef_a, coef_b, coef_c); } public boolean isPointOnLine(AlignWaysGeomPoint awPt) { // Method: // 1. create a new line from awPt and one point of 'this' // 2. check getIntersectionStatus of the two lines // 3. if status is LINES_OVERLAP, the point os one the line, otherwise not // Need an arbitrary point on this line; let it be (x, y) Double x = 0.0; Double y = getYonLine(x); if (y.isNaN()) y = 0.0; AlignWaysGeomLine line2 = new AlignWaysGeomLine(awPt, new AlignWaysGeomPoint(x, y)); getIntersection(line2); if (getIntersectionStatus() == IntersectionStatus.LINES_OVERLAP) return true; else return false; } }