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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26