/[schmitzm]/trunk/src/skrueger/geotools/LegendIconFeatureRenderer.java
ViewVC logotype

Contents of /trunk/src/skrueger/geotools/LegendIconFeatureRenderer.java

Parent Directory Parent Directory | Revision Log Revision Log


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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26