/[schmitzm]/branches/2.0-RC2/src/skrueger/geotools/LegendIconFeatureRenderer.java
ViewVC logotype

Annotation of /branches/2.0-RC2/src/skrueger/geotools/LegendIconFeatureRenderer.java

Parent Directory Parent Directory | Revision Log Revision Log


Revision 322 - (hide annotations)
Wed Aug 26 14:12:17 2009 UTC (15 years, 6 months ago) by alfonx
Original Path: branches/1.0-gt2-2.6/src/skrueger/geotools/LegendIconFeatureRenderer.java
File size: 19194 byte(s)
Moving to 
import org.opengis.feature.simple.SimpleFeature;

1 alfonx 244 /*******************************************************************************
2     * Copyright (c) 2009 Martin O. J. Schmitz.
3     *
4     * This file is part of the SCHMITZM library - a collection of utility
5 alfonx 256 * classes based on Java 1.6, focusing (not only) on Java Swing
6 alfonx 244 * and the Geotools library.
7     *
8     * The SCHMITZM project is hosted at:
9     * http://wald.intevation.org/projects/schmitzm/
10     *
11     * This program is free software; you can redistribute it and/or
12     * modify it under the terms of the GNU Lesser General Public License
13     * as published by the Free Software Foundation; either version 3
14     * of the License, or (at your option) any later version.
15     *
16     * This program is distributed in the hope that it will be useful,
17     * but WITHOUT ANY WARRANTY; without even the implied warranty of
18     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19     * GNU General Public License for more details.
20     *
21     * You should have received a copy of the GNU Lesser General Public License (license.txt)
22     * along with this program; if not, write to the Free Software
23     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24     * or try this link: http://www.gnu.org/licenses/lgpl.html
25     *
26     * Contributors:
27     * Martin O. J. Schmitz - initial API and implementation
28     * Stefan A. Krüger - additional utility classes
29     ******************************************************************************/
30 alfonx 154 /**
31     Copyright 2008 Stefan Alfons Krüger and parts from some Geotools code
32    
33     atlas-framework - This file is part of the Atlas Framework
34    
35     This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
36     This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
37     You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
38    
39     Diese Bibliothek ist freie Software; Sie dürfen sie unter den Bedingungen der GNU Lesser General Public License, wie von der Free Software Foundation veröffentlicht, weiterverteilen und/oder modifizieren; entweder gemäß Version 2.1 der Lizenz oder (nach Ihrer Option) jeder späteren Version.
40     Diese Bibliothek wird in der Hoffnung weiterverbreitet, daß sie nützlich sein wird, jedoch OHNE IRGENDEINE GARANTIE, auch ohne die implizierte Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Mehr Details finden Sie in der GNU Lesser General Public License.
41     Sie sollten eine Kopie der GNU Lesser General Public License zusammen mit dieser Bibliothek erhalten haben; falls nicht, schreiben Sie an die Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA.
42     **/
43     package skrueger.geotools;
44    
45     import java.awt.Canvas;
46     import java.awt.Color;
47     import java.awt.Dimension;
48     import java.awt.FontMetrics;
49     import java.awt.Graphics;
50     import java.awt.Graphics2D;
51     import java.awt.RenderingHints;
52     import java.awt.image.BufferedImage;
53     import java.awt.image.ImageObserver;
54     import java.util.ArrayList;
55     import java.util.List;
56    
57     import javax.swing.tree.DefaultTreeCellRenderer;
58    
59     import org.apache.log4j.Logger;
60     import org.geotools.factory.GeoTools;
61     import org.geotools.factory.Hints;
62     import org.geotools.feature.AttributeType;
63     import org.geotools.feature.IllegalAttributeException;
64 alfonx 322 import org.opengis.feature.simple.SimpleFeature;
65     import org.opengis.feature.simple.SimpleFeatureType;
66 alfonx 154 import org.geotools.geometry.jts.LiteShape2;
67     import org.geotools.renderer.lite.StyledShapePainter;
68     import org.geotools.renderer.style.SLDStyleFactory;
69     import org.geotools.renderer.style.Style2D;
70     import org.geotools.styling.LineSymbolizer;
71     import org.geotools.styling.PointSymbolizer;
72     import org.geotools.styling.PolygonSymbolizer;
73     import org.geotools.styling.RasterSymbolizer;
74     import org.geotools.styling.Rule;
75     import org.geotools.styling.Style;
76     import org.geotools.styling.Symbolizer;
77     import org.geotools.styling.TextSymbolizer;
78     import org.geotools.util.NumberRange;
79    
80     import schmitzm.geotools.feature.FeatureUtil;
81    
82     import com.vividsolutions.jts.geom.Coordinate;
83     import com.vividsolutions.jts.geom.GeometryFactory;
84     import com.vividsolutions.jts.geom.LineString;
85     import com.vividsolutions.jts.geom.LinearRing;
86     import com.vividsolutions.jts.geom.Polygon;
87    
88     /**
89     * Based on geoserver!
90     * <hr>
91     * <b>Changes by <a href="mailto:[email protected]">Martin Schmitz</a></b>
92     * <br>
93     * <ul>
94     * <li>07.02.2008:<br>
95 alfonx 318 * Determining the default values of a {@link SimpleFeatureType} by
96     * {@link FeatureUtil#getDefaultAttributeValues(SimpleFeatureType)} instead of using
97 alfonx 154 * {@link AttributeType#createDefaultValue()} directly, because the latter
98     * returns {@code null} even though the attribut is not nillable.</li>
99     * </ul>
100     *
101     * @author Stefan Alfons Krüger
102     */
103     public class LegendIconFeatureRenderer extends DefaultTreeCellRenderer {
104     private static final Dimension SIZE = new Dimension(30,20);
105    
106     Logger LOGGER = Logger.getLogger(LegendIconFeatureRenderer.class);
107    
108     /**
109     * This is a static class
110     */
111     private LegendIconFeatureRenderer() {
112     }
113    
114     final static LegendIconFeatureRenderer instance = new LegendIconFeatureRenderer();
115    
116     public static LegendIconFeatureRenderer getInstance() {
117     // In GT 2.4.5 it we have to create a new one all the time!
118     return new LegendIconFeatureRenderer();
119     // return instance;
120     }
121    
122     /** The image produced at <code>produceLegendGraphic</code> */
123     private BufferedImage legendGraphic;
124    
125     /**
126     * used to create sample point shapes with LiteShape (not lines nor
127     * polygons)
128     */
129     private static final GeometryFactory geomFac = new GeometryFactory();
130    
131     /** padding percentage factor at both sides of the legend. */
132     private static final float hpaddingFactor = 0.11f; // was 0.15
133    
134     /** top & bottom padding percentage factor for the legend */
135     private static final float vpaddingFactor = 0.08f; // was 0.15
136    
137     /**
138     * Image observer to help in creating the stack like legend graphic from the
139     * images created for each rule
140     */
141     private static final ImageObserver imgObs = new Canvas();
142    
143     /**
144     * Just a holder to avoid creating many polygon shapes from inside
145     * <code>getSampleShape()</code>
146     */
147     private LiteShape2 sampleRect;
148    
149     /**
150     * Just a holder to avoid creating many line shapes from inside
151     * <code>getSampleShape()</code>
152     */
153     private LiteShape2 sampleLine;
154    
155     /**
156     * Just a holder to avoid creating many point shapes from inside
157     * <code>getSampleShape()</code>
158     */
159     private LiteShape2 samplePoint;
160    
161     private Hints hints;
162    
163     /**
164     * Returns a <code>java.awt.Shape</code> appropiate to render a legend
165     * graphic given the symbolizer type and the legend dimensions.
166     *
167     * @param symbolizer
168     * the Symbolizer for whose type a sample shape will be created
169     * @param legendWidth
170     * the requested width, in output units, of the legend graphic
171     * @param legendHeight
172     * the requested height, in output units, of the legend graphic
173     *
174     * @return an appropiate Line2D, Rectangle2D or LiteShape(Point) for the
175     * symbolizer, wether it is a LineSymbolizer, a PolygonSymbolizer,
176     * or a Point ot Text Symbolizer
177     *
178     * @throws IllegalArgumentException
179     * if an unknown symbolizer impl was passed in.
180     */
181     private LiteShape2 getSampleShape(Symbolizer symbolizer, int legendWidth,
182     int legendHeight) {
183     LiteShape2 sampleShape;
184     final float hpad = (legendWidth * hpaddingFactor);
185     final float vpad = (legendHeight * vpaddingFactor);
186    
187     if (symbolizer instanceof LineSymbolizer) {
188     if (this.sampleLine == null) {
189     Coordinate[] coords = {
190     new Coordinate(hpad, legendHeight - vpad),
191     new Coordinate(legendWidth - hpad, vpad) };
192     LineString geom = geomFac.createLineString(coords);
193    
194     try {
195     this.sampleLine = new LiteShape2(geom, null, null, false);
196     } catch (Exception e) {
197     this.sampleLine = null;
198     }
199     }
200    
201     sampleShape = this.sampleLine;
202     } else if ((symbolizer instanceof PolygonSymbolizer)
203     || (symbolizer instanceof RasterSymbolizer)) {
204     if (this.sampleRect == null) {
205     final float w = legendWidth - (2 * hpad);
206     final float h = legendHeight - (2 * vpad);
207    
208     Coordinate[] coords = { new Coordinate(hpad, vpad),
209     new Coordinate(hpad, vpad + h),
210     new Coordinate(hpad + w, vpad + h),
211     new Coordinate(hpad + w, vpad),
212     new Coordinate(hpad, vpad) };
213     LinearRing shell = geomFac.createLinearRing(coords);
214     Polygon geom = geomFac.createPolygon(shell, null);
215    
216     try {
217     this.sampleRect = new LiteShape2(geom, null, null, false);
218     } catch (Exception e) {
219     this.sampleRect = null;
220     }
221     }
222    
223     sampleShape = this.sampleRect;
224     } else if (symbolizer instanceof PointSymbolizer
225     || symbolizer instanceof TextSymbolizer) {
226     if (this.samplePoint == null) {
227     Coordinate coord = new Coordinate(legendWidth / 2,
228     legendHeight / 2);
229    
230     try {
231     this.samplePoint = new LiteShape2(geomFac
232     .createPoint(coord), null, null, false);
233     } catch (Exception e) {
234     this.samplePoint = null;
235     }
236     }
237    
238     sampleShape = this.samplePoint;
239     } else {
240     throw new IllegalArgumentException("Unknown symbolizer: "
241     + symbolizer);
242     }
243    
244     return sampleShape;
245     }
246    
247     /**
248     * Puts a BufferedImage into this.legendGraphic
249     */
250 alfonx 318 public void produceLegendGraphic(SimpleFeatureType featureType, Style gt2Style,
251 alfonx 154 Rule[] applicableRules) {
252    
253     // final FeatureTypeStyle[] ftStyles = gt2Style.getFeatureTypeStyles();
254    
255     // if (request.getRule() != null) {
256     // applicableRules = new Rule[] { request.getRule() };
257     // } else {
258     // applicableRules = getApplicableRules(ftStyles, scaleDenominator);
259     // }
260    
261     final int ruleCount = applicableRules.length;
262    
263     /**
264     * A legend graphic is produced for each applicable rule. They're being
265     * holded here until the process is done and then painted on a "stack"
266     * like legend.
267     */
268     final List<BufferedImage> legendsStack = new ArrayList<BufferedImage>(
269     ruleCount);
270    
271     for (int i = 0; i < ruleCount; i++) {
272     BufferedImage image = createImageForRule(applicableRules[i],
273     featureType, SIZE);
274     legendsStack.add(image);
275     }
276    
277     // this.legendGraphic =
278     // scaleImage(mergeLegends(legendsStack,applicableRules), request);
279     this.legendGraphic = mergeLegends(legendsStack, applicableRules);
280     }
281    
282     /**
283     * Recieves a list of <code>BufferedImages</code> and produces a new one
284     * which holds all the images in <code>imageStack</code> one above the
285     * other.
286     *
287     * @param imageStack
288     * the list of BufferedImages, one for each applicable Rule
289     * @param rules
290     * The applicable rules, one for each image in the stack
291     * @param request
292     * The request.
293     *
294     * @return the stack image with all the images on the argument list.
295     *
296     * @throws IllegalArgumentException
297     * if the list is empty
298     */
299     private static BufferedImage mergeLegends(List<BufferedImage> imageStack,
300     Rule[] rules) {
301     if (imageStack.size() == 0) {
302     throw new IllegalArgumentException("No legend graphics passed");
303     }
304    
305     BufferedImage finalLegend = null;
306    
307     if (imageStack.size() == 1) {
308     finalLegend = (BufferedImage) imageStack.get(0);
309     } else {
310     final int imgCount = imageStack.size();
311     final String[] labels = new String[imgCount];
312    
313     BufferedImage img = ((BufferedImage) imageStack.get(0));
314     FontMetrics fontMetrics = img.getGraphics().getFontMetrics();
315    
316     final int rowHeight = Math.max(fontMetrics.getHeight(), img
317     .getHeight());
318    
319     // calculate the total dimensions of the image
320     int totalHeight = rowHeight * imgCount;
321     int totalWidth = 0;
322    
323     for (int i = 0; i < imgCount; i++) {
324     img = (BufferedImage) imageStack.get(i);
325    
326     Rule rule = rules[i];
327    
328     // does this rule have a label
329     labels[i] = rule.getTitle();
330    
331     if (labels[i] == null) {
332     labels[i] = rule.getName();
333     }
334    
335     int w = img.getWidth();
336    
337     if (labels[i] != null) {
338     Graphics g = img.getGraphics();
339     w += g.getFontMetrics().stringWidth(labels[i]);
340     }
341    
342     totalWidth = Math.max(w, totalWidth);
343     }
344    
345     // create the final image
346     finalLegend = new BufferedImage(totalWidth, totalHeight,
347     BufferedImage.TYPE_INT_ARGB);
348    
349     Graphics2D finalGraphics = finalLegend.createGraphics();
350    
351     finalGraphics.setColor(Color.white);
352     finalGraphics.fillRect(0, 0, totalWidth, totalHeight);
353    
354     int h = 0;
355    
356     for (int i = 0; i < imgCount; i++) {
357     img = (BufferedImage) imageStack.get(i);
358    
359     // draw the image
360     int y = h;
361    
362     if (img.getHeight() < rowHeight) {
363     // move the image to the center of the row
364     y += (int) ((rowHeight - img.getHeight()) / 2d);
365     }
366    
367     finalGraphics.drawImage(img, 0, y, imgObs);
368    
369     // draw the label
370     if (labels[i] != null) {
371     finalGraphics.setColor(Color.BLACK);
372    
373     y = (h + rowHeight) - fontMetrics.getDescent();
374    
375     if (fontMetrics.getHeight() < rowHeight) {
376     // move the baseline to the center of the row
377     y -= (int) ((rowHeight - fontMetrics.getHeight()) / 2d);
378     }
379    
380     finalGraphics.drawString(labels[i], img.getWidth(), y);
381     }
382    
383     h += rowHeight;
384     }
385     }
386    
387     return finalLegend;
388     }
389    
390     /**
391     * DOCUMENT ME!
392     *
393     * @return
394     *
395     * @throws IllegalStateException
396     * DOCUMENT ME!
397     */
398     public BufferedImage getLegendGraphic() {
399     if (this.legendGraphic == null) {
400     throw new IllegalStateException();
401     }
402     return this.legendGraphic;
403     }
404    
405     // /**
406     // * Paints a little rectangle in the color defined by the
407     // * {@link ColorMapEntry}
408     // *
409     // * unused
410     // *
411     // * @param cme
412     // * {@link ColorMapEntry}
413     // * @param cm
414     // * {@link ColorMap} to determine type (VALUES, INTERVALL )
415     // * @param iconWidth
416     // * Size of the rectangle
417     // * @param iconHeight
418     // * Size of the rectangle
419     // * @return {@link ImageIcon} of that color, maybe with a white border
420     // * @author Stefan Alfons Krüger
421     // */
422     // public static ImageIcon createImageForColorMapEntry(ColorMapEntry cme,
423     // ColorMap cm, Integer iconWidth, Integer iconHeight) {
424     //
425     // BufferedImage imageForRule = new BufferedImage(iconWidth, iconHeight,
426     // BufferedImage.TYPE_INT_ARGB);
427     //
428     // final Color color = StylingUtil.getColorFromColorMapEntry(cme);
429     //
430     // // Paint a block
431     // for (int x = 0; x < iconWidth; x++) {
432     // for (int y = 0; y < iconHeight; y++) {
433     // int rgb = color.getRGB();
434     // int rand = 2;
435     //
436     // // Paint a white border if the type is VALUES
437     // if ((x < rand) || (x > iconWidth - rand) || (y < rand)
438     // || (y > iconHeight - rand)) {
439     // if (cm.getType() == ColorMap.TYPE_VALUES) {
440     // rgb = Color.white.getRGB();
441     // }
442     // }
443     // imageForRule.setRGB(x, y, rgb);
444     // }
445     // }
446     // return new ImageIcon(imageForRule);
447     // }
448    
449     /**
450     * Creates a little BufferedImage that presents the Style/Symbols used by
451 alfonx 318 * the {@link MapLegend} to show a legend for the {@link SimpleFeatureType}
452 alfonx 154 *
453     * @param rule
454     * {@link Rule} that provides the text and the style to present
455     * @param featureType
456 alfonx 318 * Schema that describes the kind of the sample {@link SimpleFeature}
457 alfonx 154 * that will be rendered with the {@link Style}
458     * @param bg
459     * Background {@link Color} or <code>null</code>
460     *
461     * @throws IllegalAttributeException
462     */
463 alfonx 318 public BufferedImage createImageForRule(Rule rule, SimpleFeatureType featureType,
464 alfonx 154 Dimension size, Color bg) {
465    
466     Symbolizer[] symbolizers = rule.getSymbolizers();
467    
468     BufferedImage buffImage = new BufferedImage(size.width, size.height,
469     BufferedImage.TYPE_INT_ARGB);
470     Graphics2D graphics = buffImage.createGraphics();
471    
472     if (bg != null) {
473     // ****************************************************************************
474     // The following lines define the backgroup color for the rendered
475     // images
476     // ****************************************************************************
477     graphics.setBackground(bg);
478     graphics.setColor(bg);
479     graphics.fillRect(0, 0, size.width, size.height);
480     }
481    
482     // Enable anti-aliasing for the legend symbols
483     graphics.setRenderingHints(getHints());
484    
485     // TODO scaleDenominator = 100000000000000000000000000000d; Was ist das
486     // fuer ein Quatsch?
487     final double scaleDenominator = 100000000000000000000000000000d;
488     final NumberRange scaleRange = new NumberRange(scaleDenominator,
489     scaleDenominator);
490    
491     try {
492    
493     for (int sIdx = 0; sIdx < symbolizers.length; sIdx++) {
494     Symbolizer symbolizer = symbolizers[sIdx];
495    
496     if (symbolizer instanceof TextSymbolizer) {
497     LOGGER
498     .warn("createImageForRule for a TextSymbolizer. Sure we want that?");
499     continue;
500     }
501    
502     // if (symbolizer instanceof RasterSymbolizer) {
503     // log.warn("createImageForRule method can't be used for
504     // RasterSymbolizers..");
505     // }
506     // else
507 alfonx 318 final SimpleFeature sampleFeature = FeatureUtil.createSampleFeature(featureType);
508 alfonx 154
509     // The SLDStyleFactory has to be recreated, as it seams to cache
510     // some stuff.
511     SLDStyleFactory sldStyleFactory = new SLDStyleFactory();
512     Style2D style2d = sldStyleFactory.createStyle(sampleFeature,
513     symbolizer, scaleRange);
514    
515     LiteShape2 shape = getSampleShape(symbolizer, size.width,
516     size.height);
517    
518     if (style2d != null) {
519     new StyledShapePainter(null).paint(graphics, shape,
520     style2d, scaleDenominator);
521     }
522     }
523     } catch (Exception e) {
524     LOGGER
525     .error(
526     "Error during createImageForRule, returning empty Image",
527     e);
528     }
529     return buffImage;
530     }
531    
532     /**
533     * Define Java2D and other Hints
534     *
535     * @param hints
536     * @author <a href="mailto:[email protected]">Stefan Alfons
537     * Kr&uuml;ger</a>
538     */
539     public void setHints(Hints hints) {
540     getHints().add(hints);
541     }
542    
543     private Hints getHints() {
544     if (hints == null) {
545     hints = GeoTools.getDefaultHints();
546     hints.put(RenderingHints.KEY_ANTIALIASING,
547     RenderingHints.VALUE_ANTIALIAS_ON);
548     hints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
549     RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
550     hints.put(RenderingHints.KEY_ALPHA_INTERPOLATION,
551     RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
552     hints.put(RenderingHints.KEY_RENDERING,
553     RenderingHints.VALUE_RENDER_QUALITY);
554     hints.put(RenderingHints.KEY_COLOR_RENDERING,
555     RenderingHints.VALUE_COLOR_RENDER_QUALITY);
556     }
557     return hints;
558     }
559    
560     /**
561     * Creates a little BufferedImage that presents the Style/Symbols used by
562 alfonx 318 * the {@link MapLegend} to show a legend for the {@link SimpleFeatureType}
563 alfonx 154 *
564     * @param rule
565     * {@link Rule} that provides the text and the style to present
566     * @param featureType
567 alfonx 318 * Schema that describes the kind of the sample {@link SimpleFeature}
568 alfonx 154 * that will be rendered with the {@link Style}
569     *
570     * @throws IllegalAttributeException
571     */
572     public BufferedImage createImageForRule(final Rule rule,
573 alfonx 318 final SimpleFeatureType featureType, final Dimension size) {
574 alfonx 154 return createImageForRule(rule, featureType, size, null);
575     }
576    
577     }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26