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