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