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    }