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    }