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