    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/framework/util/MapUtils.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.framework.util;
    037
    038    import static java.lang.Math.sqrt;
    039
    040    import org.deegree.framework.log.ILogger;
    041    import org.deegree.framework.log.LoggerFactory;
    042    import org.deegree.i18n.Messages;
    043    import org.deegree.model.crs.CRSFactory;
    044    import org.deegree.model.crs.CoordinateSystem;
    045    import org.deegree.model.crs.GeoTransformer;
    046    import org.deegree.model.spatialschema.Envelope;
    047    import org.deegree.model.spatialschema.GeometryFactory;
    048    import org.deegree.model.spatialschema.Position;
    049
    050    /**
    051     *
    052     *
    053     * @version $Revision: 20692 $
    054     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
    055     * @author last edited by: $Author: apoth $
    056     *
    057     * @version 1.0. $Revision: 20692 $, $Date: 2009-11-10 13:49:55 +0100 (Di, 10. Nov 2009) $
    058     *
    059     * @since 2.0
    060     */
    061    public class MapUtils {
    062
    063        private static ILogger LOG = LoggerFactory.getLogger( MapUtils.class );
    064
    065        /**
    066         * The value of sqrt(2)
    067         */
    068        public static final double SQRT2 = sqrt( 2 );
    069
    070        /**
    071         * The Value of a PixelSize
    072         */
    073        public static final double DEFAULT_PIXEL_SIZE = 0.00028;
    074
    075        /**
    076         * @param mapWidth
    077         * @param mapHeight
    078         * @param bbox
    079         * @param crs
    080         * @return the WMS 1.1.1 scale (size of the diagonal pixel)
    081         */
    082        public static double calcScaleWMS111( int mapWidth, int mapHeight, Envelope bbox, CoordinateSystem crs ) {
    083            if ( mapWidth == 0 || mapHeight == 0 ) {
    084                return 0;
    085            }
    086            double scale = 0;
    087
    088            if ( crs == null ) {
    089                throw new RuntimeException( "Invalid crs: " + crs );
    090            }
    091
    092            try {
    093                if ( "m".equalsIgnoreCase( crs.getAxisUnits()[0].toString() ) ) {
    094                    /*
    095                     * this method to calculate a maps scale as defined in OGC WMS and SLD specification is not required for
    096                     * maps having a projected reference system. Direct calculation of scale avoids uncertainties
    097                     */
    098                    double dx = bbox.getWidth() / mapWidth;
    099                    double dy = bbox.getHeight() / mapHeight;
    100                    scale = sqrt( dx * dx + dy * dy );
    101                } else {
    102
    103                    if ( !crs.getIdentifier().equalsIgnoreCase( "EPSG:4326" ) ) {
    104                        // transform the bounding box of the request to EPSG:4326
    105                        GeoTransformer trans = new GeoTransformer( CRSFactory.create( "EPSG:4326" ) );
    106                        bbox = trans.transform( bbox, crs );
    107                    }
    108                    double dx = bbox.getWidth() / mapWidth;
    109                    double dy = bbox.getHeight() / mapHeight;
    110                    Position min = GeometryFactory.createPosition( bbox.getMin().getX() + dx * ( mapWidth / 2d - 1 ),
    111                                                                   bbox.getMin().getY() + dy * ( mapHeight / 2d - 1 ) );
    112                    Position max = GeometryFactory.createPosition( bbox.getMin().getX() + dx * ( mapWidth / 2d ),
    113                                                                   bbox.getMin().getY() + dy * ( mapHeight / 2d ) );
    114
    115                    double distance = calcDistance( min.getX(), min.getY(), max.getX(), max.getY() );
    116
    117                    scale = distance / SQRT2;
    118
    119                }
    120            } catch ( Exception e ) {
    121                LOG.logError( e.getMessage(), e );
    122                throw new RuntimeException( Messages.getMessage( "FRAMEWORK_ERROR_SCALE_CALC", e.getMessage() ) );
    123            }
    124
    125            return scale;
    126        }
    127
    128        /**
    129         * @param mapWidth
    130         * @param mapHeight
    131         * @param bbox
    132         * @param crs
    133         * @return the WMS 1.3.0 scale (horizontal size of the pixel, pixel size == 0.28mm)
    134         */
    135        public static double calcScaleWMS130( int mapWidth, int mapHeight, Envelope bbox, CoordinateSystem crs ) {
    136            if ( mapWidth == 0 || mapHeight == 0 ) {
    137                return 0;
    138            }
    139
    140            double scale = 0;
    141
    142            if ( crs == null ) {
    143                throw new RuntimeException( "Invalid crs: " + crs );
    144            }
    145
    146            try {
    147                if ( "m".equalsIgnoreCase( crs.getAxisUnits()[0].toString() ) ) {
    148                    /*
    149                     * this method to calculate a maps scale as defined in OGC WMS and SLD specification is not required for
    150                     * maps having a projected reference system. Direct calculation of scale avoids uncertainties
    151                     */
    152                    double dx = bbox.getWidth() / mapWidth;
    153                    scale = dx / DEFAULT_PIXEL_SIZE;
    154                } else {
    155
    156                    if ( !crs.getIdentifier().equalsIgnoreCase( "EPSG:4326" ) ) {
    157                        // transform the bounding box of the request to EPSG:4326
    158                        GeoTransformer trans = new GeoTransformer( CRSFactory.create( "EPSG:4326" ) );
    159                        bbox = trans.transform( bbox, crs );
    160                    }
    161                    double dx = bbox.getWidth() / mapWidth;
    162                    double dy = bbox.getHeight() / mapHeight;
    163
    164                    double minx = bbox.getMin().getX() + dx * ( mapWidth / 2d - 1 );
    165                    double miny = bbox.getMin().getY() + dy * ( mapHeight / 2d - 1 );
    166                    double maxx = bbox.getMin().getX() + dx * ( mapWidth / 2d );
    167                    double maxy = bbox.getMin().getY() + dy * ( mapHeight / 2d - 1 );
    168
    169                    double distance = calcDistance( minx, miny, maxx, maxy );
    170
    171                    scale = distance / SQRT2 / DEFAULT_PIXEL_SIZE;
    172
    173                }
    174            } catch ( Exception e ) {
    175                LOG.logError( e.getMessage(), e );
    176                throw new RuntimeException( Messages.getMessage( "FRAMEWORK_ERROR_SCALE_CALC", e.getMessage() ) );
    177            }
    178
    179            return scale;
    180        }
    181
    182        /**
    183         * calculates the map scale (denominator) as defined in the OGC SLD 1.0.0 specification
    184         *
    185         * @param mapWidth
    186         *            map width in pixel
    187         * @param mapHeight
    188         *            map height in pixel
    189         * @param bbox
    190         *            bounding box of the map
    191         * @param crs
    192         *            coordinate reference system of the map
    193         * @param pixelSize
    194         *            size of one pixel of the map measured in meter
    195         *
    196         * @return a maps scale based on the diagonal size of a pixel at the center of the map in meter.
    197         * @throws RuntimeException
    198         */
    199        public static double calcScale( int mapWidth, int mapHeight, Envelope bbox, CoordinateSystem crs, double pixelSize )
    200                                throws RuntimeException {
    201
    202            double sqpxsize;
    203            if ( pixelSize == 1d ) {
    204                LOG.logDebug( "Calculating WMS 1.1.1 scale." );
    205                return calcScaleWMS111( mapWidth, mapHeight, bbox, crs );
    206            } else if ( pixelSize == DEFAULT_PIXEL_SIZE ) {
    207                LOG.logDebug( "Calculating WMS 1.3.0 scale." );
    208                return calcScaleWMS130( mapWidth, mapHeight, bbox, crs );
    209            } else {
    210                sqpxsize = pixelSize * pixelSize;
    211                sqpxsize += sqpxsize;
    212                sqpxsize = sqrt( sqpxsize );
    213            }
    214
    215            if ( mapWidth == 0 || mapHeight == 0 ) {
    216                return 0;
    217            }
    218
    219            double scale = 0;
    220
    221            CoordinateSystem cs = crs;
    222
    223            if ( cs == null ) {
    224                throw new RuntimeException( "Invalid crs: " + crs );
    225            }
    226
    227            try {
    228                if ( "m".equalsIgnoreCase( cs.getAxisUnits()[0].toString() ) ) {
    229                    /*
    230                     * this method to calculate a maps scale as defined in OGC WMS and SLD specification is not required for
    231                     * maps having a projected reference system. Direct calculation of scale avoids uncertainties
    232                     */
    233                    double dx = bbox.getWidth() / mapWidth;
    234                    double dy = bbox.getHeight() / mapHeight;
    235                    scale = Math.sqrt( dx * dx + dy * dy ) / sqpxsize;
    236                } else {
    237
    238                    if ( !crs.getIdentifier().equalsIgnoreCase( "EPSG:4326" ) ) {
    239                        // transform the bounding box of the request to EPSG:4326
    240                        GeoTransformer trans = new GeoTransformer( CRSFactory.create( "EPSG:4326" ) );
    241                        bbox = trans.transform( bbox, crs );
    242                    }
    243                    double dx = bbox.getWidth() / mapWidth;
    244                    double dy = bbox.getHeight() / mapHeight;
    245                    Position min = GeometryFactory.createPosition( bbox.getMin().getX() + dx * ( mapWidth / 2d - 1 ),
    246                                                                   bbox.getMin().getY() + dy * ( mapHeight / 2d - 1 ) );
    247                    Position max = GeometryFactory.createPosition( bbox.getMin().getX() + dx * ( mapWidth / 2d ),
    248                                                                   bbox.getMin().getY() + dy * ( mapHeight / 2d ) );
    249
    250                    double distance = calcDistance( min.getX(), min.getY(), max.getX(), max.getY() );
    251
    252                    scale = distance / sqpxsize;
    253
    254                }
    255            } catch ( Exception e ) {
    256                LOG.logError( e.getMessage(), e );
    257                throw new RuntimeException( Messages.getMessage( "FRAMEWORK_ERROR_SCALE_CALC", e.getMessage() ) );
    258            }
    259
    260            return scale;
    261
    262        }
    263
    264        /**
    265         * calculates the distance in meters between two points in EPSG:4326 coodinates. this is a convenience method
    266         * assuming the world is a ball
    267         *
    268         * @param lon1
    269         * @param lat1
    270         * @param lon2
    271         * @param lat2
    272         * @return the distance in meters between two points in EPSG:4326 coords
    273         */
    274        public static double calcDistance( double lon1, double lat1, double lon2, double lat2 ) {
    275            double r = 6378.137;
    276            double rad = Math.PI / 180d;
    277            double cose = Math.sin( rad * lon1 ) * Math.sin( rad * lon2 ) + Math.cos( rad * lon1 ) * Math.cos( rad * lon2 )
    278                          * Math.cos( rad * ( lat1 - lat2 ) );
    279            double dist = r * Math.acos( cose ) * Math.cos( rad * Math.min( lat1, lat2 ) );
    280
    281            // * 0.835 is just an heuristic correction factor
    282            return dist * 1000 * 0.835;
    283        }
    284
    285        /**
    286         * The method calculates a new Envelope from the <code>requestedBarValue</code> It will either zoom in or zoom out
    287         * of the <code>actualBBOX<code> depending
    288         * on the ratio of the <code>requestedBarValue</code> to the <code>actualBarValue</code>
    289         *
    290         * @param currentEnvelope
    291         *            current Envelope
    292         * @param currentScale
    293         *            the scale of the current envelope
    294         * @param requestedScale
    295         *            requested scale value
    296         * @return a new Envelope
    297         */
    298        public static Envelope scaleEnvelope( Envelope currentEnvelope, double currentScale, double requestedScale ) {
    299
    300            double ratio = requestedScale / currentScale;
    301            double newWidth = currentEnvelope.getWidth() * ratio;
    302            double newHeight = currentEnvelope.getHeight() * ratio;
    303            double midX = currentEnvelope.getMin().getX() + ( currentEnvelope.getWidth() / 2d );
    304            double midY = currentEnvelope.getMin().getY() + ( currentEnvelope.getHeight() / 2d );
    305
    306            double minx = midX - newWidth / 2d;
    307            double maxx = midX + newWidth / 2d;
    308            double miny = midY - newHeight / 2d;
    309            double maxy = midY + newHeight / 2d;
    310
    311            return GeometryFactory.createEnvelope( minx, miny, maxx, maxy, currentEnvelope.getCoordinateSystem() );
    312
    313        }
    314
    315        /**
    316         * This method ensures the bbox is resized (shrunk) to match the aspect ratio defined by mapHeight/mapWidth
    317         *
    318         * @param bbox
    319         * @param mapWith
    320         * @param mapHeight
    321         * @return a new bounding box with the aspect ratio given my mapHeight/mapWidth
    322         */
    323        public static final Envelope ensureAspectRatio( Envelope bbox, double mapWith, double mapHeight ) {
    324
    325            double minx = bbox.getMin().getX();
    326            double miny = bbox.getMin().getY();
    327            double maxx = bbox.getMax().getX();
    328            double maxy = bbox.getMax().getY();
    329
    330            double dx = maxx - minx;
    331            double dy = maxy - miny;
    332
    333            double ratio = mapHeight / mapWith;
    334
    335            if ( dx >= dy ) {
    336                // height has to be corrected
    337                double[] normCoords = getNormalizedCoords( dx, ratio, miny, maxy );
    338                miny = normCoords[0];
    339                maxy = normCoords[1];
    340            } else {
    341                // width has to be corrected
    342                ratio = mapWith / mapHeight;
    343                double[] normCoords = getNormalizedCoords( dy, ratio, minx, maxx );
    344                minx = normCoords[0];
    345                maxx = normCoords[1];
    346            }
    347            CoordinateSystem crs = bbox.getCoordinateSystem();
    348
    349            return GeometryFactory.createEnvelope( minx, miny, maxx, maxy, crs );
    350        }
    351
    352        private static final double[] getNormalizedCoords( double normLen, double ratio, double min, double max ) {
    353            double mid = ( max - min ) / 2 + min;
    354            min = mid - ( normLen / 2 ) * ratio;
    355            max = mid + ( normLen / 2 ) * ratio;
    356            double[] newCoords = { min, max };
    357            return newCoords;
    358        }
    359
    360    }