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