001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/graphics/MapView.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.graphics; 037 038 import java.awt.Graphics; 039 import java.awt.Graphics2D; 040 import java.util.ArrayList; 041 import java.util.Collections; 042 import java.util.HashMap; 043 import java.util.Iterator; 044 import java.util.List; 045 046 import org.deegree.framework.log.ILogger; 047 import org.deegree.framework.log.LoggerFactory; 048 import org.deegree.framework.util.MapUtils; 049 import org.deegree.framework.util.StringTools; 050 import org.deegree.graphics.displayelements.DisplayElement; 051 import org.deegree.graphics.optimizers.Optimizer; 052 import org.deegree.graphics.transformation.GeoTransform; 053 import org.deegree.graphics.transformation.WorldToScreenTransform; 054 import org.deegree.model.crs.CRSFactory; 055 import org.deegree.model.crs.CoordinateSystem; 056 import org.deegree.model.crs.UnknownCRSException; 057 import org.deegree.model.spatialschema.Envelope; 058 059 /** 060 * This interface describes the data model of the map itself. It is built from themes containing 061 * {@link DisplayElement}s to be rendered. Themes can be added and removed. Existing themes can be 062 * re-arragned by changing their order. 063 * 064 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 065 * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider</a> 066 * @author last edited by: $Author: mschneider $ 067 * 068 * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18 Jun 2009) $ 069 */ 070 public class MapView { 071 072 private static final ILogger LOG = LoggerFactory.getLogger( MapView.class ); 073 074 private String name = null; 075 076 private HashMap<String, Theme> themes = null; 077 078 private HashMap<String, Boolean> enabled = null; 079 080 private List<Theme> themesL = null; 081 082 private Theme activatedTh = null; 083 084 private Envelope boundingbox = null; 085 086 private CoordinateSystem crs = null; 087 088 private List<EventController> eventCntr = Collections.synchronizedList( new ArrayList<EventController>() ); 089 090 private double scale; 091 092 private double pixelsize = 0.00028; 093 094 private GeoTransform projection = new WorldToScreenTransform(); 095 096 // list of Optimizers that are processed at the beginning of the paint ()-call 097 private List<Optimizer> optimizers = new ArrayList<Optimizer>(); 098 099 /** 100 * 101 * @param name 102 * @param boundingbox 103 * @param pixelsize 104 * @throws UnknownCRSException 105 */ 106 protected MapView( String name, Envelope boundingbox, double pixelsize ) throws UnknownCRSException { 107 this.name = name; 108 this.pixelsize = pixelsize; 109 themes = new HashMap<String, Theme>(); 110 themesL = new ArrayList<Theme>(); 111 enabled = new HashMap<String, Boolean>(); 112 setBoundingBox( boundingbox ); 113 crs = CRSFactory.create( "EPSG:4326" ); 114 } 115 116 /** 117 * 118 * @param name 119 * @param boundingbox 120 * @param crs 121 * @param pixelsize 122 */ 123 protected MapView( String name, Envelope boundingbox, CoordinateSystem crs, double pixelsize ) { 124 this.name = name; 125 this.pixelsize = pixelsize; 126 themes = new HashMap<String, Theme>(); 127 themesL = new ArrayList<Theme>(); 128 enabled = new HashMap<String, Boolean>(); 129 setBoundingBox( boundingbox ); 130 this.crs = crs; 131 } 132 133 /** 134 * returns the name of the map 135 * @return the name of the map 136 * 137 */ 138 public String getName() { 139 return name; 140 } 141 142 /** 143 * returns the Theme that matches the submitted name 144 * @param name of the theme to get 145 * @return the theme or <code>null</code> if not found. 146 */ 147 public Theme getTheme( String name ) { 148 return themes.get( name ); 149 } 150 151 /** 152 * returns the Theme that matches the submitted index 153 * @param index of the theme 154 * @return the Theme that matches the submitted index or <code>null</code> if the given index has no theme. 155 */ 156 public Theme getTheme( int index ) { 157 return themesL.get( index ); 158 } 159 160 /** 161 * returns the Themes in correct order. The first Theme (index == 0) shall be rendered at first 162 * (bottom most). 163 * @return the Themes in inserted order 164 */ 165 public Theme[] getAllThemes() { 166 return themesL.toArray( new Theme[themesL.size()] ); 167 } 168 169 /** 170 * Returns the current scale of the MapView. 171 * @return the current scale of the MapView. 172 * 173 */ 174 public double getScale() { 175 return scale; 176 } 177 178 /** 179 * Returns the current scale of the MapView. 180 * @param g the graphics to calculate the scale from 181 * @return the current scale of the MapView. 182 */ 183 public double getScale( Graphics g ){ 184 return MapUtils.calcScale( g.getClipBounds().width, g.getClipBounds().height, getBoundingBox(), 185 getCoordinatesSystem(), pixelsize ); 186 } 187 188 /** 189 * adds a theme to the MapView 190 * @param theme to add 191 * @throws Exception if the coordinate system of the theme could not be set to the layer. 192 */ 193 public void addTheme( Theme theme ) throws Exception 194 { 195 themes.put( theme.getName(), theme ); 196 themesL.add( theme ); 197 enabled.put( theme.getName(), Boolean.TRUE ); 198 activatedTh = theme; 199 theme.setParent( this ); 200 theme.getLayer().setCoordinatesSystem( crs ); 201 } 202 203 /** 204 * removes a theme from the MapView 205 * @param theme to remove 206 */ 207 public void removeTheme( Theme theme ) { 208 if ( theme != null ) { 209 enabled.remove( theme.getName() ); 210 themesL.remove( themesL.indexOf( theme ) ); 211 themes.remove( theme.getName() ); 212 } 213 } 214 215 /** 216 * removes the theme that matches the submitted name from the MapView 217 * @param name to of the theme to be removed. 218 */ 219 public void removeTheme( String name ) { 220 removeTheme( getTheme( name ) ); 221 } 222 223 /** 224 * removes the theme that matches the submitted index from the MapView 225 * @param index of the theme to be removed 226 */ 227 public void removeTheme( int index ) { 228 removeTheme( themesL.get( index ) ); 229 } 230 231 /** 232 * removes all themes from the MapView. 233 */ 234 public void clear() { 235 themes.clear(); 236 themesL.clear(); 237 enabled.clear(); 238 activatedTh = null; 239 } 240 241 /** 242 * swaps the positions of the submitted themes 243 * @param first will be second 244 * @param second will be first. 245 */ 246 public void swapThemes( Theme first, Theme second ) { 247 248 if ( themesL.contains( first ) && themesL.contains( second ) ) { 249 int i1 = themesL.indexOf( first ); 250 int i2 = themesL.indexOf( second ); 251 themesL.set( i1, second ); 252 themesL.set( i2, first ); 253 } 254 255 } 256 257 /** 258 * move a theme up for one index position (index = oldindex + 1) 259 * @param theme to move up 260 */ 261 public void moveUp( Theme theme ) { 262 263 int idx = themesL.indexOf( theme ); 264 if ( idx < themesL.size() - 1 ) { 265 Theme th = themesL.get( idx + 1 ); 266 swapThemes( theme, th ); 267 } 268 269 } 270 271 /** 272 * move a theme down for one index position (index = oldindex - 1) 273 * @param theme to move down 274 */ 275 public void moveDown( Theme theme ) { 276 277 int idx = themesL.indexOf( theme ); 278 if ( idx > 0 ) { 279 Theme th = themesL.get( idx - 1 ); 280 swapThemes( theme, th ); 281 } 282 283 } 284 285 /** 286 * enables or disables a theme that is part of the MapView. A theme that has been disabled won't 287 * be rendered and usually doesn't react to events targeted to the MapView, but still is part of 288 * the MapView. 289 * @param theme to be dis/en-abled 290 * @param enable true if enabled 291 */ 292 public void enableTheme( Theme theme, boolean enable ) { 293 enabled.put( theme.getName(), enable ? Boolean.TRUE : Boolean.FALSE ); 294 } 295 296 /** 297 * returns true if the passed theme is set to be enabled 298 * @param theme to check 299 * @return true if the passed theme is set to be enabled 300 */ 301 public boolean isThemeEnabled( Theme theme ) { 302 return enabled.get( theme.getName() ).booleanValue(); 303 } 304 305 /** 306 * activates a theme. Usually the activated theme is perferred to react to events (this doesn't 307 * mean that other themes are not allowed to react to events). 308 * @param theme to activate 309 */ 310 public void activateTheme( Theme theme ) { 311 activatedTh = theme; 312 } 313 314 /** 315 * returns true if the passed theme is the one that is set to be activated 316 * @param theme to check 317 * @return true if the passed theme is the one that is set to be activated 318 */ 319 public boolean isThemeActivated( Theme theme ) { 320 return activatedTh.getName().equals( theme.getName() ); 321 } 322 323 /** 324 * returns the amount of themes within the MapView. 325 * @return the amount of themes within the MapView. 326 */ 327 public int getSize() { 328 return themes.size(); 329 } 330 331 /** 332 * adds an eventcontroller to the MapView that's responsible for handling events that targets the 333 * map. E.g.: zooming, panning, selecting a feature etc. 334 * @param obj event controller to add 335 */ 336 public void addEventController( MapEventController obj ) { 337 eventCntr.add( obj ); 338 obj.addMapView( this ); 339 } 340 341 /** 342 * @param obj event controller to be removed 343 * @see org.deegree.graphics.MapView#addEventController(MapEventController) 344 */ 345 public void removeEventController( MapEventController obj ) { 346 eventCntr.remove( obj ); 347 obj.removeMapView( this ); 348 } 349 350 /** 351 * A selector is a class that offers methods for selecting and de-selecting single 352 * DisplayElements or groups of DisplayElements. A selector may offers methods like 'select all 353 * DisplayElements within a specified bounding box' or 'select all DisplayElements thats area is 354 * larger than 120 km' etc. 355 * @param obj selector to added to all themes 356 */ 357 public void addSelector( Selector obj ) { 358 for ( int i = 0; i < themesL.size(); i++ ) { 359 getTheme( i ).addSelector( obj ); 360 } 361 } 362 363 /** 364 * @param obj selector to be removed 365 * @see org.deegree.graphics.MapView#addSelector(Selector) 366 */ 367 public void removeSelector( Selector obj ) { 368 for ( int i = 0; i < themesL.size(); i++ ) { 369 getTheme( i ).removeSelector( obj ); 370 } 371 } 372 373 /** 374 * returns the BoundingBox (Envelope) of the MapView. This isn't necessary the BoundingBox of 375 * the data that will be rendered. It's the boundingBox of the the visible area of the map 376 * measured in its coordinate reference system. 377 * @return the BoundingBox (Envelope) of the MapView. 378 */ 379 public Envelope getBoundingBox() { 380 return boundingbox; 381 } 382 383 /** 384 * @param boundingbox to set. 385 * @see org.deegree.graphics.MapView#getBoundingBox() this method may be used for zooming and 386 * panning the map 387 */ 388 public void setBoundingBox( Envelope boundingbox ) { 389 this.boundingbox = boundingbox; 390 projection.setSourceRect( boundingbox ); 391 } 392 393 /** 394 * returns the coordinate reference system of the MapView 395 * @return the coordinate reference system of the MapView 396 */ 397 public CoordinateSystem getCoordinatesSystem() { 398 return crs; 399 } 400 401 /** 402 * sets the coordinate reference system of the map; 403 * @param crs to be set 404 * @throws Exception if the crs could not be set to the layers of the themes 405 */ 406 public void setCoordinateSystem( CoordinateSystem crs ) throws Exception { 407 this.crs = crs; 408 for ( int i = 0; i < themesL.size(); i++ ) { 409 Layer lay = getTheme( i ).getLayer(); 410 lay.setCoordinatesSystem( crs ); 411 } 412 } 413 414 /** 415 * renders the map to the passed graphic context 416 * 417 * @param g 418 * @throws RenderException 419 * thrown if the passed <tt>Graphic<tt> haven't 420 * clipbounds. use g.setClip( .. ); 421 */ 422 public void paint( Graphics g ) 423 throws RenderException { 424 425 if ( g.getClipBounds() == null ) { 426 throw new RenderException( "no clip bounds defined for graphic context" ); 427 } 428 429 int x = g.getClipBounds().x; 430 int y = g.getClipBounds().y; 431 int w = g.getClipBounds().width; 432 int h = g.getClipBounds().height; 433 projection.setDestRect( x, y, w + x, h + y ); 434 435 try { 436 double sc = getScale( g ); 437 LOG.logInfo( "OGC SLD scale denominator ", sc ); 438 scale = sc; 439 // call all Optimizers 440 optimize( g ); 441 } catch ( Exception e ) { 442 e.printStackTrace(); 443 throw new RenderException( StringTools.stackTraceToString( e ) ); 444 } 445 446 // paint all Themes 447 for ( int i = 0; i < themesL.size(); i++ ) { 448 if ( isThemeEnabled( getTheme( i ) ) ) { 449 getTheme( i ).paint( g ); 450 } 451 } 452 453 } 454 455 /** 456 * renders the features marked as selected of all themes contained within the MapView 457 * 458 * @param g 459 * graphic context to render the map too 460 * @throws RenderException 461 * thrown if the passed <tt>Graphic<tt> haven't 462 * clipbounds. use g.setClip( .. ); 463 */ 464 public void paintSelected( Graphics g ) 465 throws RenderException { 466 467 if ( g.getClipBounds() == null ) { 468 throw new RenderException( "no clip bounds defined for graphic context" ); 469 } 470 471 int x = g.getClipBounds().x; 472 int y = g.getClipBounds().y; 473 int width = g.getClipBounds().width; 474 int height = g.getClipBounds().height; 475 projection.setDestRect( x - 2, y - 2, width + x, height + y ); 476 477 try { 478 // call all Optimizers 479 optimize( g ); 480 } catch ( Exception e ) { 481 throw new RenderException( StringTools.stackTraceToString( e ) ); 482 } 483 484 // paint all Themes 485 for ( int i = 0; i < themesL.size(); i++ ) { 486 if ( isThemeEnabled( getTheme( i ) ) ) { 487 getTheme( i ).paintSelected( g ); 488 } 489 } 490 491 } 492 493 /** 494 * renders the features marked as highlighted of all themes contained within the MapView 495 * 496 * @param g 497 * graphic context to render the map too 498 * @throws RenderException 499 * thrown if the passed <tt>Graphic<tt> haven't 500 * clipbounds. use g.setClip( .. ); 501 */ 502 public void paintHighlighted( Graphics g ) 503 throws RenderException { 504 505 if ( g.getClipBounds() == null ) { 506 throw new RenderException( "no clip bounds defined for graphic context" ); 507 } 508 509 int x = g.getClipBounds().x; 510 int y = g.getClipBounds().y; 511 int width = g.getClipBounds().width; 512 int height = g.getClipBounds().height; 513 projection.setDestRect( x - 2, y - 2, width + x, height + y ); 514 515 try { 516 // call all Optimizers 517 optimize( g ); 518 } catch ( Exception e ) { 519 throw new RenderException( StringTools.stackTraceToString( e ) ); 520 } 521 522 // paint all Themes 523 for ( int i = 0; i < themesL.size(); i++ ) { 524 if ( isThemeEnabled( getTheme( i ) ) ) { 525 getTheme( i ).paintHighlighted( g ); 526 } 527 } 528 529 } 530 531 /** 532 * A Highlighter is a class that is responsible for managing the highlight capabilities for one 533 * or more Themes. 534 * @param highlighter to added to all themes 535 */ 536 public void addHighlighter( Highlighter highlighter ) { 537 for ( int i = 0; i < themesL.size(); i++ ) { 538 getTheme( i ).addHighlighter( highlighter ); 539 } 540 } 541 542 /** 543 * @param highlighter to be removed from all themes 544 * @see org.deegree.graphics.MapView#addHighlighter(Highlighter) 545 */ 546 public void removeHighlighter( Highlighter highlighter ) { 547 for ( int i = 0; i < themesL.size(); i++ ) { 548 getTheme( i ).removeHighlighter( highlighter ); 549 } 550 } 551 552 /** 553 * Returns the <tt>GeoTransform</tt> that is associated to this MapView. 554 * <p> 555 * 556 * @return the associated <tt>GeoTransform</tt>-instance 557 * 558 */ 559 public GeoTransform getProjection() { 560 return projection; 561 } 562 563 /** 564 * Calls all registered <tt>Optimizer</tt> subsequently. 565 * 566 * @param g 567 */ 568 private void optimize( Graphics g ) 569 throws Exception { 570 Graphics2D g2 = (Graphics2D) g; 571 Iterator<Optimizer> it = optimizers.iterator(); 572 while ( it.hasNext() ) { 573 Optimizer optimizer = it.next(); 574 optimizer.optimize( g2 ); 575 } 576 } 577 578 /** 579 * Adds an <tt>Optimizer</tt>. 580 * 581 * @param optimizer 582 */ 583 public void addOptimizer( Optimizer optimizer ) { 584 optimizers.add( optimizer ); 585 optimizer.setMapView( this ); 586 } 587 588 /** 589 * Returns the <tt>Optimizer</tt>s. 590 * 591 * @return the <tt>Optimizer</tt>s. 592 * 593 */ 594 public Optimizer[] getOptimizers() { 595 return optimizers.toArray( new Optimizer[0] ); 596 } 597 598 /** 599 * Sets the <tt>Optimizer<tt>s. 600 * @param optimizers 601 */ 602 public void setOptimizers( Optimizer[] optimizers ) { 603 this.optimizers.clear(); 604 for ( int i = 0; i < optimizers.length; i++ ) { 605 addOptimizer( optimizers[i] ); 606 } 607 } 608 609 }