001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/graphics/Theme.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.lang.reflect.InvocationTargetException;
048    import java.util.ArrayList;
049    import java.util.Collections;
050    import java.util.List;
051    
052    import org.deegree.framework.log.ILogger;
053    import org.deegree.framework.log.LoggerFactory;
054    import org.deegree.graphics.displayelements.DisplayElement;
055    import org.deegree.graphics.displayelements.DisplayElementFactory;
056    import org.deegree.graphics.displayelements.LabelDisplayElement;
057    import org.deegree.graphics.sld.UserStyle;
058    import org.deegree.io.datastore.PropertyPathResolvingException;
059    import org.deegree.model.coverage.grid.GridCoverage;
060    import org.deegree.model.feature.Feature;
061    import org.deegree.model.feature.FeatureProperty;
062    import org.deegree.model.spatialschema.GeometryException;
063    
064    /**
065     * A Theme is for usual a homogenious collection of Features coupled with a portrayal model for
066     * their graphical representation. Considering the OGC Styled Layer Descriptor specification this is
067     * not nessecary the case. In confirmation with the SLD a theme can be build from a lot of thematic
068     * completly different feature types.
069     * <p>
070     * </p>
071     * From a theoretical point of view this isn't very satisfying. But it will be supported by the
072     * <tt>Theme</tt> class.
073     * <p>
074     * </p>
075     * Assigned to the Theme are:
076     * <ul>
077     * <li>a Layer that contains the data (features)
078     * <li>a Portrayal model that determines how the features shall be rendered
079     * <li>a Selector that offers method for selection and de-selection of features
080     * <li>a event listener that handles event occuring on a theme that's for usual part of a map.
081     * </ul>
082     * 
083     * 
084     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
085     * @author last edited by: $Author: apoth $
086     * 
087     * @version. $Revision: 9340 $, $Date: 2007-12-27 13:32:12 +0100 (Do, 27 Dez 2007) $
088     */
089    public class Theme {
090    
091        private static final ILogger LOG = LoggerFactory.getLogger( Theme.class );
092    
093        private String name = null;
094    
095        private Layer layer = null;
096    
097        private UserStyle[] styles = null;
098    
099        private List<DisplayElement> displayElements = null;
100    
101        /**
102         * the MapView (map) the theme is associated to
103         * 
104         */
105        private MapView parent = null;
106    
107        /**
108         * this ArrayList contains all DisplayElements (and so the features) that are marked as
109         * selected.
110         */
111        private List<Selector> selector = Collections.synchronizedList( new ArrayList<Selector>() );
112    
113        private List<Highlighter> highlighter = Collections.synchronizedList( new ArrayList<Highlighter>() );
114    
115        private List<EventController> eventController = Collections.synchronizedList( new ArrayList<EventController>() );
116    
117        /**
118         * 
119         * @param name
120         * @param layer
121         * @param styles
122         */
123        protected Theme( String name, Layer layer, UserStyle[] styles ) {
124            this.layer = layer;
125            this.name = name;
126            displayElements = new ArrayList<DisplayElement>( 1000 );
127            setStyles( styles );
128        }
129    
130        /**
131         * sets the parent MapView of the Theme.
132         * 
133         */
134        public void setParent( MapView parent ) {
135            this.parent = parent;
136        }
137    
138        /**
139         * returns the name of the layer
140         * 
141         */
142        public String getName() {
143            return name;
144        }
145    
146        /**
147         * renders the layer to the submitted graphic context
148         */
149        public void paint( Graphics g ) {
150    
151            double scale = parent.getScale();
152    
153            if ( layer instanceof LazyRasterLayer ) {
154                // re-create raster displayelements to adapt current
155                // current boundingbox
156                createLazyRasterDisplayElements();
157            } else if ( layer instanceof OWSRasterLayer ) {
158                createOWSRasterDisplayElements();
159            }
160            for ( int i = 0; i < displayElements.size(); i++ ) {
161                DisplayElement de = displayElements.get( i );
162    
163                if ( de.doesScaleConstraintApply( scale ) ) {
164                    de.paint( g, parent.getProjection(), scale );
165                }
166            }
167    
168        }
169    
170        /**
171         * renders the display elements matching the submitted ids
172         */
173        public void paint( Graphics g, String[] ids ) {
174    
175            double scale = parent.getScale();
176    
177            if ( layer instanceof LazyRasterLayer ) {
178                // re-create raster displayelements to adapt current
179                // current boundingbox
180                createLazyRasterDisplayElements();
181            }
182    
183            for ( int k = 0; k < displayElements.size(); k++ ) {
184                DisplayElement de = displayElements.get( k );
185                for ( int i = 0; i < ids.length; i++ ) {
186                    if ( de.getAssociateFeatureId().equals( ids[i] ) ) {
187                        de.paint( g, parent.getProjection(), scale );
188                        break;
189                    }
190                }
191            }
192        }
193    
194        /**
195         * renders the selected display elements of the layer
196         */
197        public void paintSelected( Graphics g ) {
198    
199            double scale = parent.getScale();
200    
201            if ( layer instanceof LazyRasterLayer ) {
202                // re-create raster displayelements to adapt current
203                // current boundingbox
204                createLazyRasterDisplayElements();
205            }
206    
207            if ( layer instanceof OWSRasterLayer ) {
208    
209            }
210    
211            for ( int i = 0; i < displayElements.size(); i++ ) {
212                DisplayElement de = displayElements.get( i );
213                if ( de.isSelected() ) {
214                    de.paint( g, parent.getProjection(), scale );
215                }
216            }
217    
218        }
219    
220        /**
221         * renders the highlighted display elements of the layer
222         */
223        public void paintHighlighted( Graphics g ) {
224    
225            double scale = parent.getScale();
226    
227            if ( layer instanceof LazyRasterLayer ) {
228                // re-create raster displayelements to adapt current
229                // current boundingbox
230                createLazyRasterDisplayElements();
231            }
232    
233            for ( int i = 0; i < displayElements.size(); i++ ) {
234                DisplayElement de = displayElements.get( i );
235                if ( de.isHighlighted() ) {
236                    de.paint( g, parent.getProjection(), scale );
237                }
238            }
239    
240        }
241    
242        /**
243         * A selector is a class that offers methods for selecting and deselecting single
244         * DisplayElements or groups of DisplayElements. A selector may offers methods like 'select all
245         * DisplayElements within a specified bounding box' or 'select all DisplayElements thats area is
246         * larger than 120 km�' etc.
247         */
248        public void addSelector( Selector selector ) {
249            this.selector.add( selector );
250            selector.addTheme( this );
251        }
252    
253        /**
254         * @see org.deegree.graphics.Theme#addSelector(Selector)
255         */
256        public void removeSelector( Selector selector ) {
257            this.selector.remove( selector );
258            selector.removeTheme( this );
259        }
260    
261        /**
262         * A Highlighter is a class that is responsible for managing the highlight capabilities for one
263         * or more Themes.
264         */
265        public void addHighlighter( Highlighter highlighter ) {
266            this.highlighter.add( highlighter );
267            highlighter.addTheme( this );
268        }
269    
270        /**
271         * @see org.deegree.graphics.Theme#addHighlighter(Highlighter)
272         */
273        public void removeHighlighter( Highlighter highlighter ) {
274            this.highlighter.remove( highlighter );
275            highlighter.removeTheme( this );
276        }
277    
278        /**
279         * adds an eventcontroller to the theme that's reponsible for handling events that targets the
280         * theme.
281         */
282        public void addEventController( ThemeEventController controller ) {
283            eventController.add( controller );
284            controller.addTheme( this );
285        }
286    
287        /**
288         * @see org.deegree.graphics.Theme#addEventController(ThemeEventController)
289         */
290        public void removeEventController( ThemeEventController controller ) {
291            eventController.remove( controller );
292            controller.removeTheme( this );
293        }
294    
295        /**
296         * Sets the styles used for this <tt>Theme</tt>. If this method will be called all
297         * <tt>DisplayElement</tt>s will be recreated to consider the new style definitions.
298         * 
299         */
300        public void setStyles( UserStyle[] styles ) {
301    
302            this.styles = styles;
303            displayElements.clear();
304            if ( layer instanceof FeatureLayer ) {
305                createFeatureDisplayElements();
306            } else if ( layer instanceof RasterLayer ) {
307                createRasterDisplayElements();
308            } else {
309                createLazyRasterDisplayElements();
310            }
311    
312        }
313    
314        /**
315         * creates <code>DisplayElement</code>s for <code>Feature</code> instances
316         */
317        private void createFeatureDisplayElements() {
318            displayElements.clear();
319            DisplayElementFactory fac = new DisplayElementFactory();
320            // keep LabelDisplayElements separate from the other elements
321            // and append them to the end of the DisplayElement-list
322            List<DisplayElement> labelDisplayElements = new ArrayList<DisplayElement>( 100 );
323            try {
324                // instance of FeatureLayer
325                for ( int i = 0; i < ( (FeatureLayer) layer ).getSize(); i++ ) {
326                    Feature feature = ( (FeatureLayer) layer ).getFeature( i );
327                    featureToDisplayElement( styles, fac, labelDisplayElements, feature );
328                }
329            } catch ( Exception e ) {
330                LOG.logError( e.getMessage(), e );
331            }
332            displayElements.addAll( labelDisplayElements );
333        }
334    
335        /**
336         * creates <code>DisplayElement</code>s for <code>GridCoverage</code> instances
337         */
338        private void createRasterDisplayElements() {
339            displayElements.clear();
340            DisplayElementFactory fac = new DisplayElementFactory();
341            try {
342                // instance of RasterLayer
343                RasterLayer rl = (RasterLayer) layer;
344                DisplayElement[] de = fac.createDisplayElement( rl.getRaster(), styles );
345                for ( int k = 0; k < de.length; k++ ) {
346                    displayElements.add( de[k] );
347                }
348            } catch ( Exception e ) {
349                LOG.logError( e.getMessage(), e );
350            }
351        }
352    
353        /**
354         * creates <code>DisplayElement</code>s for <code>GridCoverage</code> instances that are
355         * loaded depending on current boundingbox.
356         */
357        private void createLazyRasterDisplayElements() {
358            displayElements.clear();
359            DisplayElementFactory fac = new DisplayElementFactory();
360            try {
361                if ( parent != null ) {
362                    LazyRasterLayer rl = (LazyRasterLayer) layer;
363                    double w = parent.getProjection().getDestRect().getWidth();
364                    double d = parent.getBoundingBox().getWidth() / w;
365                    GridCoverage gc = rl.getRaster( parent.getBoundingBox(), d );
366                    // gc can be null if e.g. the area covered by the raster
367                    // layer is outside the visible area.
368                    if ( gc != null ) {
369                        DisplayElement[] de = fac.createDisplayElement( gc, styles );
370                        for ( int k = 0; k < de.length; k++ ) {
371                            displayElements.add( de[k] );
372                        }
373                    }
374                }
375            } catch ( Exception e ) {
376                LOG.logError( e.getMessage(), e );
377            }
378        }
379    
380        private void createOWSRasterDisplayElements() {
381            displayElements.clear();
382    
383            DisplayElementFactory fac = new DisplayElementFactory();
384            try {
385                if ( parent != null ) {
386                    OWSRasterLayer rl = (OWSRasterLayer) layer;
387                    double w = parent.getProjection().getDestRect().getWidth();
388                    double h = parent.getProjection().getDestRect().getHeight();
389                    GridCoverage gc = rl.getRaster( parent.getBoundingBox(), w, h );
390                    if ( gc != null ) {
391                        DisplayElement[] de = fac.createDisplayElement( gc, styles );
392                        for ( int k = 0; k < de.length; k++ ) {
393                            displayElements.add( de[k] );
394                        }
395                    }
396                }
397            } catch ( Exception e ) {
398                LOG.logError( e.getMessage(), e );
399            }
400        }
401    
402        /**
403         * 
404         * @param styles
405         * @param fac
406         * @param labelDisplayElements
407         * @param feature
408         * @throws ClassNotFoundException
409         * @throws IllegalAccessException
410         * @throws InstantiationException
411         * @throws NoSuchMethodException
412         * @throws InvocationTargetException
413         * @throws GeometryException
414         * @throws PropertyPathResolvingException
415         */
416        private void featureToDisplayElement( UserStyle[] styles, DisplayElementFactory fac,
417                                              List<DisplayElement> labelDisplayElements, Feature feature )
418                                throws ClassNotFoundException, IllegalAccessException, InstantiationException,
419                                NoSuchMethodException, InvocationTargetException, GeometryException,
420                                PropertyPathResolvingException {
421            DisplayElement[] de = fac.createDisplayElement( feature, styles );
422            for ( int k = 0; k < de.length; k++ ) {
423                if ( de[k] instanceof LabelDisplayElement ) {
424                    labelDisplayElements.add( de[k] );
425                } else {
426                    displayElements.add( de[k] );
427                }
428            }
429            FeatureProperty[] fp = feature.getProperties();
430            for ( int i = 0; i < fp.length; i++ ) {
431                if ( fp[i].getValue() != null && fp[i].getValue() instanceof Feature ) {
432                    featureToDisplayElement( styles, fac, labelDisplayElements, (Feature) fp[i].getValue() );
433                }
434            }
435        }
436    
437        /**
438         * returns the styles used for this <tt>Theme</tt>.
439         * 
440         */
441        public UserStyle[] getStyles() {
442            return styles;
443        }
444    
445        /**
446         * returns the layer that holds the data of the theme
447         * 
448         */
449        public Layer getLayer() {
450            return layer;
451        }
452    
453        /**
454         * Returns all <tt>DisplayElements</tt> that this <tt>Theme</tt> contains.
455         * <p>
456         * 
457         * @return <tt>ArrayList</tt> containing <tt>DisplayElements</tt>
458         * 
459         */
460        public List<DisplayElement> getDisplayElements() {
461            return displayElements;
462        }
463    
464        /**
465         * returns the <tt>DisplayElements</tt> of the Theme
466         * 
467         */
468        public void setDisplayElements( List<DisplayElement> de ) {
469            this.displayElements = de;
470        }
471    
472    }