001 //$HeadURL: http://svn.wald.intevation.org/svn/deegree/base/trunk/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 java.awt.BasicStroke;
041 import java.awt.Color;
042 import java.awt.Dimension;
043 import java.awt.Font;
044 import java.awt.Graphics2D;
045 import java.text.DecimalFormat;
046
047 import org.deegree.framework.log.ILogger;
048 import org.deegree.framework.log.LoggerFactory;
049 import org.deegree.graphics.transformation.GeoTransform;
050 import org.deegree.graphics.transformation.WorldToScreenTransform;
051 import org.deegree.i18n.Messages;
052 import org.deegree.model.crs.CRSFactory;
053 import org.deegree.model.crs.CoordinateSystem;
054 import org.deegree.model.crs.GeoTransformer;
055 import org.deegree.model.spatialschema.Envelope;
056 import org.deegree.model.spatialschema.GeometryFactory;
057 import org.deegree.model.spatialschema.Position;
058
059 /**
060 *
061 *
062 * @version $Revision: 29842 $
063 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
064 * @author last edited by: $Author: apoth $
065 *
066 * @version 1.0. $Revision: 29842 $, $Date: 2011-03-03 10:51:42 +0100 (Thu, 03 Mar 2011) $
067 *
068 * @since 2.0
069 */
070 public class MapUtils {
071
072 private static ILogger LOG = LoggerFactory.getLogger( MapUtils.class );
073
074 /**
075 * The value of sqrt(2)
076 */
077 public static final double SQRT2 = sqrt( 2 );
078
079 /**
080 * The Value of a PixelSize
081 */
082 public static final double DEFAULT_PIXEL_SIZE = 0.00028;
083
084 /**
085 * @param mapWidth
086 * @param mapHeight
087 * @param bbox
088 * @param crs
089 * @return the WMS 1.1.1 scale (size of the diagonal pixel)
090 */
091 public static double calcScaleWMS111( int mapWidth, int mapHeight, Envelope bbox, CoordinateSystem crs ) {
092 if ( mapWidth == 0 || mapHeight == 0 ) {
093 return 0;
094 }
095 double scale = 0;
096
097 if ( crs == null ) {
098 throw new RuntimeException( "Invalid crs: " + crs );
099 }
100
101 try {
102 if ( "m".equalsIgnoreCase( crs.getAxisUnits()[0].toString() ) ) {
103 /*
104 * this method to calculate a maps scale as defined in OGC WMS and SLD specification is not required for
105 * maps having a projected reference system. Direct calculation of scale avoids uncertainties
106 */
107 double dx = bbox.getWidth() / mapWidth;
108 double dy = bbox.getHeight() / mapHeight;
109 scale = sqrt( dx * dx + dy * dy );
110 } else {
111
112 if ( !crs.getIdentifier().equalsIgnoreCase( "EPSG:4326" ) ) {
113 // transform the bounding box of the request to EPSG:4326
114 GeoTransformer trans = new GeoTransformer( CRSFactory.create( "EPSG:4326" ) );
115 bbox = trans.transform( bbox, crs );
116 }
117 double dx = bbox.getWidth() / mapWidth;
118 double dy = bbox.getHeight() / mapHeight;
119 Position min = GeometryFactory.createPosition( bbox.getMin().getX() + dx * ( mapWidth / 2d - 1 ),
120 bbox.getMin().getY() + dy * ( mapHeight / 2d - 1 ) );
121 Position max = GeometryFactory.createPosition( bbox.getMin().getX() + dx * ( mapWidth / 2d ),
122 bbox.getMin().getY() + dy * ( mapHeight / 2d ) );
123
124 double distance = calcDistance( min.getX(), min.getY(), max.getX(), max.getY() );
125
126 scale = distance / SQRT2;
127
128 }
129 } catch ( Exception e ) {
130 LOG.logError( e.getMessage(), e );
131 throw new RuntimeException( Messages.getMessage( "FRAMEWORK_ERROR_SCALE_CALC", e.getMessage() ) );
132 }
133
134 return scale;
135 }
136
137 /**
138 * @param mapWidth
139 * @param mapHeight
140 * @param bbox
141 * @param crs
142 * @return the WMS 1.3.0 scale (horizontal size of the pixel, pixel size == 0.28mm)
143 */
144 public static double calcScaleWMS130( int mapWidth, int mapHeight, Envelope bbox, CoordinateSystem crs ) {
145 if ( mapWidth == 0 || mapHeight == 0 ) {
146 return 0;
147 }
148
149 double scale = 0;
150
151 if ( crs == null ) {
152 throw new RuntimeException( "Invalid crs: " + crs );
153 }
154
155 try {
156 if ( "m".equalsIgnoreCase( crs.getAxisUnits()[0].toString() ) ) {
157 /*
158 * this method to calculate a maps scale as defined in OGC WMS and SLD specification is not required for
159 * maps having a projected reference system. Direct calculation of scale avoids uncertainties
160 */
161 double dx = bbox.getWidth() / mapWidth;
162 scale = dx / DEFAULT_PIXEL_SIZE;
163 } else {
164
165 if ( !crs.getIdentifier().equalsIgnoreCase( "EPSG:4326" ) ) {
166 // transform the bounding box of the request to EPSG:4326
167 GeoTransformer trans = new GeoTransformer( CRSFactory.create( "EPSG:4326" ) );
168 bbox = trans.transform( bbox, crs );
169 }
170 double dx = bbox.getWidth() / mapWidth;
171 double dy = bbox.getHeight() / mapHeight;
172
173 double minx = bbox.getMin().getX() + dx * ( mapWidth / 2d - 1 );
174 double miny = bbox.getMin().getY() + dy * ( mapHeight / 2d - 1 );
175 double maxx = bbox.getMin().getX() + dx * ( mapWidth / 2d );
176 double maxy = bbox.getMin().getY() + dy * ( mapHeight / 2d - 1 );
177
178 double distance = calcDistance( minx, miny, maxx, maxy );
179
180 scale = distance / SQRT2 / DEFAULT_PIXEL_SIZE;
181
182 }
183 } catch ( Exception e ) {
184 LOG.logError( e.getMessage(), e );
185 throw new RuntimeException( Messages.getMessage( "FRAMEWORK_ERROR_SCALE_CALC", e.getMessage() ) );
186 }
187
188 return scale;
189 }
190
191 /**
192 * calculates the map scale (denominator) as defined in the OGC SLD 1.0.0 specification
193 *
194 * @param mapWidth
195 * map width in pixel
196 * @param mapHeight
197 * map height in pixel
198 * @param bbox
199 * bounding box of the map
200 * @param crs
201 * coordinate reference system of the map
202 * @param pixelSize
203 * size of one pixel of the map measured in meter
204 *
205 * @return a maps scale based on the diagonal size of a pixel at the center of the map in meter.
206 * @throws RuntimeException
207 */
208 public static double calcScale( int mapWidth, int mapHeight, Envelope bbox, CoordinateSystem crs, double pixelSize )
209 throws RuntimeException {
210
211 double sqpxsize;
212 if ( pixelSize == 1d ) {
213 LOG.logDebug( "Calculating WMS 1.1.1 scale." );
214 return calcScaleWMS111( mapWidth, mapHeight, bbox, crs );
215 } else if ( pixelSize == DEFAULT_PIXEL_SIZE ) {
216 LOG.logDebug( "Calculating WMS 1.3.0 scale." );
217 return calcScaleWMS130( mapWidth, mapHeight, bbox, crs );
218 } else {
219 sqpxsize = pixelSize * pixelSize;
220 sqpxsize += sqpxsize;
221 sqpxsize = sqrt( sqpxsize );
222 }
223
224 if ( mapWidth == 0 || mapHeight == 0 ) {
225 return 0;
226 }
227
228 double scale = 0;
229
230 CoordinateSystem cs = crs;
231
232 if ( cs == null ) {
233 throw new RuntimeException( "Invalid crs: " + crs );
234 }
235
236 try {
237 if ( "m".equalsIgnoreCase( cs.getAxisUnits()[0].toString() ) ) {
238 /*
239 * this method to calculate a maps scale as defined in OGC WMS and SLD specification is not required for
240 * maps having a projected reference system. Direct calculation of scale avoids uncertainties
241 */
242 double dx = bbox.getWidth() / mapWidth;
243 double dy = bbox.getHeight() / mapHeight;
244 scale = Math.sqrt( dx * dx + dy * dy ) / sqpxsize;
245 } else {
246
247 if ( !crs.getIdentifier().equalsIgnoreCase( "EPSG:4326" ) ) {
248 // transform the bounding box of the request to EPSG:4326
249 GeoTransformer trans = new GeoTransformer( CRSFactory.create( "EPSG:4326" ) );
250 bbox = trans.transform( bbox, crs );
251 }
252 double dx = bbox.getWidth() / mapWidth;
253 double dy = bbox.getHeight() / mapHeight;
254 Position min = GeometryFactory.createPosition( bbox.getMin().getX() + dx * ( mapWidth / 2d - 1 ),
255 bbox.getMin().getY() + dy * ( mapHeight / 2d - 1 ) );
256 Position max = GeometryFactory.createPosition( bbox.getMin().getX() + dx * ( mapWidth / 2d ),
257 bbox.getMin().getY() + dy * ( mapHeight / 2d ) );
258
259 double distance = calcDistance( min.getX(), min.getY(), max.getX(), max.getY() );
260
261 scale = distance / sqpxsize;
262
263 }
264 } catch ( Exception e ) {
265 LOG.logError( e.getMessage(), e );
266 throw new RuntimeException( Messages.getMessage( "FRAMEWORK_ERROR_SCALE_CALC", e.getMessage() ) );
267 }
268
269 return scale;
270
271 }
272
273 /**
274 * calculates the distance in meters between two points in EPSG:4326 coodinates. this is a convenience method
275 * assuming the world is a ball
276 *
277 * @param lon1
278 * @param lat1
279 * @param lon2
280 * @param lat2
281 * @return the distance in meters between two points in EPSG:4326 coords
282 */
283 public static double calcDistance( double lon1, double lat1, double lon2, double lat2 ) {
284 double r = 6378.137;
285 double rad = Math.PI / 180d;
286 double cose = Math.sin( rad * lon1 ) * Math.sin( rad * lon2 ) + Math.cos( rad * lon1 ) * Math.cos( rad * lon2 )
287 * Math.cos( rad * ( lat1 - lat2 ) );
288 double dist = r * Math.acos( cose ) * Math.cos( rad * Math.min( lat1, lat2 ) );
289
290 // * 0.835 is just an heuristic correction factor
291 return dist * 1000 * 0.835;
292 }
293
294 /**
295 * The method calculates a new Envelope from the <code>requestedBarValue</code> It will either zoom in or zoom out
296 * of the <code>actualBBOX<code> depending
297 * on the ratio of the <code>requestedBarValue</code> to the <code>actualBarValue</code>
298 *
299 * @param currentEnvelope
300 * current Envelope
301 * @param currentScale
302 * the scale of the current envelope
303 * @param requestedScale
304 * requested scale value
305 * @return a new Envelope
306 */
307 public static Envelope scaleEnvelope( Envelope currentEnvelope, double currentScale, double requestedScale ) {
308
309 double ratio = requestedScale / currentScale;
310 double newWidth = currentEnvelope.getWidth() * ratio;
311 double newHeight = currentEnvelope.getHeight() * ratio;
312 double midX = currentEnvelope.getMin().getX() + ( currentEnvelope.getWidth() / 2d );
313 double midY = currentEnvelope.getMin().getY() + ( currentEnvelope.getHeight() / 2d );
314
315 double minx = midX - newWidth / 2d;
316 double maxx = midX + newWidth / 2d;
317 double miny = midY - newHeight / 2d;
318 double maxy = midY + newHeight / 2d;
319
320 return GeometryFactory.createEnvelope( minx, miny, maxx, maxy, currentEnvelope.getCoordinateSystem() );
321
322 }
323
324 /**
325 * This method ensures the bbox is resized (shrunk) to match the aspect ratio defined by mapHeight/mapWidth
326 *
327 * @param bbox
328 * @param mapWith
329 * @param mapHeight
330 * @return a new bounding box with the aspect ratio given my mapHeight/mapWidth
331 */
332 public static final Envelope ensureAspectRatio( Envelope bbox, double mapWith, double mapHeight ) {
333
334 double minx = bbox.getMin().getX();
335 double miny = bbox.getMin().getY();
336 double maxx = bbox.getMax().getX();
337 double maxy = bbox.getMax().getY();
338
339 double dx = maxx - minx;
340 double dy = maxy - miny;
341
342 double ratio = mapHeight / mapWith;
343
344 if ( dx >= dy ) {
345 // height has to be corrected
346 double[] normCoords = getNormalizedCoords( dx, ratio, miny, maxy );
347 miny = normCoords[0];
348 maxy = normCoords[1];
349 } else {
350 // width has to be corrected
351 ratio = mapWith / mapHeight;
352 double[] normCoords = getNormalizedCoords( dy, ratio, minx, maxx );
353 minx = normCoords[0];
354 maxx = normCoords[1];
355 }
356 CoordinateSystem crs = bbox.getCoordinateSystem();
357
358 return GeometryFactory.createEnvelope( minx, miny, maxx, maxy, crs );
359 }
360
361 private static final double[] getNormalizedCoords( double normLen, double ratio, double min, double max ) {
362 double mid = ( max - min ) / 2 + min;
363 min = mid - ( normLen / 2 ) * ratio;
364 max = mid + ( normLen / 2 ) * ratio;
365 double[] newCoords = { min, max };
366 return newCoords;
367 }
368
369 /**
370 *
371 * @param img
372 * @param bbox
373 * @param mapSize
374 * @param fontName
375 * @param fontSize
376 */
377 public static void drawScalbar( Graphics2D g, int desiredSize, Envelope bbox, Dimension mapSize, String fontName,
378 int fontSize ) {
379
380 desiredSize -= 30;
381 GeoTransform gt = new WorldToScreenTransform( bbox.getMin().getX(), bbox.getMin().getY(), bbox.getMax().getX(),
382 bbox.getMax().getY(), 0, 0, mapSize.getWidth() - 1,
383 mapSize.getHeight() - 1 );
384
385 // calculate scale bar max scale and size
386 int length = 0;
387 double lx = gt.getDestX( bbox.getMin().getX() );
388 double scale = 0;
389 for ( int i = 0; i < 100; i++ ) {
390 double k = 0;
391 double dec = 30 * Math.pow( 10, i );
392 for ( int j = 0; j < 9; j++ ) {
393 k += dec;
394 double tx = gt.getDestX( bbox.getMin().getX() + k );
395 if ( Math.abs( tx - lx ) < desiredSize ) {
396 length = (int) Math.round( Math.abs( tx - lx ) );
397 scale = k;
398 } else {
399 break;
400 }
401 }
402 }
403
404 // draw scale bar base line
405 g.setStroke( new BasicStroke( ( desiredSize + 30 ) / 250 ) );
406 g.setColor( Color.black );
407 g.drawLine( 10, 30, length + 10, 30 );
408 double dx = length / 3d;
409 double vdx = scale / 3;
410 double div = 1;
411 String uom = "m";
412 if ( scale > 1000 ) {
413 div = 1000;
414 uom = "km";
415 }
416 // draw scale bar scales
417 if ( fontName == null ) {
418 fontName = "SANS SERIF";
419 }
420 g.setFont( new Font( fontName, Font.PLAIN, fontSize ) );
421 DecimalFormat df = new DecimalFormat( "##.# " + uom );
422 for ( int i = 0; i < 4; i++ ) {
423 double val = ( vdx * i ) / div;
424 g.drawString( df.format( val ), (int) Math.round( 10 + i * dx ) - 8, 10 );
425 g.drawLine( (int) Math.round( 10 + i * dx ), 30, (int) Math.round( 10 + i * dx ), 20 );
426 }
427 for ( int i = 0; i < 7; i++ ) {
428 g.drawLine( (int) Math.round( 10 + i * dx / 2d ), 30, (int) Math.round( 10 + i * dx / 2d ), 25 );
429 }
430
431 g.dispose();
432
433 }
434
435 }