001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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 }