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    }