001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/model/spatialschema/SurfacePatchImpl.java $
002 /*----------------------------------------------------------------------------
003 This file is part of deegree, http://deegree.org/
004 Copyright (C) 2001-2009 by:
005 Department of Geography, University of Bonn
006 and
007 lat/lon GmbH
008
009 This library is free software; you can redistribute it and/or modify it under
010 the terms of the GNU Lesser General Public License as published by the Free
011 Software Foundation; either version 2.1 of the License, or (at your option)
012 any later version.
013 This library is distributed in the hope that it will be useful, but WITHOUT
014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016 details.
017 You should have received a copy of the GNU Lesser General Public License
018 along with this library; if not, write to the Free Software Foundation, Inc.,
019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020
021 Contact information:
022
023 lat/lon GmbH
024 Aennchenstr. 19, 53177 Bonn
025 Germany
026 http://lat-lon.de/
027
028 Department of Geography, University of Bonn
029 Prof. Dr. Klaus Greve
030 Postfach 1147, 53001 Bonn
031 Germany
032 http://www.geographie.uni-bonn.de/deegree/
033
034 e-mail: info@deegree.org
035 ----------------------------------------------------------------------------*/
036 package org.deegree.model.spatialschema;
037
038 import java.io.Serializable;
039
040 import javax.vecmath.Vector3d;
041
042 import org.deegree.model.crs.CoordinateSystem;
043
044 /**
045 * default implementation of the SurfacePatch interface from package deegree.model.spatialschema. the class is abstract because it should
046 * be specialized by derived classes <code>Polygon</code> for example
047 *
048 *
049 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
050 * @author last edited by: $Author: mschneider $
051 *
052 * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
053 */
054 public abstract class SurfacePatchImpl implements GenericSurface, SurfacePatch, Serializable {
055 /** Use serialVersionUID for interoperability. */
056 private final static long serialVersionUID = 7641735268892225180L;
057
058 protected CoordinateSystem crs = null;
059
060 protected Point centroid = null;
061
062 protected SurfaceInterpolation interpolation = null;
063
064 protected Ring exteriorRing = null;
065
066 protected Ring[] interiorRings = null;
067
068 protected double area = 0;
069
070 protected boolean valid = false;
071
072 /**
073 *
074 * @param exteriorRing
075 * @param interiorRings
076 * @param crs
077 */
078 protected SurfacePatchImpl( Ring exteriorRing, Ring[] interiorRings, CoordinateSystem crs ) {
079 this.exteriorRing = exteriorRing;
080 if ( interiorRings == null ) {
081 this.interiorRings = new Ring[0];
082 } else {
083 this.interiorRings = interiorRings;
084 }
085 this.crs = crs;
086 }
087
088 /**
089 * Creates a new SurfacePatchImpl object.
090 *
091 * @param interpolation
092 * @param exteriorRing
093 * @param interiorRings
094 * @param crs
095 *
096 * @throws GeometryException
097 */
098 protected SurfacePatchImpl( SurfaceInterpolation interpolation, Position[] exteriorRing,
099 Position[][] interiorRings, CoordinateSystem crs ) throws GeometryException {
100 this.crs = crs;
101
102 if ( ( exteriorRing == null ) || ( exteriorRing.length < 3 ) ) {
103 throw new GeometryException( "The exterior ring doesn't contains enough point!" );
104 }
105
106 // check, if the exteriorRing of the polygon is closed
107 // and if the interiorRings (if !=null) are closed
108 if ( !exteriorRing[0].equals( exteriorRing[exteriorRing.length - 1] ) ) {
109 throw new GeometryException( "The exterior ring isn't closed!" );
110 }
111
112 if ( interiorRings != null ) {
113 for ( int i = 0; i < interiorRings.length; i++ ) {
114 if ( !interiorRings[i][0].equals( interiorRings[i][interiorRings[i].length - 1] ) ) {
115 throw new GeometryException( "The interior ring " + i + " isn't closed!" );
116 }
117 }
118 }
119
120 this.interpolation = interpolation;
121 this.exteriorRing = new RingImpl( exteriorRing, crs );
122 if ( interiorRings != null ) {
123 this.interiorRings = new Ring[interiorRings.length];
124 for ( int i = 0; i < interiorRings.length; i++ ) {
125 this.interiorRings[i] = new RingImpl( interiorRings[i], crs );
126 }
127 }
128
129 setValid( false );
130 }
131
132 /**
133 * invalidates the calculated parameters of the Geometry
134 *
135 * @param valid
136 */
137 protected void setValid( boolean valid ) {
138 this.valid = valid;
139 }
140
141 /**
142 * returns true if the calculated parameters of the Geometry are valid and false if they must be recalculated
143 *
144 * @return true if the calculated parameters of the Geometry are valid and false if they must be recalculated
145 */
146 protected boolean isValid() {
147 return valid;
148 }
149
150 public SurfaceInterpolation getInterpolation() {
151 return interpolation;
152 }
153
154 public Envelope getEnvelope() {
155 return exteriorRing.getEnvelope();
156 }
157
158 public Position[] getExteriorRing() {
159 return exteriorRing.getPositions();
160 }
161
162 public Position[][] getInteriorRings() {
163 if ( interiorRings != null ) {
164 Position[][] pos = new Position[interiorRings.length][];
165 for ( int i = 0; i < pos.length; i++ ) {
166 pos[i] = interiorRings[i].getPositions();
167 }
168 return pos;
169 }
170 return new Position[0][0];
171 }
172
173 public Ring getExterior() {
174 return exteriorRing;
175 }
176
177 public Ring[] getInterior() {
178 return interiorRings;
179 }
180
181 /**
182 * @return -1
183 */
184 public double getPerimeter() {
185 return -1;
186 }
187
188 public CoordinateSystem getCoordinateSystem() {
189 return crs;
190 }
191
192 @Override
193 public boolean equals( Object other ) {
194 if ( ( other == null ) || !( other instanceof SurfacePatch ) ) {
195 return false;
196 }
197
198 // Assuming envelope cannot be null (always calculated)
199 if ( !exteriorRing.getEnvelope().equals( ( (SurfacePatch) other ).getEnvelope() ) ) {
200 return false;
201 }
202
203 // check positions of exterior ring
204 Position[] pos1 = exteriorRing.getPositions();
205 Position[] pos2 = ( (SurfacePatch) other ).getExteriorRing();
206 if ( pos1.length != pos2.length ) {
207 return false;
208 }
209 for ( int i = 0; i < pos2.length; i++ ) {
210 if ( !pos1[i].equals( pos2[i] ) ) {
211 return false;
212 }
213 }
214
215 // Assuming either can have interiorRings set to null (not checked
216 // by Constructor)
217 if ( interiorRings != null ) {
218 if ( ( (SurfacePatch) other ).getInteriorRings() == null ) {
219 return false;
220 }
221 if ( interiorRings.length != ( (SurfacePatch) other ).getInteriorRings().length ) {
222 return false;
223 }
224 for ( int i = 0; i < interiorRings.length; i++ ) {
225 // TODO
226 // correct comparing of each point considering current tolerance level
227 }
228 } else {
229 if ( ( (SurfacePatch) other ).getInteriorRings() != null ) {
230 return false;
231 }
232 }
233
234 return true;
235 }
236
237 public Point getCentroid() {
238 if ( !isValid() ) {
239 calculateParam();
240 }
241 return centroid;
242 }
243
244 public double getArea() {
245 if ( !isValid() ) {
246 calculateParam();
247 }
248 return area;
249 }
250
251 /**
252 * calculates the centroid (2D) and area (2D + 3D) of the surface patch.
253 */
254 private void calculateCentroidArea() {
255
256 double varea = calculateArea( exteriorRing.getPositions() );
257
258 Position centroid_ = calculateCentroid( exteriorRing.getPositions() );
259
260 double x = centroid_.getX();
261 double y = centroid_.getY();
262
263 x *= varea;
264 y *= varea;
265
266 double tmp = 0;
267 if ( interiorRings != null ) {
268 for ( int i = 0; i < interiorRings.length; i++ ) {
269 double dum = calculateArea( interiorRings[i].getPositions() );
270 tmp += dum;
271 Position temp = calculateCentroid( interiorRings[i].getPositions() );
272 x += ( temp.getX() * -dum );
273 y += ( temp.getY() * -dum );
274 }
275 }
276
277 area = varea - tmp;
278 centroid = new PointImpl( x / area, y / area, crs );
279
280 }
281
282 /**
283 * calculates the centroid and the area of the surface patch
284 */
285 protected void calculateParam() {
286 calculateCentroidArea();
287 setValid( true );
288 }
289
290 /**
291 * calculates the area of the surface patch 2D: taken from gems iv (modified) 3D: see
292 * http://geometryalgorithms.com/Archive/algorithm_0101/#3D%20Polygons
293 */
294 private double calculateArea( Position[] point ) {
295
296 double calcArea = 0;
297
298 // two-dimensional
299 if ( point[0].getCoordinateDimension() == 2 ) {
300 int i;
301 int j;
302 double ai;
303 double atmp = 0;
304
305 for ( i = point.length - 1, j = 0; j < point.length; i = j, j++ ) {
306 double xi = point[i].getX() - point[0].getX();
307 double yi = point[i].getY() - point[0].getY();
308 double xj = point[j].getX() - point[0].getX();
309 double yj = point[j].getY() - point[0].getY();
310 ai = ( xi * yj ) - ( xj * yi );
311 atmp += ai;
312 }
313 calcArea = Math.abs( atmp / 2 );
314
315 }
316 // three-dimensional
317 else if ( point[0].getCoordinateDimension() == 3 ) {
318
319 Vector3d planeNormal = new Vector3d();
320 planeNormal.cross( sub( point[1], point[0] ), sub( point[2], point[1] ) );
321 planeNormal.normalize();
322
323 Vector3d resultVector = new Vector3d();
324 for ( int i = 0; i < point.length - 1; ++i ) {
325 Vector3d tmp = cross( point[i], point[i + 1] );
326 resultVector.add( tmp );
327 }
328 calcArea = ( planeNormal.dot( resultVector ) ) * 0.5;
329 }
330 return calcArea;
331 }
332
333 /**
334 * calculates the centroid of the surface patch
335 * <p>
336 * taken from gems iv (modified)
337 * <p>
338 * </p>
339 * this method is only valid for the two-dimensional case.
340 *
341 * @param point
342 * @return the centroid of given positions
343 */
344 protected Position calculateCentroid( Position[] point ) {
345
346 int i;
347 int j;
348 double ai;
349 double x;
350 double y;
351 double atmp = 0;
352 double xtmp = 0;
353 double ytmp = 0;
354
355 // move points to the origin of the coordinate space
356 // (to solve precision issues)
357 double transX = point[0].getX();
358 double transY = point[0].getY();
359
360 for ( i = point.length - 1, j = 0; j < point.length; i = j, j++ ) {
361 double x1 = point[i].getX() - transX;
362 double y1 = point[i].getY() - transY;
363 double x2 = point[j].getX() - transX;
364 double y2 = point[j].getY() - transY;
365 ai = ( x1 * y2 ) - ( x2 * y1 );
366 atmp += ai;
367 xtmp += ( ( x2 + x1 ) * ai );
368 ytmp += ( ( y2 + y1 ) * ai );
369 }
370
371 if ( atmp != 0 ) {
372 x = xtmp / ( 3 * atmp ) + transX;
373 y = ytmp / ( 3 * atmp ) + transY;
374 } else {
375 x = point[0].getX();
376 y = point[0].getY();
377 }
378
379 return new PositionImpl( x, y );
380 }
381
382 @Override
383 public String toString() {
384 String ret = "SurfacePatch: ";
385 ret = "interpolation = " + interpolation + "\n";
386 ret += "exteriorRing = \n";
387 ret += ( exteriorRing + "\n" );
388 ret += ( "interiorRings = " + interiorRings + "\n" );
389 ret += ( "envelope = " + getEnvelope() + "\n" );
390 return ret;
391 }
392
393 /**
394 * this(x,y,z) = a(x,y,z) - b(x,y,z)
395 */
396 private Vector3d sub( Position a, Position b ) {
397 Vector3d result = new Vector3d( a.getX() - b.getX(), a.getY() - b.getY(), a.getZ() - b.getZ() );
398 return result;
399 }
400
401 /**
402 * this(x,y,z) = a(x,y,z) x b(x,y,z)
403 */
404 private Vector3d cross( Position a, Position b ) {
405 Vector3d result = new Vector3d( ( a.getY() * b.getZ() ) - ( a.getZ() * b.getY() ), ( a.getZ() * b.getX() )
406 - ( a.getX() * b.getZ() ),
407 ( a.getX() * b.getY() ) - ( a.getY() * b.getX() ) );
408 return result;
409 }
410 }