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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 526 - (show annotations)
Wed Nov 18 15:45:01 2009 UTC (15 years, 3 months ago) by alfonx
Original Path: branches/1.0-gt2-2.6/src/skrueger/geotools/StyledLayerUtil.java
File size: 41397 byte(s)
* a debug message was able to trigger an NPE
1 /*******************************************************************************
2 * Copyright (c) 2009 Martin O. J. Schmitz.
3 *
4 * This file is part of the SCHMITZM library - a collection of utility
5 * classes based on Java 1.6, focusing (not only) on Java Swing
6 * 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 package skrueger.geotools;
31
32 import java.awt.Color;
33 import java.awt.Dimension;
34 import java.awt.Graphics2D;
35 import java.awt.Rectangle;
36 import java.awt.geom.AffineTransform;
37 import java.awt.image.BufferedImage;
38 import java.awt.image.ColorModel;
39 import java.awt.image.ComponentColorModel;
40 import java.awt.image.DataBuffer;
41 import java.io.File;
42 import java.io.FileNotFoundException;
43 import java.io.FileWriter;
44 import java.net.URL;
45 import java.text.DecimalFormat;
46 import java.util.List;
47 import java.util.Map;
48
49 import javax.swing.BorderFactory;
50 import javax.swing.ImageIcon;
51 import javax.swing.JComponent;
52 import javax.swing.JLabel;
53
54 import net.miginfocom.swing.MigLayout;
55
56 import org.apache.log4j.Logger;
57 import org.geotools.coverage.grid.GeneralGridEnvelope;
58 import org.geotools.coverage.grid.GridCoverage2D;
59 import org.geotools.coverage.grid.GridGeometry2D;
60 import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
61 import org.geotools.coverage.grid.io.AbstractGridFormat;
62 import org.geotools.feature.FeatureCollection;
63 import org.geotools.feature.NameImpl;
64 import org.geotools.geometry.jts.ReferencedEnvelope;
65 import org.geotools.map.DefaultMapLayer;
66 import org.geotools.map.MapLayer;
67 import org.geotools.parameter.Parameter;
68 import org.geotools.renderer.lite.gridcoverage2d.GridCoverageRenderer;
69 import org.geotools.styling.ColorMap;
70 import org.geotools.styling.ColorMapEntry;
71 import org.geotools.styling.FeatureTypeStyle;
72 import org.geotools.styling.RasterSymbolizer;
73 import org.geotools.styling.Rule;
74 import org.geotools.styling.Style;
75 import org.jdom.Document;
76 import org.jdom.Element;
77 import org.jdom.input.SAXBuilder;
78 import org.jdom.output.XMLOutputter;
79 import org.opengis.feature.simple.SimpleFeature;
80 import org.opengis.feature.simple.SimpleFeatureType;
81 import org.opengis.feature.type.Name;
82 import org.opengis.parameter.GeneralParameterValue;
83
84 import schmitzm.geotools.JTSUtil;
85 import schmitzm.geotools.feature.FeatureUtil;
86 import schmitzm.geotools.styling.StylingUtil;
87 import schmitzm.io.IOUtil;
88 import schmitzm.lang.LangUtil;
89 import schmitzm.swing.JPanel;
90 import schmitzm.swing.SwingUtil;
91 import skrueger.AttributeMetadata;
92 import skrueger.RasterLegendData;
93 import skrueger.i8n.Translation;
94
95 /**
96 * This class provides static helper methods for dealing with
97 * {@link StyledLayerInterface} stuff.
98 *
99 * @author <a href="mailto:[email protected]">Martin Schmitz</a>
100 * (University of Bonn/Germany)
101 * @version 1.0
102 */
103 public class StyledLayerUtil {
104 private static final Logger LOGGER = Logger.getLogger(StyledLayerUtil.class
105 .getName());
106 private static final SAXBuilder SAX_BUILDER = new SAXBuilder();
107 private static final XMLOutputter XML_OUTPUTTER = new XMLOutputter();
108
109 /** URL for Atlas XML schema */
110 public static final String AMLURI = "http://www.wikisquare.de/AtlasML";
111 /** Name of the XML Element for the attribute meta data map */
112 public static final String ELEM_NAME_AMD = "attributeMetaData";
113 /** Name of the XML Element for the raster legend data */
114 public static final String ELEM_NAME_RLD = "rasterLegendData";
115 /** Name of the XML Element for an attribute meta data map entry */
116 public static final String ELEM_NAME_ATTRIBUTE = "dataAttribute";
117 /** Name of the XML Element for an raster legend data entry */
118 public static final String ELEM_NAME_RASTERLEGEND = "rasterLegendItem";
119 /** Name of the XML Element for a translation */
120 public static final String ELEM_NAME_TRANSLATION = "translation";
121
122 /**
123 * Creates a Geotools {@link MapLayer} from an object. If the object is a
124 * {@link StyledLayerInterface} then its sytle is used. In case of direct
125 * Geotools objects ({@link GridCoverage2D},
126 * {@link AbstractGridCoverage2DReader}, {@link FeatureCollection}) a
127 * default style is generated.
128 *
129 * @param object
130 * an Object
131 * @exception Exception
132 * if {@code null} is given as object or an error occurs
133 * during layer creation
134 */
135 public static MapLayer createMapLayer(final Object object) throws Exception {
136 return createMapLayer(object, null);
137 }
138
139 /**
140 * Creates a Geotools {@link MapLayer} from an object. If the object is a
141 * {@link StyledLayerInterface} then its sytle is used. In case of direct
142 * Geotools objects ({@link GridCoverage2D},
143 * {@link AbstractGridCoverage2DReader}, {@link FeatureCollection}) a
144 * default style is generated.
145 *
146 * @param object
147 * an Object
148 * @param forcedStyle
149 * (SLD-)Style to force for the object
150 * @exception Exception
151 * if {@code null} is given as object or an error occurs
152 * during layer creation
153 */
154 public static MapLayer createMapLayer(Object object, final Style forcedStyle)
155 throws Exception {
156 MapLayer layer = null;
157 Style style = null;
158 if (object instanceof StyledLayerInterface) {
159 style = ((StyledLayerInterface<?>) object).getStyle();
160 object = ((StyledLayerInterface<?>) object).getGeoObject();
161 }
162 if (forcedStyle != null)
163 style = forcedStyle;
164 if (style == null)
165 style = StylingUtil.createDefaultStyle(object);
166
167 if (object instanceof GridCoverage2D)
168 layer = new DefaultMapLayer((GridCoverage2D) object, style);
169 if (object instanceof AbstractGridCoverage2DReader)
170 layer = new DefaultMapLayer((AbstractGridCoverage2DReader) object,
171 style);
172 if (object instanceof FeatureCollection)
173 layer = new DefaultMapLayer((FeatureCollection) object, style);
174
175 if (layer == null)
176 throw new Exception("Can not create MapLayer from "
177 + (object == null ? "null" : object.getClass()));
178
179 return layer;
180 }
181
182 /**
183 * Creates an default instance of {@link StyledLayerInterface} for a
184 * Geotools object ({@link GridCoverage2D}, {@link FeatureCollection}) with
185 * a default style.
186 *
187 * @param object
188 * an Object
189 * @param title
190 * title for the object
191 * @exception UnsupportedOperationException
192 * if {@code null} is given as object or an error occurs
193 * during creation
194 */
195 public static StyledLayerInterface<?> createStyledLayer(
196 final Object object, final String title) {
197 return createStyledLayer(object, title, null);
198 }
199
200 /**
201 * Creates an default instance of {@link StyledLayerInterface} for a
202 * Geotools object ({@link GridCoverage2D}, {@link FeatureCollection}) with
203 * a given style.
204 *
205 * @param object
206 * an Object
207 * @param title
208 * title for the object
209 * @param style
210 * style and meta data for the object
211 * @exception UnsupportedOperationException
212 * if {@code null} is given as object or an error occurs
213 * during creation
214 */
215 public static StyledLayerInterface<?> createStyledLayer(
216 final Object object, final String title,
217 final StyledLayerStyle style) {
218 StyledLayerInterface<?> styledLayer = null;
219
220 final String id = (title != null) ? title : "defaultID";
221
222 if (object instanceof GridCoverage2D)
223 styledLayer = new StyledGridCoverage((GridCoverage2D) object, id,
224 title, style);
225 else if (object instanceof AbstractGridCoverage2DReader)
226 styledLayer = new StyledGridCoverageReader(
227 (AbstractGridCoverage2DReader) object, id, title, style);
228 else if (object instanceof FeatureCollection)
229 styledLayer = new StyledFeatureCollection(
230 (FeatureCollection) object, id, title, style);
231
232 if (styledLayer == null)
233 throw new UnsupportedOperationException(
234 "Can not create StyledLayerInterface object from "
235 + (object == null ? "null" : object.getClass()));
236
237 return styledLayer;
238 }
239
240 /**
241 * Return only the visible or invisible entries of an AttributeMetaData-Map.
242 *
243 * @param amdMap
244 * AttributeMetaData-Map
245 * @param visible
246 * indicated whether the visible or invisible entries are
247 * returned
248 */
249 public static AttributeMetadataMap getVisibleAttributeMetaData(
250 final AttributeMetadataMap amdMap, final boolean visible) {
251
252 final AttributeMetadataMap filteredMap = new AttributeMetadataMap();
253 for (final AttributeMetadata amd : amdMap.values())
254 if (amd.isVisible() == visible)
255 filteredMap.put(amd.getName(), amd);
256
257 return filteredMap;
258 }
259
260 /**
261 * Parses a {@link AttributeMetadata} object from an JDOM-{@link Element}.
262 * This method works like {@link
263 * AMLImport#parseDataAttribute(org.w3c.dom.Node}, but for JDOM.
264 *
265 * @param element
266 * {@link Element} to parse
267 */
268 public static AttributeMetadata parseAttributeMetaData(final Element element) {
269 final String namespace = String.valueOf(element
270 .getAttributeValue("namespace"));
271 final String localname = String.valueOf(element
272 .getAttributeValue("localname"));
273 final Name aName = new NameImpl(namespace, localname);
274 final Boolean visible = Boolean.valueOf(element
275 .getAttributeValue("visible"));
276 final String unit = element.getAttributeValue("unit");
277
278 Translation name = new Translation();
279 Translation desc = new Translation();
280 for (final Element childElement : (List<Element>) element.getChildren()) {
281 if (childElement.getName() == null)
282 continue;
283
284 if (childElement.getName().equals("name"))
285 name = parseTranslation(childElement);
286 else if (childElement.getName().equals("desc"))
287 desc = parseTranslation(childElement);
288 }
289 return new AttributeMetadata(aName, visible, name, desc, unit);
290 }
291
292 /**
293 * Parses a {@link AttributeMetadata} map from an JDOM-{@link Element} with
294 * {@code <attribute>}-childs.
295 *
296 * @param element
297 * {@link Element} to parse
298 */
299 public static AttributeMetadataMap parseAttributeMetaDataMap(
300 final Element element) {
301 final AttributeMetadataMap metaData = new AttributeMetadataMap();
302 final List<Element> attributesElements = element
303 .getChildren(ELEM_NAME_ATTRIBUTE);
304 for (final Element attibuteElement : attributesElements) {
305 final AttributeMetadata attrMetaData = parseAttributeMetaData(attibuteElement);
306 metaData.put(attrMetaData.getName(), attrMetaData);
307 }
308 return metaData;
309 }
310
311 /**
312 * Loads a {@link AttributeMetadata} object from an URL.
313 *
314 * @param documentUrl
315 * {@link URL} to parse
316 * @see #parseAttributeMetaData(Element)
317 */
318 public static AttributeMetadataMap loadAttributeMetaDataMap(
319 final URL documentUrl) throws Exception {
320 final Document document = SAX_BUILDER.build(documentUrl);
321 return parseAttributeMetaDataMap(document.getRootElement());
322 }
323
324 /**
325 * Creates an JDOM {@link Element} for the given {@link AttributeMetadata}
326 * object.
327 *
328 * @param amd
329 * meta data for one attribute
330 */
331 public static Element createAttributeMetaDataElement(
332 final AttributeMetadata amd) {
333 final Element element = new Element(ELEM_NAME_ATTRIBUTE, AMLURI);
334 element.setAttribute("namespace", String.valueOf(amd.getName()
335 .getNamespaceURI()));
336 element.setAttribute("localname", String.valueOf(amd.getLocalName()));
337 element.setAttribute("visible", String.valueOf(amd.isVisible()));
338 element.setAttribute("unit", amd.getUnit());
339 // Creating a aml:name tag...
340 element.addContent(createTranslationElement("name", amd.getTitle()));
341 // Creating a aml:desc tag...
342 element.addContent(createTranslationElement("desc", amd.getDesc()));
343 return element;
344 }
345
346 /**
347 * Creates an JDOM {@link Element} for the given {@link AttributeMetadata}
348 * map.
349 *
350 * @param amdMap
351 * map of attribute meta data
352 */
353 public static Element createAttributeMetaDataMapElement(
354 final AttributeMetadataMap amdMap) {
355 final Element element = new Element(ELEM_NAME_AMD, AMLURI);
356 for (final AttributeMetadata amd : amdMap.values())
357 element.addContent(createAttributeMetaDataElement(amd));
358 return element;
359 }
360
361 /**
362 * Saves a {@link AttributeMetadata AttributeMetaData-Map} to an URL.
363 *
364 * @param amdMap
365 * map of {@link AttributeMetadata}
366 * @param documentUrl
367 * {@link URL} to store the XML
368 */
369 public static void saveAttributeMetaDataMap(
370 final AttributeMetadataMap amdMap, final URL documentUrl)
371 throws Exception {
372 // Create XML-Document
373 final FileWriter out = new FileWriter(new File(documentUrl.toURI()));
374 XML_OUTPUTTER.output(createAttributeMetaDataMapElement(amdMap), out);
375 out.flush();
376 out.close();
377 }
378
379 /**
380 * Parses a {@link RasterLegendData} object from an JDOM-{@link Element}.
381 * This method works like {@link
382 * AMLImport#parseRasterLegendData(org.w3c.dom.Node}, but for JDOM.
383 *
384 * @param element
385 * {@link Element} to parse
386 */
387 public static RasterLegendData parseRasterLegendData(final Element element) {
388
389 final boolean paintGaps = Boolean.valueOf(element
390 .getAttributeValue("paintGaps"));
391
392 final RasterLegendData rld = new RasterLegendData(paintGaps);
393
394 for (final Element childElement : (List<Element>) element.getChildren()) {
395 final String name = childElement.getName();
396 // Cancel if it's an attribute
397 if (childElement.getChildren().size() == 0)
398 continue;
399
400 if (name.equals(ELEM_NAME_RASTERLEGEND)) {
401 final String valueAttr = childElement
402 .getAttributeValue("value");
403 if (valueAttr == null)
404 throw new UnsupportedOperationException(
405 "Attribute 'value' missing for definition of <"
406 + ELEM_NAME_RASTERLEGEND + ">");
407 final double value = Double.valueOf(valueAttr);
408
409 // first and only item should be the label
410 final Element labelElement = childElement.getChild("label");
411 // id label element is missing, the translation is searched
412 // directly
413 // as childs of the rasterLegendItem element
414 final Translation label = parseTranslation(labelElement != null ? labelElement
415 : childElement);
416 rld.put(value, label);
417 }
418 }
419
420 return rld;
421 }
422
423 /**
424 * Loads a {@link RasterLegendData} object from an URL.
425 *
426 * @param documentUrl
427 * {@link URL} to parse
428 * @see #parseAttributeMetaData(Element)
429 */
430 public static RasterLegendData loadRasterLegendData(final URL documentUrl)
431 throws Exception {
432 final Document document = SAX_BUILDER.build(documentUrl);
433 return parseRasterLegendData(document.getRootElement());
434 }
435
436 /**
437 * Creates an JDOM {@link Element} for the given {@link RasterLegendData}
438 * map.
439 *
440 * @param rld
441 * raster legend data
442 */
443 public static Element createRasterLegendDataElement(
444 final RasterLegendData rld) {
445 final Element element = new Element(ELEM_NAME_RLD, AMLURI);
446 element.setAttribute("paintGaps", rld.isPaintGaps().toString());
447 for (final Double key : rld.getSortedKeys()) {
448 final Element item = new Element(ELEM_NAME_RASTERLEGEND, AMLURI);
449 item.setAttribute("value", key.toString());
450 item.addContent(createTranslationElement("label", rld.get(key)));
451 element.addContent(item);
452 }
453 return element;
454 }
455
456 /**
457 * Creates {@link RasterLegendData} from a {@link ColorMap}.
458 *
459 * @param colorMap
460 * a color map
461 * @param paintGaps
462 * indicated whether gaps are painted between the legend items
463 * @param digits
464 * number of digits the grid value classes (and legend) are
465 * rounded to (null means no round; >= 0 means digits after
466 * comma; < 0 means digits before comma)
467 */
468 public static RasterLegendData generateRasterLegendData(
469 final ColorMap colorMap, final boolean paintGaps,
470 final Integer digits) {
471 final DecimalFormat decFormat = digits != null ? new DecimalFormat(
472 SwingUtil.getNumberFormatPattern(digits)) : null;
473 final RasterLegendData rld = new RasterLegendData(paintGaps);
474 for (final ColorMapEntry cme : colorMap.getColorMapEntries()) {
475 final double value = StylingUtil.getQuantityFromColorMapEntry(cme);
476 String label = cme.getLabel();
477 // if no label is set (e.g. quantitative style),
478 // use the value as label
479 if (label == null || label.equals(""))
480 if (digits == null)
481 label = String.valueOf(value);
482 else
483 label = decFormat.format(LangUtil.round(value, digits));
484 rld.put(value, new Translation(" " + label));
485 }
486 return rld;
487 }
488
489 /**
490 * Creates {@link RasterLegendData} from the {@link ColorMap} of a style.
491 *
492 * @param style
493 * a raster style (must contain a {@link RasterSymbolizer})
494 * @param paintGaps
495 * indicated whether gaps are painted between the legend items
496 * @param digits
497 * number of digits the grid value classes (and legend) are
498 * rounded to (null means no round; >= 0 means digits after
499 * comma; < 0 means digits before comma)
500 */
501 public static RasterLegendData generateRasterLegendData(final Style style,
502 final boolean paintGaps, final Integer digits) {
503 final ColorMap colorMap = StylingUtil.getColorMapFromStyle(style);
504 if (colorMap == null)
505 throw new IllegalArgumentException(
506 "Color map can not be determined from style!");
507 return generateRasterLegendData(colorMap, paintGaps, digits);
508 }
509
510 /**
511 * Saves a {@link RasterLegendData} to an URL.
512 *
513 * @param rld
514 * raster legend data
515 * @param documentUrl
516 * {@link URL} to store the XML
517 */
518 public static void saveRasterLegendData(final RasterLegendData rld,
519 final URL documentUrl) throws Exception {
520 // Create XML-Document
521 final FileWriter out = new FileWriter(new File(documentUrl.toURI()));
522 XML_OUTPUTTER.output(createRasterLegendDataElement(rld), out);
523 out.flush();
524 out.close();
525 }
526
527 /**
528 * Parses a {@link Translation} object from an JDOM-{@link Element}. This
529 * method works like {@link AMLImport#parseTranslation(org.w3c.dom.Node},
530 * but for JDOM.
531 *
532 * @param element
533 * {@link Element} to parse
534 */
535 public final static Translation parseTranslation(final Element element) {
536 final Translation trans = new Translation();
537
538 if (element == null)
539 return trans;
540
541 for (final Element translationElement : (List<Element>) element
542 .getChildren()) {
543 final String name = translationElement.getName();
544 if (name == null)
545 continue;
546
547 // lang attribute
548 String lang = translationElement.getAttributeValue("lang");
549 // set the default, if no language code is set
550 if (lang == null)
551 lang = Translation.DEFAULT_KEY;
552
553 final String translationText = translationElement.getValue();
554 if (translationText == null)
555 trans.put(lang, "");
556 else
557 trans.put(lang, translationText);
558 }
559
560 // if no <translation> is given, the value of the node should
561 // be used as a default translation
562 if (trans.size() == 0)
563 trans.put(Translation.DEFAULT_KEY, element.getValue());
564 // trans = new Translation(
565 // ((List<Element>)element.getChildren()).get(0).getValue() );
566
567 return trans;
568 }
569
570 /**
571 * Creates an JDOM {@link Element} for the given {@link Translation}.
572 *
573 * @param tagname
574 * Name of the Element
575 * @param translation
576 * Translation to store in the Element
577 */
578 public final static Element createTranslationElement(final String tagname,
579 final Translation translation) {
580 final Element element = new Element(tagname, AMLURI);
581 if (translation == null)
582 throw new UnsupportedOperationException(
583 "Translation element can not be created from null!");
584
585 // If only a default translation is set, the <translation
586 // lang="..">..</tranlation>
587 // part is not used
588 if (translation.keySet().size() == 1
589 && translation.get(Translation.DEFAULT_KEY) != null) {
590 element.addContent(translation.get(Translation.DEFAULT_KEY));
591 return element;
592 }
593
594 // add a <translation lang="..">..</tranlation> part to the element for
595 // all languages
596 for (final String lang : translation.keySet()) {
597 final Element translationElement = new Element(
598 ELEM_NAME_TRANSLATION, AMLURI);
599 translationElement.setAttribute("lang", lang);
600 String translationString = translation.get(lang);
601 if (translationString == null)
602 translationString = "";
603 translationElement.addContent(translationString);
604 element.addContent(translationElement);
605 }
606
607 return element;
608 }
609
610 /**
611 * Sets a style to {@link StyledLayerInterface}.
612 *
613 * @param styledObject
614 * a styled object
615 * @param style
616 * a Style
617 */
618 public static void setStyledLayerStyle(
619 final StyledLayerInterface styledObject,
620 final StyledLayerStyle<?> style) {
621 // set SLD style
622 styledObject.setStyle(style.getGeoObjectStyle());
623 // set meta data
624 if (styledObject instanceof StyledGridCoverageInterface
625 && (style.getMetaData() instanceof RasterLegendData || style
626 .getMetaData() == null)) {
627 final RasterLegendData sourceRld = (RasterLegendData) style
628 .getMetaData();
629 final RasterLegendData destRld = ((StyledGridCoverageInterface) styledObject)
630 .getLegendMetaData();
631 if (destRld != null && sourceRld != null) {
632 destRld.setPaintGaps(sourceRld.isPaintGaps());
633 destRld.clear();
634 destRld.putAll(sourceRld);
635 }
636 return;
637 }
638 if (styledObject instanceof StyledFeatureCollectionInterface
639 && (style.getMetaData() instanceof Map || style.getMetaData() == null)) {
640 final AttributeMetadataMap sourceAmd = (AttributeMetadataMap) style
641 .getMetaData();
642 final AttributeMetadataMap destAmd = ((StyledFeatureCollectionInterface) styledObject)
643 .getAttributeMetaDataMap();
644 if (destAmd != null && sourceAmd != null) {
645 destAmd.clear();
646 destAmd.putAll(sourceAmd);
647 }
648 return;
649 }
650
651 throw new UnsupportedOperationException(
652 "Style is not compatible to object: "
653 + (style.getMetaData() == null ? null : style
654 .getMetaData().getClass().getSimpleName())
655 + " <-> "
656 + (styledObject == null ? null : styledObject
657 .getClass().getSimpleName()));
658 }
659
660 /**
661 * Returns the style a {@link StyledLayerInterface} as a
662 * {@link StyledLayerStyle}.
663 *
664 * @param styledObject
665 * a styled object
666 * @return {@code StyledLayerStyle<RasterLegendData>} for
667 * {@link StyledGridCoverageInterface} or {@code
668 * StyledLayerStyle<Map<Integer,AttributeMetaData>>} for
669 * {@link StyledFeatureCollectionInterface}
670 */
671 public static StyledLayerStyle<?> getStyledLayerStyle(
672 final StyledLayerInterface styledObject) {
673 if (styledObject instanceof StyledGridCoverageInterface)
674 return getStyledLayerStyle((StyledGridCoverageInterface) styledObject);
675 if (styledObject instanceof StyledFeatureCollectionInterface)
676 return getStyledLayerStyle((StyledFeatureCollectionInterface) styledObject);
677 throw new UnsupportedOperationException(
678 "Unknown type of StyledLayerInterface: "
679 + (styledObject == null ? null : styledObject
680 .getClass().getSimpleName()));
681 }
682
683 /**
684 * Returns the style and raster meta data of a
685 * {@link StyledGridCoverageInterface} as a {@link StyledLayerStyle}.
686 *
687 * @param styledGC
688 * a styled grid coverage
689 */
690 public static StyledLayerStyle<RasterLegendData> getStyledLayerStyle(
691 final StyledGridCoverageInterface styledGC) {
692 return new StyledLayerStyle<RasterLegendData>(styledGC.getStyle(),
693 styledGC.getLegendMetaData());
694 }
695
696 /**
697 * Returns the style and attribute meta data of a
698 * {@link StyledFeatureCollectionInterface} as a {@link StyledLayerStyle}.
699 *
700 * @param styledFC
701 * a styled feature collection
702 */
703 public static StyledLayerStyle<AttributeMetadataMap> getStyledLayerStyle(
704 final StyledFeatureCollectionInterface styledFC) {
705 return new StyledLayerStyle<AttributeMetadataMap>(styledFC.getStyle(),
706 styledFC.getAttributeMetaDataMap());
707 }
708
709 /**
710 * Loads a {@linkplain Style SLD-Style} and {@linkplain RasterLegendData
711 * Raster-LegendData} for a given geo-object (raster) source. The SLD file
712 * must be present. A missing raster legend-data file is tolerated.
713 *
714 * @param geoObjectURL
715 * URL of the (already read) raster object
716 * @param sldExt
717 * file extention for the SLD file
718 * @param rldExt
719 * file extention for the raster legend-data file
720 * @return {@code null} in case of any error
721 */
722 public static StyledLayerStyle<RasterLegendData> loadStyledRasterStyle(
723 final URL geoObjectURL, final String sldExt, final String rldExt) {
724 RasterLegendData metaData = null;
725 Style sldStyle = null;
726 try {
727 final Style[] styles = StylingUtil.loadSLD(IOUtil.changeUrlExt(
728 geoObjectURL, sldExt));
729 // SLD must be present
730 if (styles == null || styles.length == 0)
731 return null;
732 sldStyle = styles[0];
733 } catch (final Exception err) {
734 // SLD must be present
735 LangUtil.logDebugError(LOGGER, err);
736 return null;
737 }
738
739 try {
740 metaData = StyledLayerUtil.loadRasterLegendData(IOUtil
741 .changeUrlExt(geoObjectURL, rldExt));
742 } catch (final FileNotFoundException err) {
743 // ignore missing raster legend data
744 } catch (final Exception err) {
745 // any other error during legend data creation leads to error
746 LangUtil.logDebugError(LOGGER, err);
747 return null;
748 }
749 return new StyledLayerStyle<RasterLegendData>(sldStyle, metaData);
750 }
751
752 /**
753 * Loads a {@linkplain Style SLD-Style} from a {@code .sld} file and
754 * {@linkplain RasterLegendData Raster-LegendData} from a {@code .rld} file
755 * for a given geo-object (raster) source. The SLD file must be present. A
756 * missing raster legend-data file is tolerated.
757 *
758 * @param geoObjectURL
759 * URL of the (already read) raster object
760 * @param sldExt
761 * file extention for the SLD file
762 * @param rldExt
763 * file extention for the raster legend-data file
764 * @return {@code null} in case of any error
765 */
766 public static StyledLayerStyle<RasterLegendData> loadStyledRasterStyle(
767 final URL geoObjectURL) {
768 return loadStyledRasterStyle(geoObjectURL, "sld", "rld");
769 }
770
771 /**
772 * Loads a {@linkplain Style SLD-Style} and a {@linkplain AttributeMetadata
773 * AttributeMetaData-Map} for a given geo-object (feature) source. The SLD
774 * file must be present. A missing attribute meta-data file is tolerated.
775 *
776 * @param geoObjectURL
777 * URL of the (already read) feature object
778 * @param sldExt
779 * file extention for the SLD file
780 * @param rldExt
781 * file extention for the raster legend-data file
782 * @return {@code null} in case of any error
783 */
784 public static StyledLayerStyle<AttributeMetadataMap> loadStyledFeatureStyle(
785 final URL geoObjectURL, final String sldExt, final String rldExt) {
786 AttributeMetadataMap metaData = null;
787 Style sldStyle = null;
788 try {
789 final Style[] styles = StylingUtil.loadSLD(IOUtil.changeUrlExt(
790 geoObjectURL, sldExt));
791 // SLD must be present
792 if (styles == null || styles.length == 0)
793 return null;
794 sldStyle = styles[0];
795 } catch (final Exception err) {
796 // SLD must be present
797 LangUtil.logDebugError(LOGGER, err);
798 return null;
799 }
800
801 try {
802 metaData = StyledLayerUtil.loadAttributeMetaDataMap(IOUtil
803 .changeUrlExt(geoObjectURL, rldExt));
804 } catch (final FileNotFoundException err) {
805 // ignore missing attribute meta data
806 } catch (final Exception err) {
807 // any other error during meta data creation leads to error
808 LangUtil.logDebugError(LOGGER, err);
809 return null;
810 }
811
812 return new StyledLayerStyle<AttributeMetadataMap>(sldStyle, metaData);
813 }
814
815 /**
816 * Loads a {@linkplain Style SLD-Style} from a {@code .sld} file and
817 * {@linkplain AttributeMetadata AttributeMetaData-Map} from a {@code .amd}
818 * file for a given geo-object (feature) source. The SLD file must be
819 * present. A missing attribute meta-data file is tolerated.
820 *
821 * @param geoObjectURL
822 * URL of the (already read) feature object
823 * @param sldExt
824 * file extention for the SLD file
825 * @param rldExt
826 * file extention for the raster legend-data file
827 * @return {@code null} in case of any error
828 */
829 public static StyledLayerStyle<AttributeMetadataMap> loadStyledFeatureStyle(
830 final URL geoObjectURL) {
831 return loadStyledFeatureStyle(geoObjectURL, "sld", "amd");
832 }
833
834 /**
835 * Stores a {@linkplain Style SLD-Style} and {@linkplain RasterLegendData
836 * Raster-LegendData} for a given geo-object (raster) source.
837 *
838 * @param style
839 * style to save
840 * @param geoObjectURL
841 * URL of the raster object
842 * @param sldExt
843 * file extention for the SLD file
844 * @param mdExt
845 * file extention for the meta-data file
846 */
847 public static <T> void saveStyledLayerStyle(
848 final StyledLayerStyle<T> style, final URL geoObjectURL,
849 final String sldExt, final String mdExt) throws Exception {
850 // Store the SLD
851 final Style sldStyle = style.getGeoObjectStyle();
852 if (sldStyle != null) {
853 StylingUtil.saveStyleToSLD(sldStyle, IOUtil.changeFileExt(new File(
854 geoObjectURL.toURI()), sldExt));
855 }
856
857 // Store the meta data
858 final T metaData = style.getMetaData();
859 if (metaData != null) {
860 if (metaData instanceof RasterLegendData) {
861 saveRasterLegendData((RasterLegendData) metaData, IOUtil
862 .changeUrlExt(geoObjectURL, mdExt));
863 // } else if ( metaData instanceof
864 // Map<Integer,AttributeMetaData> ) { // LEIDER NICHT
865 // KOMPILIERBAR!!
866 } else if (metaData instanceof Map) {
867 saveAttributeMetaDataMap((AttributeMetadataMap) metaData,
868 IOUtil.changeUrlExt(geoObjectURL, mdExt));
869 } else
870 throw new UnsupportedOperationException(
871 "Export for meta data not yet supported: "
872 + metaData.getClass().getSimpleName());
873 }
874 }
875
876 /**
877 * Stores the {@linkplain Style SLD-Style} to a {@code .sld} file and the
878 * meta data ({@link RasterLegendData} or {@link AttributeMetadata}) to a
879 * {@code .rld} or {@code .amd} file. for a given geo-object source.
880 *
881 * @param style
882 * style to save
883 * @param geoObjectURL
884 * URL of the (already read) raster object
885 */
886 public static void saveStyledLayerStyle(final StyledLayerStyle<?> style,
887 final URL geoObjectURL) throws Exception {
888 if (style.getMetaData() instanceof RasterLegendData)
889 saveStyledLayerStyle(style, geoObjectURL, "sld", "rld");
890 else
891 saveStyledLayerStyle(style, geoObjectURL, "sld", "amd");
892 }
893
894 /**
895 * Creates a {@link JPanel} that shows a legend for a list of
896 * {@link FeatureTypeStyle}s and a targeted featureType
897 *
898 * @param featureType
899 * If this a legend for Point, Polygon or Line?
900 * @param list
901 * The Styles to presented in this legend
902 *
903 * @author <a href="mailto:[email protected]">Stefan Alfons
904 * Kr&uuml;ger</a>
905 */
906 public static JPanel createLegendPanel(Style style,
907 final SimpleFeatureType featureType, final int iconWidth,
908 final int iconHeight) {
909
910 final List<FeatureTypeStyle> list = style.featureTypeStyles();
911
912 final JPanel panel = new JPanel(new MigLayout("wrap 2", "[]:3:[]"));
913
914 if (style == null) {
915 // No Style => no legend
916 return panel;
917 }
918
919 for (final FeatureTypeStyle ftStyle : list) {
920
921 // One child-node for every rule
922 final List<Rule> rules = ftStyle.rules();
923 for (final Rule rule : rules) {
924
925 /**
926 * Let's not create a hbox for Rules that only contain
927 * TextSymbolizers
928 */
929 if (StylingUtil.getTextSymbolizers(rule.getSymbolizers())
930 .size() == rule.getSymbolizers().length)
931 continue;
932
933 final BufferedImage imageForRule = LegendIconFeatureRenderer
934 .getInstance().createImageForRule(rule, featureType,
935 new Dimension(iconWidth, iconHeight));
936
937 final ImageIcon legendIcon = new ImageIcon(imageForRule);
938
939 final JLabel iconLabel = new JLabel(legendIcon);
940 panel.add(iconLabel, "sgx1");
941 // hbox.setAlignmentX(0f);
942 // hbox.add(iconLabel);
943 // hbox.add(Box.createHorizontalStrut(3));
944
945 final Translation labelT = new Translation();
946 labelT.fromOneLine(rule.getDescription().getTitle());
947 final JLabel classTitleLabel = new JLabel(labelT.toString());
948
949 panel.add(classTitleLabel, "sgx2");
950 classTitleLabel.setLabelFor(iconLabel);
951 }
952 }
953
954 return panel;
955 }
956
957 /**
958 * Creates a {@link JComponent} that contains a legend for a given
959 * rasterLayer and a given {@link Style}.
960 *
961 * @param style
962 * if <code>null</code>, the default {@link Style} is extracetd
963 * from the {@link StyledRasterInterface}
964 */
965 public static JPanel createLegendPanel(
966 final StyledRasterInterface<?> styledRaster, Style style,
967 final int iconWidth, final int iconHeight) {
968
969 // If no style is given, we use the default style for this layer
970 if (style == null)
971 style = styledRaster.getStyle();
972
973 /**
974 * Determine whether a Style is responsible for the coloring
975 */
976 ColorModel colorModel = null;
977 if (!isStyleable(styledRaster)
978 || (isStyleable(styledRaster) && style == null)) {
979 colorModel = getColorModel(styledRaster);
980 }
981
982 final RasterLegendData rasterLegendData = styledRaster
983 .getLegendMetaData();
984 final List<Double> legendRasterValues = rasterLegendData
985 .getSortedKeys();
986 final Map<Double, GridCoverage2D> sampleRasters = rasterLegendData
987 .createSampleRasters();
988
989 final JPanel panel = new JPanel(new MigLayout("wrap 2"));
990
991 for (final Double rValue : legendRasterValues) {
992
993 // final Dimension ICON_SIZE = new Dimension(iconWidth,
994 // new JLabel().getFontMetrics(new JLabel().getFont())
995 // .getHeight() > 5 ? new JLabel().getFontMetrics(
996 // new JLabel().getFont()).getHeight() : iconHeight);
997
998 // ****************************************************************************
999 // Create the actual icon
1000 // ****************************************************************************
1001 final BufferedImage buffImage = new BufferedImage(iconWidth,
1002 iconHeight, BufferedImage.TYPE_INT_ARGB);
1003
1004 final Graphics2D graphics = buffImage.createGraphics();
1005
1006 if (colorModel != null) {
1007 // The colors come from the ColorModel!
1008
1009 try {
1010 Object inData = null;
1011 switch (colorModel.getTransferType()) {
1012 case DataBuffer.TYPE_BYTE:
1013 inData = new byte[] { rValue.byteValue() };
1014 break;
1015 case DataBuffer.TYPE_USHORT:
1016 inData = new short[] { rValue.shortValue() };
1017 break;
1018 case DataBuffer.TYPE_INT:
1019 inData = new int[] { rValue.intValue() };
1020 break;
1021 case DataBuffer.TYPE_SHORT:
1022 inData = new short[] { rValue.shortValue() };
1023 break;
1024 case DataBuffer.TYPE_FLOAT:
1025 inData = new float[] { rValue.floatValue() };
1026 break;
1027 case DataBuffer.TYPE_DOUBLE:
1028 inData = new double[] { rValue.doubleValue() };
1029 break;
1030 default:
1031 inData = rValue.intValue();
1032 }
1033 final Color color = new Color(colorModel.getRGB(inData));
1034 graphics.setBackground(color);
1035 graphics.setColor(color);
1036 graphics.fillRect(0, 0, iconWidth, iconHeight);
1037 } catch (final Exception e) {
1038 LOGGER.info(
1039 "Dann nehmen wir halt den GridCoverageRenderer", e);
1040 colorModel = null;
1041 }
1042 } else {
1043 // The colors come from the Style
1044
1045 /**
1046 * The coverage contains only one value of value rValue
1047 */
1048 final GridCoverage2D sampleCov = sampleRasters.get(rValue);
1049 GridCoverageRenderer renderer;
1050 try {
1051 renderer = new GridCoverageRenderer(sampleCov
1052 .getCoordinateReferenceSystem(), JTSUtil
1053 .createEnvelope(sampleCov.getEnvelope()),
1054 new Rectangle(iconWidth, iconHeight),
1055 (AffineTransform) null);
1056 } catch (final Exception e1) {
1057 throw new RuntimeException(
1058 "Creating a GridCoverageRenderer failed:", e1);
1059 }
1060
1061 /**
1062 * Iterate over all FeatureTypeStyles.
1063 */
1064 final List<RasterSymbolizer> rSymbols = StylingUtil
1065 .getRasterSymbolizers(style);
1066
1067 for (final RasterSymbolizer symbolizer : rSymbols) {
1068 try {
1069 renderer.paint(graphics, sampleCov, symbolizer);
1070 } catch (final Exception ee) {
1071 LOGGER.error("Unable to paint " + symbolizer
1072 + " into the legend image", ee);
1073 }
1074 }
1075 }
1076
1077 final JLabel iconLabel = new JLabel(new ImageIcon(buffImage));
1078 // hbox.setAlignmentX(0f);
1079 panel.add(iconLabel, "sgx1");
1080 // hbox.add(Box.createHorizontalStrut(3));
1081
1082 final Translation labelT = rasterLegendData.get(rValue);
1083 final JLabel classTitleLabel = new JLabel(labelT.toString());
1084 panel.add(classTitleLabel, "sgx2"
1085 + (rasterLegendData.getPaintGaps() ? ", gapy 0 3" : ""));
1086 classTitleLabel.setLabelFor(iconLabel);
1087
1088 // box.add(hbox);
1089
1090 if (rasterLegendData.getPaintGaps()) {
1091 iconLabel
1092 .setBorder(BorderFactory.createLineBorder(Color.black));
1093 }
1094
1095 }
1096
1097 return panel;
1098 }
1099
1100 /**
1101 * Extracts the {@link ColorModel} of any {@link StyledRasterInterface}. May
1102 * return <code>null</code> if the geoobject can not be accessed.
1103 */
1104 @SuppressWarnings("unchecked")
1105 public static ColorModel getColorModel(
1106 final StyledRasterInterface<?> styledGrid) {
1107 ColorModel colorModel = null;
1108 try {
1109 final Object geoObject = styledGrid.getGeoObject();
1110 if (geoObject instanceof GridCoverage2D) {
1111 final GridCoverage2D cov = (GridCoverage2D) geoObject;
1112 colorModel = cov.getRenderedImage().getColorModel();
1113 } else if (styledGrid instanceof StyledRasterPyramidInterface) {
1114
1115 final Parameter readGG = new Parameter(
1116 AbstractGridFormat.READ_GRIDGEOMETRY2D);
1117
1118 final ReferencedEnvelope mapExtend = new org.geotools.geometry.jts.ReferencedEnvelope(
1119 styledGrid.getEnvelope(), styledGrid.getCrs());
1120
1121 readGG.setValue(new GridGeometry2D(new GeneralGridEnvelope(
1122 new Rectangle(0, 0, 1, 1)), mapExtend));
1123
1124 final FeatureCollection<SimpleFeatureType, SimpleFeature> rFc = (FeatureCollection<SimpleFeatureType, SimpleFeature>) geoObject;
1125
1126 final AbstractGridCoverage2DReader aReader = (AbstractGridCoverage2DReader) FeatureUtil
1127 .getWrappedGeoObject(rFc);
1128 final GridCoverage2D cov = (GridCoverage2D) aReader
1129 .read(new GeneralParameterValue[] { readGG });
1130 colorModel = cov.getRenderedImage().getColorModel();
1131 }
1132 } catch (final Exception e) {
1133 LOGGER.error("Error reading the colormodel from " + styledGrid, e);
1134 return null;
1135 }
1136 return colorModel;
1137 }
1138
1139 /**
1140 * @return <code>true</code> if a {@link RasterSymbolizer} can be applied
1141 * and will have an effect. Some rasters (e.g. GeoTIFF) can come
1142 * with their own {@link ColorModel} and will ignore any
1143 * {@link RasterSymbolizer} = SLD.
1144 */
1145 public static boolean isStyleable(
1146 final StyledRasterInterface<?> styledRaster) {
1147 final ColorModel colorModel = getColorModel(styledRaster);
1148
1149 // LOGGER.info("The colormodel of " + styledRaster.getTitle() + " is "
1150 // + colorModel != null ? colorModel.getClass().getSimpleName() : "NULL");
1151
1152 if (colorModel == null)
1153 return true;
1154 if (colorModel instanceof ComponentColorModel)
1155 return true;
1156 return false;
1157 }
1158
1159 /**
1160 * Set the given Style as the Style of the {@link MapLayer}, unless the
1161 * styles are the same (not comparing selection stuff). If the
1162 * {@link MapLayer}s {@link Style} is changed, the selection FTS is kept.<br/>
1163 * Remember {@link MapLayer#setStyle(Style)} triggers an event leading to a
1164 * repaint, so only use it when needed.
1165 *
1166 * @return <code>true</code> if the {@link MapLayer}'s {@link Style} has been changed.
1167 */
1168 public static boolean updateMapLayerStyleIfChangedAndKeepSelection(MapLayer mapLayer,
1169 Style style2) {
1170
1171 Style mapLayerStyleCleaned = StylingUtil
1172 .removeSelectionFeatureTypeStyle(mapLayer.getStyle());
1173
1174 Style newStyleCleaned = StylingUtil.removeSelectionFeatureTypeStyle(style2);
1175
1176 if (StylingUtil.isStyleDifferent(mapLayerStyleCleaned,
1177 newStyleCleaned)) {
1178
1179 // They are different when compared without SELECTION FTS!
1180
1181 // Now let's copy any SELECTION FTS to the now style
1182 FeatureTypeStyle selectionFeatureTypeStyle = StylingUtil.getSelectionFeatureTypeStyle( mapLayer.getStyle() );
1183 if (selectionFeatureTypeStyle != null) {
1184 newStyleCleaned.featureTypeStyles().add(selectionFeatureTypeStyle);
1185 // newStyleCleaned is not so clean anymore... We just alled a selcetion FTS
1186 }
1187
1188 mapLayer.setStyle(newStyleCleaned);
1189
1190 return true;
1191
1192 } else {
1193 return false;
1194 }
1195 }
1196
1197 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26