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 }