001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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    }