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 }