001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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: apoth $
051     *
052     * @version $Revision: 25223 $, $Date: 2010-07-09 09:30:18 +0200 (Fr, 09 Jul 2010) $
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         * @throws GeometryException 
078         */
079        protected SurfacePatchImpl( Ring exteriorRing, Ring[] interiorRings, CoordinateSystem crs ) throws GeometryException {
080            this.exteriorRing = exteriorRing;
081            if ( interiorRings == null ) {
082                this.interiorRings = new Ring[0];
083            } else {
084                this.interiorRings = interiorRings;
085            }
086         // check, if the exteriorRing of the polygon is closed
087            // and if the interiorRings (if !=null) are closed
088            Position[] pos = this.exteriorRing.getPositions();
089            if ( !pos[0].equals(pos[pos.length - 1] ) ) {
090                throw new GeometryException( "The exterior ring isn't closed!" );
091            }
092            
093            if ( interiorRings != null ) {
094                for ( int i = 0; i < interiorRings.length; i++ ) {
095                    pos = interiorRings[i].getPositions();
096                    if ( !pos[0].equals( pos[pos.length - 1] ) ) {
097                        throw new GeometryException( "The interior ring " + i + " isn't closed!" );
098                    }
099                }
100            }
101            
102            this.crs = crs;
103        }
104    
105        /**
106         * Creates a new SurfacePatchImpl object.
107         *
108         * @param interpolation
109         * @param exteriorRing
110         * @param interiorRings
111         * @param crs
112         *
113         * @throws GeometryException
114         */
115        protected SurfacePatchImpl( SurfaceInterpolation interpolation, Position[] exteriorRing,
116                                    Position[][] interiorRings, CoordinateSystem crs ) throws GeometryException {
117            this.crs = crs;
118    
119            if ( ( exteriorRing == null ) || ( exteriorRing.length < 3 ) ) {
120                throw new GeometryException( "The exterior ring doesn't contains enough point!" );
121            }
122    
123            // check, if the exteriorRing of the polygon is closed
124            // and if the interiorRings (if !=null) are closed
125            if ( !exteriorRing[0].equals( exteriorRing[exteriorRing.length - 1] ) ) {
126                throw new GeometryException( "The exterior ring isn't closed!" );
127            }
128    
129            if ( interiorRings != null ) {
130                for ( int i = 0; i < interiorRings.length; i++ ) {
131                    if ( !interiorRings[i][0].equals( interiorRings[i][interiorRings[i].length - 1] ) ) {
132                        throw new GeometryException( "The interior ring " + i + " isn't closed!" );
133                    }
134                }
135            }
136    
137            this.interpolation = interpolation;
138            this.exteriorRing = new RingImpl( exteriorRing, crs );
139            if ( interiorRings != null ) {
140                this.interiorRings = new Ring[interiorRings.length];
141                for ( int i = 0; i < interiorRings.length; i++ ) {
142                    this.interiorRings[i] = new RingImpl( interiorRings[i], crs );
143                }
144            }
145    
146            setValid( false );
147        }
148    
149        /**
150         * invalidates the calculated parameters of the Geometry
151         *
152         * @param valid
153         */
154        protected void setValid( boolean valid ) {
155            this.valid = valid;
156        }
157    
158        /**
159         * returns true if the calculated parameters of the Geometry are valid and false if they must be recalculated
160         *
161         * @return true if the calculated parameters of the Geometry are valid and false if they must be recalculated
162         */
163        protected boolean isValid() {
164            return valid;
165        }
166    
167        public SurfaceInterpolation getInterpolation() {
168            return interpolation;
169        }
170    
171        public Envelope getEnvelope() {
172            return exteriorRing.getEnvelope();
173        }
174    
175        public Position[] getExteriorRing() {
176            return exteriorRing.getPositions();
177        }
178    
179        public Position[][] getInteriorRings() {
180            if ( interiorRings != null ) {
181                Position[][] pos = new Position[interiorRings.length][];
182                for ( int i = 0; i < pos.length; i++ ) {
183                    pos[i] = interiorRings[i].getPositions();
184                }
185                return pos;
186            }
187            return new Position[0][0];
188        }
189    
190        public Ring getExterior() {
191            return exteriorRing;
192        }
193    
194        public Ring[] getInterior() {
195            return interiorRings;
196        }
197    
198        /**
199         * @return -1
200         */
201        public double getPerimeter() {
202            return -1;
203        }
204    
205        public CoordinateSystem getCoordinateSystem() {
206            return crs;
207        }
208    
209        @Override
210        public boolean equals( Object other ) {
211            if ( ( other == null ) || !( other instanceof SurfacePatch ) ) {
212                return false;
213            }
214    
215            // Assuming envelope cannot be null (always calculated)
216            if ( !getEnvelope().equals( ( (SurfacePatch) other ).getEnvelope() ) ) {
217                return false;
218            }
219    
220            // check positions of exterior ring
221            Position[] pos1 = getExteriorRing();
222            Position[] pos2 = ( (SurfacePatch) other ).getExteriorRing();
223            if ( pos1.length != pos2.length ) {
224                return false;            
225            }
226            for ( int i = 0; i < pos2.length; i++ ) {
227                if ( !pos1[i].equals( pos2[i] ) ) {
228                    return false;
229                }
230            }
231    
232            // Assuming either can have interiorRings set to null (not checked
233            // by Constructor)
234            if ( getInterior() != null ) {
235                if ( ( (SurfacePatch) other ).getInterior() == null ) {
236                    return false;
237                }
238                if ( getInterior().length != ( (SurfacePatch) other ).getInterior().length ) {
239                    return false;
240                }
241                for ( int i = 0; i < getInterior().length; i++ ) {
242                    // TODO
243                }
244            } else {
245                if ( ( (SurfacePatch) other ).getInterior() != null ) {
246                    return false;
247                }
248            }
249    
250            return true;
251        }
252    
253        public Point getCentroid() {
254            if ( !isValid() ) {
255                calculateParam();
256            }
257            return centroid;
258        }
259    
260        public double getArea() {
261            if ( !isValid() ) {
262                calculateParam();
263            }
264            return area;
265        }
266    
267        /**
268         * calculates the centroid (2D) and area (2D + 3D) of the surface patch.
269         */
270        private void calculateCentroidArea() {
271    
272            double varea = calculateArea( exteriorRing.getPositions() );
273    
274            Position centroid_ = calculateCentroid( exteriorRing.getPositions() );
275    
276            double x = centroid_.getX();
277            double y = centroid_.getY();
278    
279            x *= varea;
280            y *= varea;
281    
282            double tmp = 0;
283            if ( interiorRings != null ) {
284                for ( int i = 0; i < interiorRings.length; i++ ) {
285                    double dum = calculateArea( interiorRings[i].getPositions() );
286                    tmp += dum;
287                    Position temp = calculateCentroid( interiorRings[i].getPositions() );
288                    x += ( temp.getX() * -dum );
289                    y += ( temp.getY() * -dum );
290                }
291            }
292    
293            area = varea - tmp;
294            centroid = new PointImpl( x / area, y / area, crs );
295    
296        }
297    
298        /**
299         * calculates the centroid and the area of the surface patch
300         */
301        protected void calculateParam() {
302            calculateCentroidArea();
303            setValid( true );
304        }
305    
306        /**
307         * calculates the area of the surface patch 2D: taken from gems iv (modified) 3D: see
308         * http://geometryalgorithms.com/Archive/algorithm_0101/#3D%20Polygons
309         */
310        private double calculateArea( Position[] point ) {
311    
312            double calcArea = 0;
313    
314            // two-dimensional
315            if ( point[0].getCoordinateDimension() == 2 ) {
316                int i;
317                int j;
318                double ai;
319                double atmp = 0;
320    
321                for ( i = point.length - 1, j = 0; j < point.length; i = j, j++ ) {
322                    double xi = point[i].getX() - point[0].getX();
323                    double yi = point[i].getY() - point[0].getY();
324                    double xj = point[j].getX() - point[0].getX();
325                    double yj = point[j].getY() - point[0].getY();
326                    ai = ( xi * yj ) - ( xj * yi );
327                    atmp += ai;
328                }
329                calcArea = Math.abs( atmp / 2 );
330    
331            }
332            // three-dimensional
333            else if ( point[0].getCoordinateDimension() == 3 ) {
334    
335                Vector3d planeNormal = new Vector3d();
336                planeNormal.cross( sub( point[1], point[0] ), sub( point[2], point[1] ) );
337                planeNormal.normalize();
338    
339                Vector3d resultVector = new Vector3d();
340                for ( int i = 0; i < point.length - 1; ++i ) {
341                    Vector3d tmp = cross( point[i], point[i + 1] );
342                    resultVector.add( tmp );
343                }
344                calcArea = ( planeNormal.dot( resultVector ) ) * 0.5;
345            }
346            return calcArea;
347        }
348    
349        /**
350         * calculates the centroid of the surface patch
351         * <p>
352         * taken from gems iv (modified)
353         * <p>
354         * </p>
355         * this method is only valid for the two-dimensional case.
356         *
357         * @param point
358         * @return the centroid of given positions
359         */
360        protected Position calculateCentroid( Position[] point ) {
361    
362            int i;
363            int j;
364            double ai;
365            double x;
366            double y;
367            double atmp = 0;
368            double xtmp = 0;
369            double ytmp = 0;
370    
371            // move points to the origin of the coordinate space
372            // (to solve precision issues)
373            double transX = point[0].getX();
374            double transY = point[0].getY();
375    
376            for ( i = point.length - 1, j = 0; j < point.length; i = j, j++ ) {
377                double x1 = point[i].getX() - transX;
378                double y1 = point[i].getY() - transY;
379                double x2 = point[j].getX() - transX;
380                double y2 = point[j].getY() - transY;
381                ai = ( x1 * y2 ) - ( x2 * y1 );
382                atmp += ai;
383                xtmp += ( ( x2 + x1 ) * ai );
384                ytmp += ( ( y2 + y1 ) * ai );
385            }
386    
387            if ( atmp != 0 ) {
388                x = xtmp / ( 3 * atmp ) + transX;
389                y = ytmp / ( 3 * atmp ) + transY;
390            } else {
391                x = point[0].getX();
392                y = point[0].getY();
393            }
394    
395            return new PositionImpl( x, y );
396        }
397    
398        @Override
399        public String toString() {
400            String ret = "SurfacePatch: ";
401            ret = "interpolation = " + interpolation + "\n";
402            ret += "exteriorRing = \n";
403            ret += ( exteriorRing + "\n" );
404            ret += ( "interiorRings = " + interiorRings + "\n" );
405            ret += ( "envelope = " + getEnvelope() + "\n" );
406            return ret;
407        }
408    
409        /**
410         * this(x,y,z) = a(x,y,z) - b(x,y,z)
411         */
412        private Vector3d sub( Position a, Position b ) {
413            Vector3d result = new Vector3d( a.getX() - b.getX(), a.getY() - b.getY(), a.getZ() - b.getZ() );
414            return result;
415        }
416    
417        /**
418         * this(x,y,z) = a(x,y,z) x b(x,y,z)
419         */
420        private Vector3d cross( Position a, Position b ) {
421            Vector3d result = new Vector3d( ( a.getY() * b.getZ() ) - ( a.getZ() * b.getY() ), ( a.getZ() * b.getX() )
422                                                                                               - ( a.getX() * b.getZ() ),
423                                            ( a.getX() * b.getY() ) - ( a.getY() * b.getX() ) );
424            return result;
425        }
426    }