/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 1998-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.math; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.io.Serializable; import javax.vecmath.MismatchedSizeException; import org.opengis.util.Cloneable; /** * Equation of a line in a two dimensional space (<var>x</var>,<var>y</var>). * A line has an equation of the form <var>y</var>=<var>a</var><var>x</var>+<var>b</var>. * At the difference of {@link Line2D} (which are bounded by (<var>x1</var>,<var>y1</var>) * and (<var>x2</var>,<var>y2</var>) points), {@code Line} objects extends toward infinity. * * The equation parameters for a {@code Line} object can bet set at construction * time or using one of the {@code setLine(...)} methods. The <var>y</var> value * can be computed for a given <var>x</var> value using the {@link #y} method. Method * {@link #x} compute the converse and should work even if the line is vertical. * * @since 2.0 * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (PMO, IRD) * * @see Point2D * @see Line2D * @see Plane */ public class Line implements Cloneable, Serializable { /** * Serial number for compatibility with different versions. */ private static final long serialVersionUID = 2185952238314399110L; /** * Small value for rounding errors. */ private static final double EPS = 1E-12; /** * The slope for this line. */ private double slope; /** * The <var>y</var> value at <var>x</var>==0. */ private double y0; /** * Value of <var>x</var> at <var>y</var>==0. * This value is used for vertical lines. */ private double x0; /** * Construct an initially unitialized line. All methods will returns {@link Double#NaN}. */ public Line() { slope = y0 = x0 = Double.NaN; } /** * Construct a line with the specified slope and offset. * The linear equation will be <var>y</var>=<var>slope</var>*<var>x</var>+<var>y0</var>. * * @param slope The slope. * @param y0 The <var>y</var> value at <var>x</var>==0. * * @see #setLine(double, double) */ public Line(final double slope, final double y0) { this.slope = slope; this.y0 = y0; this.x0 = -y0/slope; } /** * Sets the slope and offset for this line. * The linear equation will be <var>y</var>=<var>slope</var>*<var>x</var>+<var>y0</var>. * * @param slope The slope. * @param y0 The <var>y</var> value at <var>x</var>==0. * * @see #setLine(Point2D, Point2D) * @see #setLine(Line2D) * @see #setLine(double[], double[]) */ public void setLine(final double slope, final double y0) { this.slope = slope; this.y0 = y0; this.x0 = -y0/slope; } /** * Sets a line colinear with the specified line segment. The line will continues * toward infinity after the line segment extremities. * * @param line The line segment. * * @see #setLine(Point2D,Point2D) */ public void setLine(final Line2D line) { setLine(line.getX1(), line.getY1(), line.getX2(), line.getY2()); } /** * Sets a line through the specified point. The line will continues * toward infinity after the point. * * @param p1 Coordinate of the first point. * @param p2 Coordinate of the second point. * * @see #setLine(Line2D) */ public void setLine(final Point2D p1, final Point2D p2) { setLine(p1.getX(), p1.getY(), p2.getX(), p2.getY()); } /** * Sets a line through the specified point. The line will continues * toward infinity after the point. * * @param x1 Ordinate <var>x</var> of the first point. * @param y1 Ordinate <var>y</var> of the first point. * @param x2 Ordinate <var>x</var> of the second point. * @param y2 Ordinate <var>y</var> of the second point. * * @see #setLine(Point2D,Point2D) * @see #setLine(Line2D) */ private void setLine(final double x1, final double y1, final double x2, final double y2) { this.slope = (y2-y1)/(x2-x1); this.x0 = x2 - y2/slope; this.y0 = y2 - slope*x2; if (Double.isNaN(x0) && slope==0) { // Occurs for horizontal lines right on the x axis. x0 = Double.POSITIVE_INFINITY; } if (Double.isNaN(y0) && Double.isInfinite(slope)) { // Occurs for vertical lines right on the y axis. y0 = Double.POSITIVE_INFINITY; } } /** * Given a set of data points {@code x[0..ndata-1]}, {@code y[0..ndata-1]}, * fit them to a straight line <var>y</var>=<var>b</var>+<var>m</var><var>x</var> in * a least-squares senses. This method assume that the <var>x</var> values are precise * and all uncertainty is in <var>y</var>. * * <p>Reference: <a * href="http://shakti.cc.trincoll.edu/~palladin/courses/ENGR431/statistics/node9.html">Linear * Regression Curve Fitting</a>. * * @param x Vector of <var>x</var> values (independant variable). * @param y Vector of <var>y</var> values (dependant variable). * @return Estimation of the correlation coefficient. The closer * this coefficient is to 1, the better the fit. * * @throws MismatchedSizeException if <var>x</var> and <var>y</var> don't have the same length. */ public double setLine(final double[] x, final double[] y) throws MismatchedSizeException { final int N = x.length; if (N != y.length) { throw new MismatchedSizeException(); } int count = 0; double mean_x = 0; double mean_y = 0; for (int i=0; i<N; i++) { final double xi = x[i]; final double yi = y[i]; if (!Double.isNaN(xi) && !Double.isNaN(yi)) { mean_x += xi; mean_y += yi; count++; } } mean_x /= count; mean_y /= count; /* * We have to solve two equations with two unknows: * * 1) mean(y) = b + m*mean(x) * 2) mean(xy) = b*mean(x) + m*mean(x²) * * Those formulas lead to a quadratic equation. However, * the formulas become very simples if we set 'mean(x)=0'. * We can achieve this result by computing instead of (2): * * 2b) mean(dx y) = m*mean(dx²) * * where dx=x-mean(x). In this case mean(dx)==0. */ double mean_x2 = 0; double mean_y2 = 0; double mean_xy = 0; for (int i=0; i<N; i++) { double xi = x[i]; double yi = y[i]; if (!Double.isNaN(xi) && !Double.isNaN(yi)) { xi -= mean_x; mean_x2 += xi*xi; mean_y2 += yi*yi; mean_xy += xi*yi; } } mean_x2 /= count; mean_y2 /= count; mean_xy /= count; /* * Assuming that 'mean(x)==0', then the correlation * coefficient can be approximate by: * * R = mean(xy) / sqrt( mean(x²) * (mean(y²) - mean(y)²) ) */ slope = mean_xy/mean_x2; y0 = mean_y-mean_x*slope; return mean_xy/Math.sqrt(mean_x2 * (mean_y2-mean_y*mean_y)); } /** * Translates the line. The slope stay unchanged. * * @param dx The horizontal translation. * @param dy The vertical translation. */ public void translate(final double dx, final double dy) { if (slope==0 || Double.isInfinite(slope)) { x0 += dx; y0 += dy; } else { x0 += dx - dy/slope; y0 += dy - slope*dx; } } /** * Computes <var>y</var>=<var>f</var>(<var>x</var>). * If the line is vertical, then this method returns an infinite value. * This method is final for performance reason. * * @param x The <var>x</var> value. * @return The <var>y</var> value. * * @see #x(double) */ public final double y(final double x) { return slope*x + y0; } /** * Computes <var>x</var>=<var>f</var><sup>-1</sup>(<var>y</var>). * If the line is horizontal, then this method returns an infinite value. * This method is final for performance reason. * * @param y The <var>y</var> value. * @return The <var>x</var> value. * * @see #y(double) */ public final double x(final double y) { return y/slope + x0; } /** * Returns the <var>y</var> value for <var>x</var>==0. * Coordinate (0, <var>y0</var>) is the intersection point with the <var>y</var> axis. */ public final double getY0() { return y0; } /** * Returns the <var>x</var> value for <var>y</var>==0. * Coordinate (<var>x0</var>,0) is the intersection point with the <var>x</var> axis. */ public final double getX0() { return x0; } /** * Returns the slope. */ public final double getSlope() { return slope; } /** * Returns the intersection point between this line and the specified one. * If both lines are parallel, then this method returns {@code null}. * * @param line The line to intersect. * @return The intersection point, or {@code null}. */ public Point2D intersectionPoint(final Line line) { double x, y; if (Double.isInfinite(slope)) { if (Double.isInfinite(line.slope)) { return null; } x = x0; y = x*line.slope + line.y0; } else { if (!Double.isInfinite(line.slope)) { x = (y0-line.y0) / (line.slope-slope); if (Double.isInfinite(x)) { return null; } } else { x = line.x0; } y = x*slope + y0; } return new Point2D.Double(x,y); } /** * Returns the intersection point between this line and the specified bounded line. * If both lines are parallel or if the specified {@code line} doesn't reach * this line (since {@link Line2D} do not extends toward infinities), then this * method returns {@code null}. * * @param line The bounded line to intersect. * @return The intersection point, or {@code null}. */ public Point2D intersectionPoint(final Line2D line) { final double x1 = line.getX1(); final double y1 = line.getY1(); final double x2 = line.getX2(); final double y2 = line.getY2(); double x,y; double m = (y2-y1)/(x2-x1); if (Double.isInfinite(slope)) { x = x0; y = x*m + (y2-m*x2); } else { if (!Double.isInfinite(m)) { x = (y0-(y2-m*x2)) / (m-slope); } else { x = 0.5*(x1+x2); } y = x*slope + y0; } double eps; /* * Ensures that the intersection is in the range of valid x values. */ eps = EPS*Math.abs(x); if (x1 <= x2) { if (!(x>=x1-eps && x<=x2+eps)) { return null; } } else { if (!(x<=x1+eps && x>=x2-eps)) { return null; } } /* * Ensures that the intersection is in the range of valid y values. */ eps = EPS*Math.abs(y); if (y1 <= y2) { if (!(y>=y1-eps && y<=y2+eps)) { return null; } } else { if (!(y<=y1-eps && y>=y2+eps)) { return null; } } return new Point2D.Double(x,y); } /** * Returns the nearest point on this line from the specified point. * * @param point An arbitrary point. * @return The point on this line which is the nearest of the specified {@code point}. */ public Point2D nearestColinearPoint(final Point2D point) { if (!Double.isInfinite(slope)) { final double x = ((point.getY()-y0)*slope + point.getX()) / (slope*slope+1); return new Point2D.Double(x, x*slope+y0); } else { return new Point2D.Double(x0, point.getY()); } } /** * Computes the base of a isosceles triangle having the specified summit and side length. * The base will be colinear with this line. In other words, this method compute two * points (<var>x1</var>,<var>y1</var>) and (<var>x2</var>,<var>y2</var>) located in * such a way that: * <ul> * <li>Both points are on this line.</li> * <li>The distance between any of the two points and the specified {@code summit} * is exactly {@code sideLength}.</li> * </ul> * * @param summit The summit of the isosceles triangle. * @param sideLength The length for the two sides of the isosceles triangle. * @return The base of the isoscele triangle, colinear with this line, or {@code null} * if the base can't be computed. If non-null, then the triangle is the figure formed * by joining (<var>x1</var>,<var>y1</var>), (<var>x2</var>,<var>y2</var>) and * {@code summit}. */ public Line2D isoscelesTriangleBase(final Point2D summit, double sideLength) { sideLength *= sideLength; if (slope == 0) { final double x = summit.getX(); final double dy = y0-summit.getY(); final double dx = Math.sqrt(sideLength - dy*dy); if (Double.isNaN(dx)) { return null; } return new Line2D.Double(x+dx, y0, x-dx, y0); } if (Double.isInfinite(slope)) { final double y = summit.getY(); final double dx = x0-summit.getX(); final double dy = Math.sqrt(sideLength - dx*dx); if (Double.isNaN(dy)) { return null; } return new Line2D.Double(x0, y+dy, x0, y-dy); } final double x = summit.getX(); final double y = summit.getY(); final double dy = y0 - y + slope*x; final double B = -slope*dy; final double A = slope*slope + 1; final double C = Math.sqrt(B*B + A*(sideLength - dy*dy)); if (Double.isNaN(C)) { return null; } final double x1 = (B+C)/A + x; final double x2 = (B-C)/A + x; return new Line2D.Double(x1, slope*x1+y0, x2, slope*x2+y0); } /** * Returns a string representation of this line. This method returns * the linear equation in the form <code>"y=m*x+b"</code>. * * @return A string representation of this line. */ @Override public String toString() { if (!Double.isInfinite(slope)) { StringBuilder buffer = new StringBuilder("y= "); if (slope != 0) { buffer.append(slope).append("*x"); if (y0 != 0) { buffer.append(" + "); } else { return buffer.toString(); } } return buffer.append(y0).toString(); } else { return "x= " + x0; } } /** * Compares this object with the specified one for equality. */ @Override public boolean equals(final Object object) { if (object!=null && getClass().equals(object.getClass())) { final Line that = (Line) object; return Double.doubleToLongBits(this.slope) == Double.doubleToLongBits(that.slope) && Double.doubleToLongBits(this.y0 ) == Double.doubleToLongBits(that.y0 ) && Double.doubleToLongBits(this.x0 ) == Double.doubleToLongBits(that.x0 ); } else { return false; } } /** * Returns a hash code value for this line. */ @Override public int hashCode() { final long code = Double.doubleToLongBits(slope) + 37*Double.doubleToLongBits(y0); return (int) code ^ (int) (code >>> 32); } /** * Returns a clone of this line. */ @Override public Line clone() { try { return (Line) super.clone(); } catch (CloneNotSupportedException exception) { throw new AssertionError(exception); } } }