001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/model/csct/resources/OpenGIS.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/exse/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     It has been implemented within SEAGIS - An OpenSource implementation of OpenGIS specification
012     (C) 2001, Institut de Recherche pour le D�veloppement (http://sourceforge.net/projects/seagis/)
013     SEAGIS Contacts:  Surveillance de l'Environnement Assist�e par Satellite
014     Institut de Recherche pour le D�veloppement / US-Espace
015     mailto:seasnet@teledetection.fr
016    
017    
018     This library is free software; you can redistribute it and/or
019     modify it under the terms of the GNU Lesser General Public
020     License as published by the Free Software Foundation; either
021     version 2.1 of the License, or (at your option) any later version.
022    
023     This library is distributed in the hope that it will be useful,
024     but WITHOUT ANY WARRANTY; without even the implied warranty of
025     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
026     Lesser General Public License for more details.
027    
028     You should have received a copy of the GNU Lesser General Public
029     License along with this library; if not, write to the Free Software
030     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
031    
032     Contact:
033    
034     Andreas Poth
035     lat/lon GmbH
036     Aennchenstr. 19
037     53115 Bonn
038     Germany
039     E-Mail: poth@lat-lon.de
040    
041     Klaus Greve
042     Department of Geography
043     University of Bonn
044     Meckenheimer Allee 166
045     53115 Bonn
046     Germany
047     E-Mail: klaus.greve@uni-bonn.de
048    
049     
050     ---------------------------------------------------------------------------*/
051    package org.deegree.model.csct.resources;
052    
053    // OpenGIS dependencies (SEAGIS)
054    import java.awt.geom.Point2D;
055    import java.awt.geom.Rectangle2D;
056    
057    import org.deegree.model.csct.cs.AxisInfo;
058    import org.deegree.model.csct.cs.AxisOrientation;
059    import org.deegree.model.csct.cs.CompoundCoordinateSystem;
060    import org.deegree.model.csct.cs.CoordinateSystem;
061    import org.deegree.model.csct.cs.GeographicCoordinateSystem;
062    import org.deegree.model.csct.cs.HorizontalCoordinateSystem;
063    import org.deegree.model.csct.cs.HorizontalDatum;
064    import org.deegree.model.csct.cs.TemporalCoordinateSystem;
065    import org.deegree.model.csct.cs.TemporalDatum;
066    import org.deegree.model.csct.cs.VerticalCoordinateSystem;
067    import org.deegree.model.csct.cs.VerticalDatum;
068    import org.deegree.model.csct.ct.CoordinateTransformation;
069    import org.deegree.model.csct.ct.CoordinateTransformationFactory;
070    import org.deegree.model.csct.ct.MathTransform;
071    import org.deegree.model.csct.ct.MathTransform2D;
072    import org.deegree.model.csct.ct.TransformException;
073    import org.deegree.model.csct.pt.AngleFormat;
074    import org.deegree.model.csct.pt.CoordinatePoint;
075    import org.deegree.model.csct.pt.Envelope;
076    import org.deegree.model.csct.pt.Latitude;
077    import org.deegree.model.csct.pt.Longitude;
078    import org.deegree.model.csct.pt.MismatchedDimensionException;
079    import org.deegree.model.csct.resources.css.ResourceKeys;
080    import org.deegree.model.csct.resources.css.Resources;
081    
082    /**
083     * A set of static methods working on OpenGIS objects.  Some of those methods
084     * are useful, but not really rigorous. This is why they do not appear in the
085     * "official" package, but instead in this private one. <strong>Do not rely on
086     * this API!</strong> It may change in incompatible way in any future version.
087     *
088     * @version 1.0
089     * @author Martin Desruisseaux
090     */
091    public final class OpenGIS {
092        /**
093         * Do not allow creation of
094         * instances of this class.
095         */
096        private OpenGIS() {
097        }
098    
099        /**
100         * Returns the dimension of the first axis of a particular type.
101         * For example, <code>getDimensionOf(cs,&nbsp;AxisInfo.TIME)</code>
102         * would returns the dimension number of time axis.
103         */
104        public static int getDimensionOf( final CoordinateSystem cs, final AxisInfo axis ) {
105            final int dimension = cs.getDimension();
106            final AxisOrientation orientation = axis.orientation.absolute();
107            for ( int i = 0; i < dimension; i++ )
108                if ( orientation.equals( cs.getAxis( i ).orientation.absolute() ) )
109                    return i;
110            return -1;
111        }
112    
113        /**
114         * Returns a two-dimensional coordinate system representing the two first dimensions
115         * of the specified coordinate system. If <code>cs</code> is already a two-dimensional
116         * coordinate system, then it is returned unchanged. Otherwise, if it is a
117         * {@link CompoundCoordinateSystem}, then the head coordinate system is examined.
118         *
119         * @param  cs The coordinate system.
120         * @return A two-dimensional coordinate system that represents the two first
121         *         dimensions of <code>cs</code>.
122         * @throws IllegalArgumentException if <code>cs</code> can't be reduced to
123         *         a two-coordinate system.
124         */
125        public static CoordinateSystem getCoordinateSystem2D( CoordinateSystem cs )
126                                throws IllegalArgumentException {
127            if ( cs != null ) {
128                while ( cs.getDimension() != 2 ) {
129                    if ( !( cs instanceof CompoundCoordinateSystem ) ) {
130                        throw new IllegalArgumentException(
131                                                            Resources.format(
132                                                                              ResourceKeys.ERROR_CANT_REDUCE_TO_TWO_DIMENSIONS_$1,
133                                                                              cs.getName() ) );
134                    }
135                    cs = ( (CompoundCoordinateSystem) cs ).getHeadCS();
136                }
137            }
138            return cs;
139        }
140    
141        /**
142         * Returns the first horizontal datum found in a coordinate system,
143         * or <code>null</code> if there is none. Note: in a future version,
144         * we may implement this method directly into {@link CoordinateSystem}
145         * (not sure yet if it would be a good idea).
146         */
147        public static HorizontalDatum getHorizontalDatum( final CoordinateSystem cs ) {
148            if ( cs instanceof HorizontalCoordinateSystem ) {
149                return ( (HorizontalCoordinateSystem) cs ).getHorizontalDatum();
150            }
151            if ( cs instanceof CompoundCoordinateSystem ) {
152                HorizontalDatum datum;
153                final CompoundCoordinateSystem comp = (CompoundCoordinateSystem) cs;
154                if ( ( datum = getHorizontalDatum( comp.getHeadCS() ) ) != null )
155                    return datum;
156                if ( ( datum = getHorizontalDatum( comp.getTailCS() ) ) != null )
157                    return datum;
158            }
159            return null;
160        }
161    
162        /**
163         * Returns the first vertical datum found in a coordinate system,
164         * or <code>null</code> if there is none. Note: if a future version,
165         * we may implement this method directly into {@link CoordinateSystem}
166         * (not sure yet if it would be a good idea).
167         */
168        public static VerticalDatum getVerticalDatum( final CoordinateSystem cs ) {
169            if ( cs instanceof VerticalCoordinateSystem ) {
170                return ( (VerticalCoordinateSystem) cs ).getVerticalDatum();
171            }
172            if ( cs instanceof CompoundCoordinateSystem ) {
173                VerticalDatum datum;
174                final CompoundCoordinateSystem comp = (CompoundCoordinateSystem) cs;
175                if ( ( datum = getVerticalDatum( comp.getHeadCS() ) ) != null )
176                    return datum;
177                if ( ( datum = getVerticalDatum( comp.getTailCS() ) ) != null )
178                    return datum;
179            }
180            return null;
181        }
182    
183        /**
184         * Returns the first temporal datum found in a coordinate system,
185         * or <code>null</code> if there is none. Note: if a future version,
186         * we may implement this method directly into {@link CoordinateSystem}
187         * (not sure yet if it would be a good idea).
188         */
189        public static TemporalDatum getTemporalDatum( final CoordinateSystem cs ) {
190            if ( cs instanceof TemporalCoordinateSystem ) {
191                return ( (TemporalCoordinateSystem) cs ).getTemporalDatum();
192            }
193            if ( cs instanceof CompoundCoordinateSystem ) {
194                TemporalDatum datum;
195                final CompoundCoordinateSystem comp = (CompoundCoordinateSystem) cs;
196                if ( ( datum = getTemporalDatum( comp.getHeadCS() ) ) != null )
197                    return datum;
198                if ( ( datum = getTemporalDatum( comp.getTailCS() ) ) != null )
199                    return datum;
200            }
201            return null;
202        }
203    
204        /**
205         * Transform an envelope. The transformation is only approximative.
206         *
207         * @param  transform The transform to use.
208         * @param  envelope Envelope to transform. This envelope will not be modified.
209         * @return The transformed envelope. It may not have the same number of dimensions
210         *         than the original envelope.
211         * @throws TransformException if a transform failed.
212         */
213        public static Envelope transform( final MathTransform transform, final Envelope envelope )
214                                throws TransformException {
215            final int sourceDim = transform.getDimSource();
216            //final int targetDim = transform.getDimTarget();
217            if ( envelope.getDimension() != sourceDim ) {
218                throw new MismatchedDimensionException( sourceDim, envelope.getDimension() );
219            }
220            int coordinateNumber = 0;
221            Envelope transformed = null;
222            CoordinatePoint targetPt = null;
223            final CoordinatePoint sourcePt = new CoordinatePoint( sourceDim );
224            for ( int i = sourceDim; --i >= 0; )
225                sourcePt.ord[i] = envelope.getMinimum( i );
226    
227            loop: do {
228                // Transform a point and add the transformed
229                // point to the destination envelope.
230                targetPt = transform.transform( sourcePt, targetPt );
231                if ( transformed != null )
232                    transformed.add( targetPt );
233                else
234                    transformed = new Envelope( targetPt, targetPt );
235    
236                // Get the next point's coordinate.   The 'coordinateNumber' variable should
237                // be seen as a number in base 3 where the number of digits is equals to the
238                // number of dimensions. For example, a 4-D space would have numbers ranging
239                // from "0000" to "2222". The digits are then translated into minimal, central
240                // or maximal ordinates.
241                int n = ++coordinateNumber;
242                for ( int i = sourceDim; --i >= 0; ) {
243                    switch ( n % 3 ) {
244                    case 0:
245                        sourcePt.ord[i] = envelope.getMinimum( i );
246                        n /= 3;
247                        break;
248                    case 1:
249                        sourcePt.ord[i] = envelope.getCenter( i );
250                        continue loop;
251                    case 2:
252                        sourcePt.ord[i] = envelope.getMaximum( i );
253                        continue loop;
254                    }
255                }
256                break;
257            } while ( true );
258            return transformed;
259        }
260    
261        /**
262         * Transform an envelope. The transformation is only approximative.
263         * Invoking this method is equivalent to invoking the following:
264         * <br>
265         * <pre>transform(transform, new Envelope(source)).toRectangle2D()</pre>
266         *
267         * @param  transform The transform to use. Source and target dimension must be 2.
268         * @param  source The rectangle to transform (may be <code>null</code>).
269         * @param  dest  The destination rectangle (may be <code>source</code>).
270         *         If <code>null</code>, a new rectangle will be created and returned.
271         * @return <code>dest</code>, or a new rectangle if <code>dest</code> was non-null
272         *         and <code>source</code> was null.
273         * @throws TransformException if a transform failed.
274         */
275        public static Rectangle2D transform( final MathTransform2D transform, final Rectangle2D source,
276                                            final Rectangle2D dest )
277                                throws TransformException {
278            if ( source == null ) {
279                return null;
280            }
281            double xmin = Double.POSITIVE_INFINITY;
282            double ymin = Double.POSITIVE_INFINITY;
283            double xmax = Double.NEGATIVE_INFINITY;
284            double ymax = Double.NEGATIVE_INFINITY;
285            final Point2D.Double point = new Point2D.Double();
286            for ( int i = 0; i < 8; i++ ) {
287                /*
288                 *   (0)----(5)----(1)
289                 *    |             |
290                 *   (4)           (7)
291                 *    |             |
292                 *   (2)----(6)----(3)
293                 */
294                point.x = ( i & 1 ) == 0 ? source.getMinX() : source.getMaxX();
295                point.y = ( i & 2 ) == 0 ? source.getMinY() : source.getMaxY();
296                switch ( i ) {
297                case 5: // fallthrough
298                case 6:
299                    point.x = source.getCenterX();
300                    break;
301                case 7: // fallthrough
302                case 4:
303                    point.y = source.getCenterY();
304                    break;
305                }
306                transform.transform( point, point );
307                if ( point.x < xmin )
308                    xmin = point.x;
309                if ( point.x > xmax )
310                    xmax = point.x;
311                if ( point.y < ymin )
312                    ymin = point.y;
313                if ( point.y > ymax )
314                    ymax = point.y;
315            }
316            if ( dest != null ) {
317                dest.setRect( xmin, ymin, xmax - xmin, ymax - ymin );
318                return dest;
319            }
320            return new XRectangle2D( xmin, ymin, xmax - xmin, ymax - ymin );
321        }
322    
323        /**
324         * Retourne une cha�ne de caract�res repr�sentant la r�gion g�ographique sp�cifi�e. La
325         * cha�ne retourn�e sera de la forme "45�00.00'N-50�00.00'N 30�00.00'E-40�00.00'E". Si
326         * une projection cartographique est n�cessaire pour obtenir cette repr�sentation, elle
327         * sera faite automatiquement. Cette cha�ne sert surtout � des fins de d�boguage et sa
328         * forme peut varier.
329         */
330        public static String toWGS84String( final CoordinateSystem cs, Rectangle2D bounds ) {
331            StringBuffer buffer = new StringBuffer();
332            try {
333                if ( !GeographicCoordinateSystem.WGS84.equivalents( cs ) ) {
334                    final CoordinateTransformation tr = CoordinateTransformationFactory.getDefault().createFromCoordinateSystems(
335                                                                                                                                  cs,
336                                                                                                                                  GeographicCoordinateSystem.WGS84 );
337                    bounds = transform( (MathTransform2D) tr.getMathTransform(), bounds, null );
338                }
339                final AngleFormat fmt = new AngleFormat( "DD�MM.m'" );
340                buffer = fmt.format( new Latitude( bounds.getMinY() ), buffer, null );
341                buffer.append( '-' );
342                buffer = fmt.format( new Latitude( bounds.getMaxY() ), buffer, null );
343                buffer.append( ' ' );
344                buffer = fmt.format( new Longitude( bounds.getMinX() ), buffer, null );
345                buffer.append( '-' );
346                buffer = fmt.format( new Longitude( bounds.getMaxX() ), buffer, null );
347            } catch ( TransformException exception ) {
348                buffer.append( Utilities.getShortClassName( exception ) );
349                final String message = exception.getLocalizedMessage();
350                if ( message != null ) {
351                    buffer.append( ": " );
352                    buffer.append( message );
353                }
354            }
355            return buffer.toString();
356        }
357    }