001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/framework/util/MapUtils.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    package org.deegree.framework.util;
044    
045    import org.deegree.framework.log.ILogger;
046    import org.deegree.framework.log.LoggerFactory;
047    import org.deegree.i18n.Messages;
048    import org.deegree.model.crs.CRSFactory;
049    import org.deegree.model.crs.CoordinateSystem;
050    import org.deegree.model.crs.GeoTransformer;
051    import org.deegree.model.spatialschema.Envelope;
052    import org.deegree.model.spatialschema.GeometryFactory;
053    import org.deegree.model.spatialschema.Position;
054    
055    /**
056     * 
057     * 
058     * @version $Revision: 9439 $
059     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
060     * @author last edited by: $Author: rbezema $
061     * 
062     * @version 1.0. $Revision: 9439 $, $Date: 2008-01-08 11:53:23 +0100 (Di, 08 Jan 2008) $
063     * 
064     * @since 2.0
065     */
066    public class MapUtils {
067    
068        private static ILogger LOG = LoggerFactory.getLogger( MapUtils.class );
069    
070        /**
071         * The Value of sqrt(2)
072         */
073        public static final double SQRT2 = 1.4142135623730950488016887242096980785;
074    
075        /**
076         * The Value of a PixelSize
077         */
078        public static final double DEFAULT_PIXEL_SIZE = 0.00028;
079    
080        /**
081         * calculates the map scale (denominator) as defined in the OGC SLD 1.0.0 specification
082         * 
083         * @param mapWidth
084         *            map width in pixel
085         * @param mapHeight
086         *            map height in pixel
087         * @param bbox
088         *            bounding box of the map
089         * @param crs
090         *            coordinate reference system of the map
091         * @param pixelSize
092         *            size of one pixel of the map measured in meter
093         * 
094         * @return a maps scale based on the diagonal size of a pixel at the center of the map in meter.
095         * @throws RuntimeException
096         */
097        public static double calcScale( int mapWidth, int mapHeight, Envelope bbox, CoordinateSystem crs, double pixelSize )
098                                throws RuntimeException {
099    
100            if ( mapWidth == 0 || mapHeight == 0 ) {
101                return 0;
102            }
103    
104            double scale = 0;
105    
106            CoordinateSystem cs = crs;
107    
108            if ( cs == null ) {
109                throw new RuntimeException( "Invalid crs: " + crs );
110            }
111    
112            try {
113                if ( "m".equals( cs.getUnits() ) ) {
114    
115                    /*
116                     * this method to calculate a maps scale as defined in OGC WMS and SLD specification
117                     * is not required for maps having a projected reference system. Direct calculation
118                     * of scale avoids uncertaincies
119                     */
120                    double bboxWidth = bbox.getWidth();
121                    double bboxHeight = bbox.getHeight();
122                    double d1 = Math.sqrt( ( mapWidth * mapWidth ) + ( mapHeight * mapHeight ) );
123                    double d2 = Math.sqrt( ( bboxWidth * bboxWidth ) + ( bboxHeight * bboxHeight ) );
124                    scale = ( d2 / d1 ) / pixelSize;
125                } else {
126    
127                    if ( !crs.getName().equalsIgnoreCase( "EPSG:4326" ) ) {
128                        // transform the bounding box of the request to EPSG:4326
129                        GeoTransformer trans = new GeoTransformer( CRSFactory.create( "EPSG:4326" ) );
130                        bbox = trans.transform( bbox, crs );
131                    }
132                    double dx = bbox.getWidth() / mapWidth;
133                    double dy = bbox.getHeight() / mapHeight;
134                    Position min = GeometryFactory.createPosition( bbox.getMin().getX() + dx * ( mapWidth / 2d - 1 ),
135                                                                   bbox.getMin().getY() + dy * ( mapHeight / 2d - 1 ) );
136                    Position max = GeometryFactory.createPosition( bbox.getMin().getX() + dx * ( mapWidth / 2d ),
137                                                                   bbox.getMin().getY() + dy * ( mapHeight / 2d ) );
138    
139                    double distance = calcDistance( min.getX(), min.getY(), max.getX(), max.getY() );
140    
141                    scale = distance * ( SQRT2 / pixelSize );
142    
143                }
144            } catch ( Exception e ) {
145                LOG.logError( e.getMessage(), e );
146                throw new RuntimeException( Messages.getMessage( "FRAMEWORK_ERROR_SCALE_CALC", e.getMessage() ) );
147            }
148    
149            return scale;
150    
151        }
152    
153        /**
154         * calculates the distance in meters between two points in EPSG:4326 coodinates. this is a
155         * convenience method assuming the world is a ball
156         * 
157         * @param lon1
158         * @param lat1
159         * @param lon2
160         * @param lat2
161         * @return the distance in meters between two points in EPSG:4326 coords
162         */
163        public static double calcDistance( double lon1, double lat1, double lon2, double lat2 ) {
164            double r = 6378.137;
165            double rad = Math.PI / 180d;
166            double cose = Math.sin( rad * lon1 ) * Math.sin( rad * lon2 ) + Math.cos( rad * lon1 ) * Math.cos( rad * lon2 )
167                          * Math.cos( rad * ( lat1 - lat2 ) );
168            double dist = r * Math.acos( cose ) * Math.cos( rad * Math.min( lat1, lat2 ) );
169    
170            // * 0.75 is just an heuristic correction factor
171            return dist * 1000 * 0.75;
172        }
173    
174        /**
175         * The method calculates a new Envelope from the <code>requestedBarValue</code> It will either
176         * zoom in or zoom out of the <code>actualBBOX<code> depending
177         * on the ratio of the <code>requestedBarValue</code> to the <code>actualBarValue</code>
178         * @param currentEnvelope current Envelope 
179         * @param currentScale the scale of the current envelope
180         * @param requestedScale requested scale value 
181         * @return a new Envelope
182         */
183        public static Envelope scaleEnvelope( Envelope currentEnvelope, double currentScale, double requestedScale ) {
184    
185            double ratio = requestedScale / currentScale;
186            double newWidth = currentEnvelope.getWidth() * ratio;
187            double newHeight = currentEnvelope.getHeight() * ratio;
188            double midX = currentEnvelope.getMin().getX() + ( currentEnvelope.getWidth() / 2d );
189            double midY = currentEnvelope.getMin().getY() + ( currentEnvelope.getHeight() / 2d );
190    
191            double minx = midX - newWidth / 2d;
192            double maxx = midX + newWidth / 2d;
193            double miny = midY - newHeight / 2d;
194            double maxy = midY + newHeight / 2d;
195    
196            return GeometryFactory.createEnvelope( minx, miny, maxx, maxy, currentEnvelope.getCoordinateSystem() );
197    
198        }
199    
200        /**
201         * This method ensures the bbox is resized (shrunk) to match the aspect ratio defined by
202         * mapHeight/mapWidth
203         * 
204         * @param bbox
205         * @param mapWith
206         * @param mapHeight
207         * @return a new bounding box with the aspect ratio given my mapHeight/mapWidth
208         */
209        public static final Envelope ensureAspectRatio( Envelope bbox, double mapWith, double mapHeight ) {
210    
211            double minx = bbox.getMin().getX();
212            double miny = bbox.getMin().getY();
213            double maxx = bbox.getMax().getX();
214            double maxy = bbox.getMax().getY();
215    
216            double dx = maxx - minx;
217            double dy = maxy - miny;
218    
219            double ratio = mapHeight / mapWith;
220    
221            if ( dx >= dy ) {
222                // height has to be corrected
223                double[] normCoords = getNormalizedCoords( dx, ratio, miny, maxy );
224                miny = normCoords[0];
225                maxy = normCoords[1];
226            } else {
227                // width has to be corrected
228                ratio = mapWith / mapHeight;
229                double[] normCoords = getNormalizedCoords( dy, ratio, minx, maxx );
230                minx = normCoords[0];
231                maxx = normCoords[1];
232            }
233            CoordinateSystem crs = bbox.getCoordinateSystem();
234    
235            return GeometryFactory.createEnvelope( minx, miny, maxx, maxy, crs );
236        }
237    
238        private static final double[] getNormalizedCoords( double normLen, double ratio, double min, double max ) {
239            double mid = ( max - min ) / 2 + min;
240            min = mid - ( normLen / 2 ) * ratio;
241            max = mid + ( normLen / 2 ) * ratio;
242            double[] newCoords = { min, max };
243            return newCoords;
244        }
245    
246    }