/* * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI * for visualizing and manipulating spatial features with geometry and attributes. * * Copyright (C) 2003 Vivid Solutions * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.vividsolutions.jump.warp; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.List; import com.vividsolutions.jts.algorithm.RobustCGAlgorithms; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LinearRing; import com.vividsolutions.jts.util.Assert; /** * A triangle, with special methods for use with BilinearInterpolatedTransform. * @see BilinearInterpolatedTransform */ public class Triangle { private static GeometryFactory factory = new GeometryFactory(); private static Point2D hasher = new Point2D.Double(); private SaalfeldCoefficients sc; private Coordinate p1; private Coordinate p2; private Coordinate p3; private int hashCode; private Envelope envelope = null; /** * Creates a Triangle. * @param p1 one vertex * @param p2 another vertex * @param p3 another vertex */ public Triangle(Coordinate p1, Coordinate p2, Coordinate p3) { this.p1 = p1; this.p2 = p2; this.p3 = p3; Assert.isTrue(!p1.equals(p2), "p1 = " + p1 + "; p2 = " + p2); Assert.isTrue(!p2.equals(p3), "p1 = " + p1 + "; p2 = " + p2); Assert.isTrue(!p3.equals(p1), "p1 = " + p1 + "; p2 = " + p2); initHashCode(); sc = saalfeldCoefficients(); } /** * Returns the first vertex. * @return the first vertex */ public Coordinate getP1() { return p1; } /** * Returns the second vertex. * @return the second vertex */ public Coordinate getP2() { return p2; } /** * Returns the third vertex. * @return the third vertex */ public Coordinate getP3() { return p3; } /** * Returns the smallest of this Triangle's three heights (as measured * perpendicularly from each side). * @return the smallest of this Triangle's three altitudes */ public double getMinHeight() { return (2 * getArea()) / getMaxSideLength(); } /** * Returns the area of the triangle. * See http://www.mathcs.emory.edu/~rudolf/math108/summ1-2-3/node7.html * @return the area of the triangle */ public double getArea() { return 0.5 * Math.abs(((p2.x - p1.x) * (p3.y - p1.y)) - ((p2.y - p1.y) * (p3.x - p1.x))); } /** * Returns the length of this Triangle's longest side. * @return the length of this Triangle's longest side */ public double getMaxSideLength() { return Math.max(Point2D.distance(p1.x, p1.y, p2.x, p2.y), Math.max(Point2D.distance(p2.x, p2.y, p3.x, p3.y), Point2D.distance(p3.x, p3.y, p1.x, p1.y))); } /** * Converts this Triangle to a JTS Geometry. * @return a LinearRing with the same vertices as this Triangle */ public LinearRing toLinearRing() { //<<TODO:IMPROVE>> Why not return a LinearRing rather than a general //Geometry? [Jon Aquino] return factory.createLinearRing(new Coordinate[] { p1, p2, p3, p1 }); } public String toString() { return toLinearRing().toString(); } private static RobustCGAlgorithms cga = new RobustCGAlgorithms(); /** * Returns whether this Triangle contains the given coordinate * @param p the point to test for containment * @return whether this Triangle contains the given coordinate */ public boolean contains(Coordinate p) { if (p.equals(p1) || p.equals(p2) || p.equals(p3)) { return true; } //Unfortunately we cannot use Saalfeld's point-in-triangle test because it //is not robust (see TriangulatorTestCase#testContains2) [Jon Aquino] //Can't simply use != because if one is 1 and the other is 0 that's OK. [Jon Aquino] if (cga.computeOrientation(p1, p2, p) == - cga.computeOrientation(p2, p3, p)) { return false; } if (cga.computeOrientation(p1, p2, p) == - cga.computeOrientation(p3, p1, p)) { return false; } return true; } /** * Returns whether this Triangle has the same vertices as the given Triangle * @param o another Triangle; otherwise, equals will return false * @return true if o is a Triangle and has the same vertices (though not * necessarily in the same order) */ public boolean equals(Object o) { if (!(o instanceof Triangle)) { return false; } Triangle other = (Triangle) o; return other.hasVertex(p1) && other.hasVertex(p2) && other.hasVertex(p3); } /** * Returns whether v is one of this Triangle's vertices. * @param v the candidate point * @return whether v is equal to one of the vertices of this Triangle */ public boolean hasVertex(Coordinate v) { return p1.equals(v) || p2.equals(v) || p3.equals(v); } public int hashCode() { return hashCode; } /** * Returns the three triangles that result from splitting this triangle at * a given point. * @param newVertex the split point, which must be inside triangle * @return three Triangles resulting from splitting this triangle at the * given Coordinate */ public List subTriangles(Coordinate newVertex) { ArrayList triangles = new ArrayList(); triangles.add(new Triangle(p1, p2, newVertex)); triangles.add(new Triangle(p2, p3, newVertex)); triangles.add(new Triangle(p3, p1, newVertex)); return triangles; } protected Coordinate min(Coordinate a, Coordinate b) { return (a.compareTo(b) < 0) ? a : b; } private void initHashCode() { Coordinate min = min(min(p1, p2), p3); hasher.setLocation(min.x, min.y); hashCode = hasher.hashCode(); } private SaalfeldCoefficients saalfeldCoefficients() { double T = ((p1.x * p2.y) + (p2.x * p3.y) + (p3.x * p1.y)) - (p3.x * p2.y) - (p2.x * p1.y) - (p1.x * p3.y); SaalfeldCoefficients sc = new SaalfeldCoefficients(); sc.A1 = (p3.x - p2.x) / T; sc.B1 = (p2.y - p3.y) / T; sc.C1 = ((p2.x * p3.y) - (p3.x * p2.y)) / T; sc.A2 = (p1.x - p3.x) / T; sc.B2 = (p3.y - p1.y) / T; sc.C2 = ((p3.x * p1.y) - (p1.x * p3.y)) / T; return sc; } /** * Converts from a Euclidean coordinate to a simplicial coordinate. * @param euclideanCoordinate the Euclidean coordinate * @return a new 3D Coordinate with the corresponding simplicial values */ public Coordinate toSimplicialCoordinate(Coordinate euclideanCoordinate) { //<<TODO>> Preserve the z-coordinate [Jon Aquino] double s1 = s1(euclideanCoordinate); double s2 = s2(euclideanCoordinate); double s3 = 1 - s1 - s2; return new Coordinate(s1, s2, s3); } /** * Converts from a simplicial coordinate to a Euclidean coordinate. * @param simplicialCoordinate the simplicial coordinate, which uses x, y, and z * @return a new Coordinate with the corresponding Euclidean values */ public Coordinate toEuclideanCoordinate(Coordinate simplicialCoordinate) { return toEuclideanCoordinate(simplicialCoordinate.x, simplicialCoordinate.y, simplicialCoordinate.z); } private Coordinate toEuclideanCoordinate(double s1, double s2, double s3) { return new Coordinate((s1 * p1.x) + (s2 * p2.x) + (s3 * p3.x), (s1 * p1.y) + (s2 * p2.y) + (s3 * p3.y)); } /** * Computes the first simplicial coordinate. * @param c a Euclidean coordinate * @return the first simplicial coordinate for the given Euclidean coordinate */ private double s1(Coordinate c) { return (sc.A1 * c.y) + (sc.B1 * c.x) + (sc.C1); } /** * Computes the second simplicial coordinate. * @param c a Euclidean coordinate * @return the second simplicial coordinate for the given Euclidean coordinate */ private double s2(Coordinate c) { return (sc.A2 * c.y) + (sc.B2 * c.x) + (sc.C2); } /** * Returns the bounds of this Triangle. * @return the smallest Envelope enclosing this Triangle */ public Envelope getEnvelope() { if (envelope == null) { envelope = new Envelope(p1, p2); envelope.expandToInclude(p3); } return envelope; } private class SaalfeldCoefficients { public double A1; public double B1; public double C1; public double A2; public double B2; public double C2; } }