001: /*
002: * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI
003: * for visualizing and manipulating spatial features with geometry and attributes.
004: *
005: * Copyright (C) 2003 Vivid Solutions
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: *
021: * For more information, contact:
022: *
023: * Vivid Solutions
024: * Suite #1A
025: * 2328 Government Street
026: * Victoria BC V8T 5G5
027: * Canada
028: *
029: * (250)385-6040
030: * www.vividsolutions.com
031: */
032:
033: package com.vividsolutions.jump.warp;
034:
035: import java.awt.geom.Point2D;
036: import java.util.ArrayList;
037: import java.util.List;
038:
039: import com.vividsolutions.jts.algorithm.RobustCGAlgorithms;
040: import com.vividsolutions.jts.geom.Coordinate;
041: import com.vividsolutions.jts.geom.Envelope;
042: import com.vividsolutions.jts.geom.GeometryFactory;
043: import com.vividsolutions.jts.geom.LinearRing;
044: import com.vividsolutions.jts.util.Assert;
045:
046: /**
047: * A triangle, with special methods for use with BilinearInterpolatedTransform.
048: * @see BilinearInterpolatedTransform
049: */
050: public class Triangle {
051: private static GeometryFactory factory = new GeometryFactory();
052: private static Point2D hasher = new Point2D.Double();
053: private SaalfeldCoefficients sc;
054: private Coordinate p1;
055: private Coordinate p2;
056: private Coordinate p3;
057: private int hashCode;
058: private Envelope envelope = null;
059:
060: /**
061: * Creates a Triangle.
062: * @param p1 one vertex
063: * @param p2 another vertex
064: * @param p3 another vertex
065: */
066: public Triangle(Coordinate p1, Coordinate p2, Coordinate p3) {
067: this .p1 = p1;
068: this .p2 = p2;
069: this .p3 = p3;
070: Assert.isTrue(!p1.equals(p2), "p1 = " + p1 + "; p2 = " + p2);
071: Assert.isTrue(!p2.equals(p3), "p1 = " + p1 + "; p2 = " + p2);
072: Assert.isTrue(!p3.equals(p1), "p1 = " + p1 + "; p2 = " + p2);
073: initHashCode();
074: sc = saalfeldCoefficients();
075: }
076:
077: /**
078: * Returns the first vertex.
079: * @return the first vertex
080: */
081: public Coordinate getP1() {
082: return p1;
083: }
084:
085: /**
086: * Returns the second vertex.
087: * @return the second vertex
088: */
089: public Coordinate getP2() {
090: return p2;
091: }
092:
093: /**
094: * Returns the third vertex.
095: * @return the third vertex
096: */
097: public Coordinate getP3() {
098: return p3;
099: }
100:
101: /**
102: * Returns the smallest of this Triangle's three heights (as measured
103: * perpendicularly from each side).
104: * @return the smallest of this Triangle's three altitudes
105: */
106: public double getMinHeight() {
107: return (2 * getArea()) / getMaxSideLength();
108: }
109:
110: /**
111: * Returns the area of the triangle.
112: * See http://www.mathcs.emory.edu/~rudolf/math108/summ1-2-3/node7.html
113: * @return the area of the triangle
114: */
115: public double getArea() {
116: return 0.5 * Math.abs(((p2.x - p1.x) * (p3.y - p1.y))
117: - ((p2.y - p1.y) * (p3.x - p1.x)));
118: }
119:
120: /**
121: * Returns the length of this Triangle's longest side.
122: * @return the length of this Triangle's longest side
123: */
124: public double getMaxSideLength() {
125: return Math.max(Point2D.distance(p1.x, p1.y, p2.x, p2.y), Math
126: .max(Point2D.distance(p2.x, p2.y, p3.x, p3.y), Point2D
127: .distance(p3.x, p3.y, p1.x, p1.y)));
128: }
129:
130: /**
131: * Converts this Triangle to a JTS Geometry.
132: * @return a LinearRing with the same vertices as this Triangle
133: */
134: public LinearRing toLinearRing() {
135: //<<TODO:IMPROVE>> Why not return a LinearRing rather than a general
136: //Geometry? [Jon Aquino]
137: return factory.createLinearRing(new Coordinate[] { p1, p2, p3,
138: p1 });
139: }
140:
141: public String toString() {
142: return toLinearRing().toString();
143: }
144:
145: private static RobustCGAlgorithms cga = new RobustCGAlgorithms();
146:
147: /**
148: * Returns whether this Triangle contains the given coordinate
149: * @param p the point to test for containment
150: * @return whether this Triangle contains the given coordinate
151: */
152: public boolean contains(Coordinate p) {
153: if (p.equals(p1) || p.equals(p2) || p.equals(p3)) {
154: return true;
155: }
156:
157: //Unfortunately we cannot use Saalfeld's point-in-triangle test because it
158: //is not robust (see TriangulatorTestCase#testContains2) [Jon Aquino]
159:
160: //Can't simply use != because if one is 1 and the other is 0 that's OK. [Jon Aquino]
161: if (cga.computeOrientation(p1, p2, p) == -cga
162: .computeOrientation(p2, p3, p)) {
163: return false;
164: }
165:
166: if (cga.computeOrientation(p1, p2, p) == -cga
167: .computeOrientation(p3, p1, p)) {
168: return false;
169: }
170:
171: return true;
172: }
173:
174: /**
175: * Returns whether this Triangle has the same vertices as the given Triangle
176: * @param o another Triangle; otherwise, equals will return false
177: * @return true if o is a Triangle and has the same vertices (though not
178: * necessarily in the same order)
179: */
180: public boolean equals(Object o) {
181: if (!(o instanceof Triangle)) {
182: return false;
183: }
184:
185: Triangle other = (Triangle) o;
186:
187: return other.hasVertex(p1) && other.hasVertex(p2)
188: && other.hasVertex(p3);
189: }
190:
191: /**
192: * Returns whether v is one of this Triangle's vertices.
193: * @param v the candidate point
194: * @return whether v is equal to one of the vertices of this Triangle
195: */
196: public boolean hasVertex(Coordinate v) {
197: return p1.equals(v) || p2.equals(v) || p3.equals(v);
198: }
199:
200: public int hashCode() {
201: return hashCode;
202: }
203:
204: /**
205: * Returns the three triangles that result from splitting this triangle at
206: * a given point.
207: * @param newVertex the split point, which must be inside triangle
208: * @return three Triangles resulting from splitting this triangle at the
209: * given Coordinate
210: */
211: public List subTriangles(Coordinate newVertex) {
212: ArrayList triangles = new ArrayList();
213: triangles.add(new Triangle(p1, p2, newVertex));
214: triangles.add(new Triangle(p2, p3, newVertex));
215: triangles.add(new Triangle(p3, p1, newVertex));
216:
217: return triangles;
218: }
219:
220: protected Coordinate min(Coordinate a, Coordinate b) {
221: return (a.compareTo(b) < 0) ? a : b;
222: }
223:
224: private void initHashCode() {
225: Coordinate min = min(min(p1, p2), p3);
226: hasher.setLocation(min.x, min.y);
227: hashCode = hasher.hashCode();
228: }
229:
230: private SaalfeldCoefficients saalfeldCoefficients() {
231: double T = ((p1.x * p2.y) + (p2.x * p3.y) + (p3.x * p1.y))
232: - (p3.x * p2.y) - (p2.x * p1.y) - (p1.x * p3.y);
233: SaalfeldCoefficients sc = new SaalfeldCoefficients();
234: sc.A1 = (p3.x - p2.x) / T;
235: sc.B1 = (p2.y - p3.y) / T;
236: sc.C1 = ((p2.x * p3.y) - (p3.x * p2.y)) / T;
237: sc.A2 = (p1.x - p3.x) / T;
238: sc.B2 = (p3.y - p1.y) / T;
239: sc.C2 = ((p3.x * p1.y) - (p1.x * p3.y)) / T;
240:
241: return sc;
242: }
243:
244: /**
245: * Converts from a Euclidean coordinate to a simplicial coordinate.
246: * @param euclideanCoordinate the Euclidean coordinate
247: * @return a new 3D Coordinate with the corresponding simplicial values
248: */
249: public Coordinate toSimplicialCoordinate(
250: Coordinate euclideanCoordinate) {
251: //<<TODO>> Preserve the z-coordinate [Jon Aquino]
252: double s1 = s1(euclideanCoordinate);
253: double s2 = s2(euclideanCoordinate);
254: double s3 = 1 - s1 - s2;
255:
256: return new Coordinate(s1, s2, s3);
257: }
258:
259: /**
260: * Converts from a simplicial coordinate to a Euclidean coordinate.
261: * @param simplicialCoordinate the simplicial coordinate, which uses x, y, and z
262: * @return a new Coordinate with the corresponding Euclidean values
263: */
264: public Coordinate toEuclideanCoordinate(
265: Coordinate simplicialCoordinate) {
266: return toEuclideanCoordinate(simplicialCoordinate.x,
267: simplicialCoordinate.y, simplicialCoordinate.z);
268: }
269:
270: private Coordinate toEuclideanCoordinate(double s1, double s2,
271: double s3) {
272: return new Coordinate((s1 * p1.x) + (s2 * p2.x) + (s3 * p3.x),
273: (s1 * p1.y) + (s2 * p2.y) + (s3 * p3.y));
274: }
275:
276: /**
277: * Computes the first simplicial coordinate.
278: * @param c a Euclidean coordinate
279: * @return the first simplicial coordinate for the given Euclidean coordinate
280: */
281: private double s1(Coordinate c) {
282: return (sc.A1 * c.y) + (sc.B1 * c.x) + (sc.C1);
283: }
284:
285: /**
286: * Computes the second simplicial coordinate.
287: * @param c a Euclidean coordinate
288: * @return the second simplicial coordinate for the given Euclidean coordinate
289: */
290: private double s2(Coordinate c) {
291: return (sc.A2 * c.y) + (sc.B2 * c.x) + (sc.C2);
292: }
293:
294: /**
295: * Returns the bounds of this Triangle.
296: * @return the smallest Envelope enclosing this Triangle
297: */
298: public Envelope getEnvelope() {
299: if (envelope == null) {
300: envelope = new Envelope(p1, p2);
301: envelope.expandToInclude(p3);
302: }
303:
304: return envelope;
305: }
306:
307: private class SaalfeldCoefficients {
308: public double A1;
309: public double B1;
310: public double C1;
311: public double A2;
312: public double B2;
313: public double C2;
314: }
315: }
|