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