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