001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/framework/util/MapUtils.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2006 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.crs.IGeoTransformer;
052    import org.deegree.model.spatialschema.Envelope;
053    import org.deegree.model.spatialschema.GeometryFactory;
054    import org.deegree.model.spatialschema.Position;
055    
056    /**
057     * 
058     *
059     * @version $Revision: 6259 $
060     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
061     * @author last edited by: $Author: bezema $
062     *
063     * @version 1.0. $Revision: 6259 $, $Date: 2007-03-20 10:15:15 +0100 (Di, 20 Mär 2007) $
064     *
065     * @since 2.0
066     */
067    public class MapUtils {
068        
069        private static ILogger LOG = LoggerFactory.getLogger( MapUtils.class );
070        
071        /**
072         * The Value of sqrt(2)
073         */
074        public static final double SQRT2 = 1.4142135623730950488016887242096980785;
075        
076        /**
077         * The Value of a PixelSize
078         */
079        public static final double DEFAULT_PIXEL_SIZE = 0.00028;
080        
081        /**
082         * calculates the map scale (denominator) as defined in the OGC SLD 1.0.0 
083         * specification
084         * 
085         * @param mapWidth map width in pixel
086         * @param mapHeight map height in pixel
087         * @param bbox bounding box of the map
088         * @param crs coordinate reference system of the map
089         * @param pixelSize size of one pixel of the map measured in meter
090         * 
091         * @return a maps scale based on the diagonal size of a pixel at the 
092         *         center of the map in meter.
093         * @throws RuntimeException 
094         */
095        public static double calcScale( int mapWidth, int mapHeight, Envelope bbox,
096                                        CoordinateSystem crs, double pixelSize )
097                                 throws RuntimeException {
098             
099             if( mapWidth == 0 || mapHeight == 0) {
100                 return 0;
101             }
102    
103             double scale = 0;
104    
105             CoordinateSystem cs = crs;
106    
107             if ( cs == null ) {
108                 throw new RuntimeException( "Invalid crs: " + crs );
109             }
110    
111             try {
112                 if ( "m".equals( cs.getUnits() ) ) {
113    
114                     /*
115                      this method to calculate a maps scale as defined in OGC WMS
116                      and SLD specification is not required for maps having a projected
117                      reference system. Direct calculation of scale avoids uncertaincies
118                     */
119    //                 double tmp = Math.pow( mapWidth, 2 ) + Math.pow( mapHeight, 2 );                
120    //                 double d1 = Math.sqrt( tmp );
121    //                 tmp = Math.pow( bbox.getWidth(), 2 ) + Math.pow( bbox.getHeight(), 2 );
122    //                 double d2 = Math.sqrt( tmp );
123    //                 scale = ( d2 / d1 ) / ( pixelSize / SQRT2 );
124                     double bboxWidth = bbox.getWidth();
125                     double bboxHeight = bbox.getHeight();
126                     double d1 = (mapWidth*mapWidth) + (mapHeight*mapHeight); //Math.sqrt( tmp );
127                     double d2 = (bboxWidth*bboxWidth) + (bboxHeight*bboxHeight);//Math.sqrt( tmp );
128                     scale = Math.sqrt( d2 / d1 ) * ( SQRT2 / pixelSize );
129    
130                 } else {
131    
132                     if ( !crs.getName().equalsIgnoreCase( "EPSG:4326" ) ) {
133                         // transform the bounding box of the request to EPSG:4326               
134                         IGeoTransformer trans = new GeoTransformer( CRSFactory.create( "EPSG:4326" ) );
135                         bbox = trans.transform( bbox, crs );
136                     }
137                     double dx = bbox.getWidth() / mapWidth;
138                     double dy = bbox.getHeight() / mapHeight;
139                     Position min = GeometryFactory.createPosition( bbox.getMin().getX()
140                                                                    + dx * ( mapWidth / 2d - 1 ),
141                                                                    bbox.getMin().getY()
142                                                                    + dy * ( mapHeight / 2d - 1 ) );
143                     Position max = GeometryFactory.createPosition( bbox.getMin().getX()
144                                                                    + dx * ( mapWidth / 2d ),
145                                                                    bbox.getMin().getY()
146                                                                    + dy * ( mapHeight / 2d ) );
147    
148                     double distance = calcDistance( min.getX(), min.getY(), max.getX(), max.getY() );
149    
150                     scale = distance * ( SQRT2 / pixelSize );
151    
152                 }
153             } catch ( Exception e ) {
154                 LOG.logError( e.getMessage(), e );
155                 throw new RuntimeException( Messages.getMessage( "FRAMEWORK_ERROR_SCALE_CALC", e.getMessage() ) );
156             }
157    
158             return scale;
159    
160         }
161    
162        /**
163         * calculates the distance in meters between two points in EPSG:4326 coodinates.
164         * this is a convenience method assuming the world is a ball
165         * @param lon1 
166         * @param lat1 
167         * @param lon2 
168         * @param lat2 
169         * @return the distance in meters between two points in EPSG:4326 coords
170         */
171        public static double calcDistance( double lon1, double lat1, double lon2, double lat2 ) {
172            double r = 6378.137;
173            double rad = Math.PI / 180d;
174            double cose = Math.sin( rad * lon1 ) * Math.sin( rad * lon2 ) + Math.cos( rad * lon1 )
175                   * Math.cos( rad * lon2 ) * Math.cos( rad * ( lat1 - lat2 ) );        
176            double dist = r * Math.acos( cose ) * Math.cos( rad * Math.min( lat1, lat2 ) ) ;
177    
178            // * 0.75 is just an heuristic correction factor
179            return dist * 1000 * 0.75;
180        }
181    
182    
183        /**
184         * The method calculates a new Envelope from the <code>requestedBarValue</code> It will either
185         * zoom in or zoom out of the <code>actualBBOX<code> depending
186         * on the ratio of the <code>requestedBarValue</code> to the <code>actualBarValue</code>
187         * @param currentEnvelope current Envelope 
188         * @param currentScale the scale of the current envelope
189         * @param requestedScale requested scale value 
190         * @return a new Envelope
191         */
192        public static Envelope scaleEnvelope( Envelope currentEnvelope, double currentScale, 
193                                              double requestedScale ) {
194    
195            double ratio = requestedScale / currentScale;
196            double newWidth = currentEnvelope.getWidth() * ratio;
197            double newHeight = currentEnvelope.getHeight() * ratio;
198            double midX = currentEnvelope.getMin().getX() + ( currentEnvelope.getWidth() / 2d );
199            double midY = currentEnvelope.getMin().getY() + ( currentEnvelope.getHeight() / 2d );
200    
201            double minx = midX - newWidth / 2d;
202            double maxx = midX + newWidth / 2d;
203            double miny = midY - newHeight / 2d;
204            double maxy = midY + newHeight / 2d;
205    
206            return GeometryFactory.createEnvelope( minx, miny, maxx, maxy,
207                                                   currentEnvelope.getCoordinateSystem() );
208    
209        }    
210        
211        /**
212         * This method ensures the bbox is resized (shrunk) to match the aspect ratio defined by
213         * mapHeight/mapWidth
214         * 
215         * @param bbox
216         * @param mapWith
217         * @param mapHeight
218         * @return a new bounding box with the aspect ratio given my mapHeight/mapWidth
219         */
220        public static final Envelope ensureAspectRatio( Envelope bbox, double mapWith, double mapHeight ) {
221    
222            double minx = bbox.getMin().getX();
223            double miny = bbox.getMin().getY();
224            double maxx = bbox.getMax().getX();
225            double maxy = bbox.getMax().getY();
226    
227            double dx = maxx - minx;
228            double dy = maxy - miny;
229    
230            double ratio = mapHeight / mapWith;
231    
232            if ( dx >= dy ) {
233                // height has to be corrected
234                double[] normCoords = getNormalizedCoords( dx, ratio, miny, maxy );
235                miny = normCoords[0];
236                maxy = normCoords[1];
237            } else {
238                // width has to be corrected
239                ratio = mapWith / mapHeight;
240                double[] normCoords = getNormalizedCoords( dy, ratio, minx, maxx );
241                minx = normCoords[0];
242                maxx = normCoords[1];
243            }
244            CoordinateSystem crs = bbox.getCoordinateSystem();
245    
246            return GeometryFactory.createEnvelope( minx, miny, maxx, maxy, crs );
247        }
248    
249        private static final double[] getNormalizedCoords( double normLen, double ratio, double min,
250                                                           double max ) {
251            double mid = ( max - min ) / 2 + min;
252            min = mid - ( normLen / 2 ) * ratio;
253            max = mid + ( normLen / 2 ) * ratio;
254            double[] newCoords = { min, max };
255            return newCoords;
256        }
257    
258    }
259    
260    /* ********************************************************************
261     Changes to this class. What the people have been up to:
262     $Log$
263     Revision 1.8  2006/12/13 16:07:06  mays
264     bugfix in scaleEnvelope: change from getWidth() to getHeight() for Y-coordinates
265    
266     Revision 1.7  2006/11/28 13:44:56  mays
267     add method ensureAspectRatio() and depending method getNormalizedCoords()
268    
269     Revision 1.6  2006/11/27 09:07:52  poth
270     JNI integration of proj4 has been removed. The CRS functionality now will be done by native deegree code.
271    
272     Revision 1.5  2006/11/26 19:02:54  poth
273     bug fix
274    
275     Revision 1.4  2006/11/26 18:48:16  poth
276     bug fix
277    
278     Revision 1.3  2006/11/23 08:25:14  bezema
279     changed the scale calculation to be faster, added a SQRT2 and a PIXELSIZE field
280    
281     Revision 1.1  2006/10/17 20:31:19  poth
282     *** empty log message ***
283    
284     Revision 1.23  2006/09/27 16:46:41  poth
285     transformation method signature changed
286    
287     Revision 1.22  2006/09/26 14:21:17  poth
288     bug fix
289    
290     Revision 1.21  2006/09/26 13:31:11  poth
291     bug fix
292    
293     Revision 1.20  2006/09/26 13:29:55  poth
294     bug fix
295    
296     Revision 1.19  2006/09/26 13:16:43  poth
297     LOG statement added
298    
299     Revision 1.18  2006/09/25 20:28:53  poth
300     bug fixes - map scale calculation
301    
302     Revision 1.17  2006/09/25 12:47:00  poth
303     bug fixes - map scale calculation
304    
305     Revision 1.16  2006/09/22 09:07:58  taddei
306     refactored: added scaleEnvelope to this class, becaus it used by many classes
307    
308     Revision 1.15  2006/09/18 12:37:22  bezema
309     fixed a division by null
310    
311     Revision 1.14  2006/07/28 09:24:39  poth
312     bug fix - calculating geographic distance
313    
314     Revision 1.13  2006/07/25 06:21:53  poth
315     bug fix - calculating map scale
316    
317     Revision 1.12  2006/07/11 13:47:52  bezema
318     changed the throws exception to throws RuntimeException
319    
320     Revision 1.11  2006/07/11 13:39:02  taddei
321     *** empty log message ***
322    
323     Revision 1.10  2006/05/31 12:19:34  poth
324     *** empty log message ***
325    
326     Revision 1.9  2006/05/31 11:57:37  poth
327     bug fix - pixel size considered
328    
329     Revision 1.8  2006/05/31 09:17:05  ncho
330     SN:  calcDistance-changed private to public
331     and added NullPointerException
332    
333     Revision 1.7  2006/05/03 20:09:52  poth
334     *** empty log message ***
335    
336     Revision 1.6  2006/05/01 20:15:27  poth
337     *** empty log message ***
338    
339     Revision 1.5  2006/04/06 20:25:28  poth
340     *** empty log message ***
341    
342     Revision 1.4  2006/03/30 21:20:27  poth
343     *** empty log message ***
344    
345     Revision 1.3  2005/08/19 07:45:11  poth
346     no message
347    
348     Revision 1.2  2005/08/05 09:42:20  poth
349     no message
350    
351     Revision 1.1  2005/07/30 18:11:20  poth
352     no message
353    
354    
355     ********************************************************************** */