/*
* 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.io.Serializable;
import javax.vecmath.MismatchedSizeException;
import javax.vecmath.Point3d;
import org.opengis.util.Cloneable;
/**
* Equation of a plane in a three-dimensional space (<var>x</var>,<var>y</var>,<var>z</var>).
* The plane equation is expressed by {@link #c}, {@link #cx} and {@link #cy} coefficients as
* below:
*
* <blockquote>
* <var>z</var>(<var>x</var>,<var>y</var>) = <var>c</var> +
* <var>cx</var>*<var>x</var> + <var>cy</var>*<var>y</var>
* </blockquote>
*
* Those coefficients can be set directly, or computed by a linear regression of this plane
* through a set of three-dimensional points.
*
* @since 2.0
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (PMO, IRD)
* @author Howard Freeland, for algorithmic inspiration
*/
public class Plane implements Cloneable, Serializable {
/**
* Serial number for compatibility with different versions.
*/
private static final long serialVersionUID = 2956201711131316723L;
/**
* The <var>c</var> coefficient for this plane. This coefficient appears in the place equation
* <var><strong>c</strong></var>+<var>cx</var>*<var>x</var>+<var>cy</var>*<var>y</var>.
*/
public double c;
/**
* The <var>cx</var> coefficient for this plane. This coefficient appears in the place equation
* <var>c</var>+<var><strong>cx</strong></var>*<var>x</var>+<var>cy</var>*<var>y</var>.
*/
public double cx;
/**
* The <var>cy</var> coefficient for this plane. This coefficient appears in the place equation
* <var>c</var>+<var>cx</var>*<var>x</var>+<var><strong>cy</strong></var>*<var>y</var>.
*/
public double cy;
/**
* Construct a new plane. All coefficients are set to 0.
*/
public Plane() {
}
/**
* Computes the <var>z</var> value for the specified (<var>x</var>,<var>y</var>) point.
* The <var>z</var> value is computed using the following equation:
*
* <blockquote><code>
* z(x,y) = c + cx*x + cy*y
* </code></blockquote>
*
* @param x The <var>x</var> value.
* @param y The <var>y</var> value.
* @return The <var>z</var> value.
*/
public final double z(final double x, final double y) {
return c + cx*x + cy*y;
}
/**
* Computes the <var>y</var> value for the specified (<var>x</var>,<var>z</var>) point.
* The <var>y</var> value is computed using the following equation:
*
* <blockquote><code>
* y(x,z) = (z - (c+cx*x)) / cy
* </code></blockquote>
*
* @param x The <var>x</var> value.
* @param z The <var>y</var> value.
* @return The <var>y</var> value.
*/
public final double y(final double x, final double z) {
return (z - (c+cx*x)) / cy;
}
/**
* Computes the <var>x</var> value for the specified (<var>y</var>,<var>z</var>) point.
* The <var>x</var> value is computed using the following equation:
*
* <blockquote><code>
* x(y,z) = (z - (c+cy*y)) / cx
* </code></blockquote>
*
* @param y The <var>x</var> value.
* @param z The <var>y</var> value.
* @return The <var>x</var> value.
*/
public final double x(final double y, final double z) {
return (z - (c+cy*y)) / cx;
}
/**
* Computes the plane's coefficients from the specified points. Three points
* are enough for determining exactly the plan, providing that the points are
* not colinear.
*
* @throws ArithmeticException If the three points are colinear.
*/
public void setPlane(final Point3d P1, final Point3d P2, final Point3d P3)
throws ArithmeticException
{
final double m00 = P2.x*P3.y - P3.x*P2.y;
final double m01 = P3.x*P1.y - P1.x*P3.y;
final double m02 = P1.x*P2.y - P2.x*P1.y;
final double det = m00 + m01 + m02;
if (det == 0) {
throw new ArithmeticException("Points are colinear");
}
c = (( m00 )*P1.z + ( m01 )*P2.z + ( m02 )*P3.z) / det;
cx = ((P2.y-P3.y)*P1.z + (P3.y-P1.y)*P2.z + (P1.y-P2.y)*P3.z) / det;
cy = ((P3.x-P2.x)*P1.z + (P1.x-P3.x)*P2.z + (P2.x-P1.x)*P3.z) / det;
}
/**
* Computes the plane's coefficients from a set of points. This method use
* a linear regression in the least-square sense. Result is undertermined
* if all points are colinear.
*
* @param x vector of <var>x</var> coordinates
* @param y vector of <var>y</var> coordinates
* @param z vector of <var>z</var> values
*
* @throws MismatchedSizeException if <var>x</var>, <var>y</var> and <var>z</var>
* don't have the same length.
*/
public void setPlane(final double[] x, final double[] y, final double[] z)
throws MismatchedSizeException
{
final int N = x.length;
if (N!=y.length || N!=z.length) {
throw new MismatchedSizeException();
}
double sum_x = 0;
double sum_y = 0;
double sum_z = 0;
double sum_xx = 0;
double sum_yy = 0;
double sum_xy = 0;
double sum_zx = 0;
double sum_zy = 0;
for (int i=0; i<N; i++) {
final double xi = x[i];
final double yi = y[i];
final double zi = z[i];
sum_x += xi;
sum_y += yi;
sum_z += zi;
sum_xx += xi*xi;
sum_yy += yi*yi;
sum_xy += xi*yi;
sum_zx += zi*xi;
sum_zy += zi*yi;
}
/*
* ( sum_zx - sum_z*sum_x ) = cx*(sum_xx - sum_x*sum_x) + cy*(sum_xy - sum_x*sum_y)
* ( sum_zy - sum_z*sum_y ) = cx*(sum_xy - sum_x*sum_y) + cy*(sum_yy - sum_y*sum_y)
*/
final double ZX = sum_zx - sum_z*sum_x/N;
final double ZY = sum_zy - sum_z*sum_y/N;
final double XX = sum_xx - sum_x*sum_x/N;
final double XY = sum_xy - sum_x*sum_y/N;
final double YY = sum_yy - sum_y*sum_y/N;
final double den= (XY*XY - XX*YY);
cy = (ZX*XY - ZY*XX) / den;
cx = (ZY*XY - ZX*YY) / den;
c = (sum_z - (cx*sum_x + cy*sum_y)) / N;
}
/**
* Returns a string representation of this plane.
* The string will contains the plane's equation, as below:
*
* <blockquote>
* <var>z</var>(<var>x</var>,<var>y</var>) = {@link #c} +
* {@link #cx}*<var>x</var> + {@link #cy}*<var>y</var>
* </blockquote>
*/
@Override
public String toString() {
final StringBuilder buffer = new StringBuilder("z(x,y)= ");
if (c==0 && cx==0 && cy==0) {
buffer.append(0);
} else {
if (c != 0) {
buffer.append(c).append(" + ");
}
if (cx != 0) {
buffer.append(cx).append("*x");
if (cy != 0) {
buffer.append(" + ");
}
}
if (cy != 0) {
buffer.append(cy).append("*y");
}
}
return buffer.toString();
}
/**
* Compares this plane with the specified object for equality.
*/
@Override
public boolean equals(final Object object) {
if (object!=null && getClass().equals(object.getClass())) {
final Plane that = (Plane) object;
return Double.doubleToLongBits(this.c ) == Double.doubleToLongBits(that.c ) &&
Double.doubleToLongBits(this.cx) == Double.doubleToLongBits(that.cx) &&
Double.doubleToLongBits(this.cy) == Double.doubleToLongBits(that.cy);
} else {
return false;
}
}
/**
* Returns a hash code value for this plane.
*/
@Override
public int hashCode() {
final long code = Double.doubleToLongBits(c ) +
37*(Double.doubleToLongBits(cx) +
37*(Double.doubleToLongBits(cy)));
return (int) code ^ (int) (code >>> 32);
}
/**
* Returns a clone of this plane.
*/
@Override
public Plane clone() {
try {
return (Plane) super.clone();
} catch (CloneNotSupportedException exception) {
throw new AssertionError(exception);
}
}
}