0001: /* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
0002: * This code is licensed under the GPL 2.0 license, availible at the root
0003: * application directory.
0004: */
0005: package org.vfny.geoserver.wms.responses.map.kml;
0006:
0007: import java.awt.Color;
0008: import java.awt.Paint;
0009: import java.io.IOException;
0010: import java.io.OutputStream;
0011: import java.io.OutputStreamWriter;
0012: import java.io.StringWriter;
0013: import java.nio.charset.Charset;
0014: import java.text.DecimalFormat;
0015: import java.text.DecimalFormatSymbols;
0016: import java.text.NumberFormat;
0017: import java.util.ArrayList;
0018: import java.util.Iterator;
0019: import java.util.List;
0020: import java.util.Locale;
0021: import java.util.NoSuchElementException;
0022: import java.util.logging.Logger;
0023:
0024: import javax.media.jai.util.Range;
0025: import javax.servlet.http.HttpServletRequest;
0026: import javax.xml.transform.TransformerException;
0027:
0028: import org.geoserver.template.FeatureWrapper;
0029: import org.geoserver.template.GeoServerTemplateLoader;
0030: import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
0031: import org.geotools.data.DataSourceException;
0032: import org.geotools.feature.Feature;
0033: import org.geotools.feature.FeatureCollection;
0034: import org.geotools.feature.FeatureIterator;
0035: import org.geotools.feature.FeatureType;
0036: import org.geotools.feature.GeometryAttributeType;
0037: import org.geotools.feature.IllegalAttributeException;
0038: import org.geotools.geometry.jts.JTS;
0039: import org.geotools.gml.producer.GeometryTransformer;
0040: import org.geotools.map.MapLayer;
0041: import org.geotools.referencing.CRS;
0042: import org.geotools.renderer.style.LineStyle2D;
0043: import org.geotools.renderer.style.MarkStyle2D;
0044: import org.geotools.renderer.style.PolygonStyle2D;
0045: import org.geotools.renderer.style.SLDStyleFactory;
0046: import org.geotools.renderer.style.Style2D;
0047: import org.geotools.renderer.style.TextStyle2D;
0048: import org.geotools.styling.FeatureTypeStyle;
0049: import org.geotools.styling.LineSymbolizer;
0050: import org.geotools.styling.Mark;
0051: import org.geotools.styling.PointSymbolizer;
0052: import org.geotools.styling.PolygonSymbolizer;
0053: import org.geotools.styling.RasterSymbolizer;
0054: import org.geotools.styling.Rule;
0055: import org.geotools.styling.Style;
0056: import org.geotools.styling.Symbolizer;
0057: import org.geotools.styling.TextSymbolizer;
0058: import org.geotools.util.NumberRange;
0059: import org.opengis.filter.Filter;
0060: import org.opengis.filter.expression.Expression;
0061: import org.opengis.geometry.MismatchedDimensionException;
0062: import org.opengis.referencing.FactoryException;
0063: import org.opengis.referencing.crs.CoordinateReferenceSystem;
0064: import org.opengis.referencing.operation.MathTransform;
0065: import org.opengis.referencing.operation.TransformException;
0066: import org.vfny.geoserver.global.GeoServer;
0067: import org.vfny.geoserver.wms.WMSMapContext;
0068:
0069: import com.vividsolutions.jts.geom.Coordinate;
0070: import com.vividsolutions.jts.geom.Geometry;
0071: import com.vividsolutions.jts.geom.GeometryCollection;
0072: import com.vividsolutions.jts.geom.MultiLineString;
0073: import com.vividsolutions.jts.geom.MultiPoint;
0074: import com.vividsolutions.jts.geom.MultiPolygon;
0075:
0076: import freemarker.template.Configuration;
0077: import freemarker.template.Template;
0078: import freemarker.template.TemplateException;
0079:
0080: /**
0081: * Writer for KML/KMZ (Keyhole Markup Language) files. Normaly controled by an
0082: * EncodeKML instance, this class handles the styling information and ensures
0083: * that the geometries produced match the pseudo GML expected by GE.
0084: *
0085: * @REVISIT: Once this is fully working, revisit as an extention to
0086: * TransformerBase
0087: * @author James Macgill
0088: * @author $Author: Alessio Fabiani (alessio.fabiani@gmail.com) $
0089: * @author $Author: Simone Giannecchini (simboss1@gmail.com) $
0090: * @author Brent Owens
0091: *
0092: * @deprecated use {@link KMLTransformer}.
0093: */
0094: public class KMLWriter extends OutputStreamWriter {
0095: private static final Logger LOGGER = org.geotools.util.logging.Logging
0096: .getLogger(KMLWriter.class.getPackage().getName());
0097:
0098: /**
0099: * a number formatter set up to write KML legible numbers
0100: */
0101: private static DecimalFormat formatter;
0102:
0103: /**
0104: * The template configuration
0105: */
0106: private static Configuration templateConfig;
0107:
0108: /**
0109: * Resolves the FeatureTypeStyle info per feature into a Style2D object.
0110: */
0111: private SLDStyleFactory styleFactory = new SLDStyleFactory();
0112:
0113: // TODO: calcuate a real value based on image size to bbox ratio, as image
0114: // size has no meanining for KML yet this is a fudge.
0115: private double scaleDenominator = 1;
0116:
0117: /** Tolerance used to compare doubles for equality */
0118: private static final double TOLERANCE = 1e-6;
0119:
0120: /**
0121: * The CRS of the data we are querying. It is a bit of a hack because
0122: * sometimes when we grab the CRS from the feature itself, we get null. This
0123: * variable is paired with setSourceCrs() so EncodeKML can can use the
0124: * feature type's schema to set the CRS.
0125: */
0126: private CoordinateReferenceSystem sourceCrs;
0127:
0128: /**
0129: * Handles the outputing of geometries as GML
0130: */
0131: private GeometryTransformer transformer;
0132:
0133: static {
0134: Locale locale = new Locale("en", "US");
0135:
0136: DecimalFormatSymbols decimalSymbols = new DecimalFormatSymbols(
0137: locale);
0138: decimalSymbols.setDecimalSeparator('.');
0139: formatter = new DecimalFormat();
0140: formatter.setDecimalFormatSymbols(decimalSymbols);
0141:
0142: // do not group
0143: formatter.setGroupingSize(0);
0144:
0145: // do not show decimal separator if it is not needed
0146: formatter.setDecimalSeparatorAlwaysShown(false);
0147: formatter.setDecimalFormatSymbols(null);
0148:
0149: // set default number of fraction digits
0150: formatter.setMaximumFractionDigits(5);
0151:
0152: // minimun fraction digits to 0 so they get not rendered if not needed
0153: formatter.setMinimumFractionDigits(0);
0154:
0155: // initialize the template engine, this is static to maintain a cache
0156: // over instantiations of kml writer
0157: templateConfig = new Configuration();
0158: templateConfig.setObjectWrapper(new FeatureWrapper());
0159: }
0160:
0161: /** Holds the map layer set, styling info and area of interest bounds */
0162: private WMSMapContext mapContext;
0163:
0164: /**
0165: * Creates a new KMLWriter object.
0166: *
0167: * @param out
0168: * OutputStream to write the KML into
0169: * @param config
0170: * WMSMapContext describing the map to be generated.
0171: */
0172: public KMLWriter(OutputStream out, WMSMapContext mapContext) {
0173: super (out, Charset.forName("UTF-8"));
0174: this .mapContext = mapContext;
0175:
0176: transformer = new GeometryTransformer();
0177: // transformer.setUseDummyZ(true);
0178: transformer.setOmitXMLDeclaration(true);
0179: transformer.setNamespaceDeclarationEnabled(true);
0180:
0181: GeoServer config = mapContext.getRequest().getGeoServer();
0182: transformer.setNumDecimals(config.getNumDecimals());
0183: }
0184:
0185: /**
0186: * Sets the maximum number of digits allowed in the fraction portion of a
0187: * number.
0188: *
0189: * @param numDigits
0190: * @see NumberFormat#setMaximumFractionDigits
0191: */
0192: public void setMaximunFractionDigits(int numDigits) {
0193: formatter.setMaximumFractionDigits(numDigits);
0194: }
0195:
0196: /**
0197: * Gets the maximum number of digits allowed in the fraction portion of a
0198: * number.
0199: *
0200: * @return int numDigits
0201: * @see NumberFormat#getMaximumFractionDigits
0202: */
0203: public int getMaximunFractionDigits() {
0204: return formatter.getMaximumFractionDigits();
0205: }
0206:
0207: /**
0208: * Sets the minimum number of digits allowed in the fraction portion of a
0209: * number.
0210: *
0211: * @param numDigits
0212: * @see NumberFormat#setMinimumFractionDigits
0213: */
0214: public void setMinimunFractionDigits(int numDigits) {
0215: formatter.setMinimumFractionDigits(numDigits);
0216: }
0217:
0218: /*
0219: * Sets the minimum number of digits allowed in the fraction portion of a
0220: * number.
0221: *
0222: * @param numDigits
0223: *
0224: * @see NumberFormat#getMinimumFractionDigits
0225: */
0226: public int getMinimunFractionDigits() {
0227: return formatter.getMinimumFractionDigits();
0228: }
0229:
0230: public void setRequestedScale(double scale) {
0231: scaleDenominator = scale;
0232: }
0233:
0234: public void setSourceCrs(CoordinateReferenceSystem crs) {
0235: sourceCrs = crs;
0236: }
0237:
0238: /**
0239: * Formated version of standard write double
0240: *
0241: * @param d
0242: * The double to format and write out.
0243: *
0244: * @throws IOException
0245: */
0246: public void write(double d) throws IOException {
0247: write(formatter.format(d));
0248: }
0249:
0250: /**
0251: * Convinience method to add a newline char to the output
0252: *
0253: * @throws IOException
0254: */
0255: public void newline() throws IOException {
0256: super .write('\n');
0257: }
0258:
0259: public void writeFeaturesAsRaster(final FeatureCollection features,
0260: final MapLayer layer, final int order) throws IOException,
0261: AbortedException {
0262: Style style = layer.getStyle();
0263:
0264: try {
0265: FeatureType featureType = features.getSchema();
0266:
0267: setUpWriterHandler(featureType);
0268:
0269: FeatureTypeStyle[] fts = style.getFeatureTypeStyles();
0270: processStylersRaster(features, fts, layer, order);
0271: LOGGER.fine("encoded "
0272: + featureType.getTypeName().toString());
0273: } catch (NoSuchElementException ex) {
0274: throw new DataSourceException(ex.getMessage(), ex);
0275: } catch (IllegalAttributeException ex) {
0276: throw new DataSourceException(ex.getMessage(), ex);
0277: }
0278: }
0279:
0280: public void writeFeaturesAsVectors(
0281: final FeatureCollection features, final MapLayer layer)
0282: throws IOException, AbortedException {
0283: Style style = layer.getStyle();
0284:
0285: try {
0286: FeatureType featureType = features.getSchema();
0287:
0288: setUpWriterHandler(featureType);
0289:
0290: FeatureTypeStyle[] fts = style.getFeatureTypeStyles();
0291: processStylersVector(features, fts, layer);
0292: LOGGER.fine("encoded "
0293: + featureType.getTypeName().toString());
0294: } catch (NoSuchElementException ex) {
0295: throw new DataSourceException(ex.getMessage(), ex);
0296: } catch (IllegalAttributeException ex) {
0297: throw new DataSourceException(ex.getMessage(), ex);
0298: }
0299: }
0300:
0301: public void writeCoverages(final FeatureCollection features,
0302: final MapLayer layer) throws IOException, AbortedException {
0303: Style style = layer.getStyle();
0304:
0305: try {
0306: FeatureType featureType = features.getSchema();
0307:
0308: setUpWriterHandler(featureType);
0309:
0310: FeatureTypeStyle[] fts = style.getFeatureTypeStyles();
0311: processStylersCoverage(features, fts, layer);
0312: LOGGER.fine("encoded "
0313: + featureType.getTypeName().toString());
0314: } catch (NoSuchElementException ex) {
0315: throw new DataSourceException(ex.getMessage(), ex);
0316: } catch (IllegalAttributeException ex) {
0317: throw new DataSourceException(ex.getMessage(), ex);
0318: }
0319: }
0320:
0321: /**
0322: * Write all the features in a collection which pass the rules in the
0323: * provided Style object.
0324: *
0325: * @TODO: support Name and Description information
0326: */
0327:
0328: /*
0329: * public void writeFeatures(final FeatureCollection features, final
0330: * MapLayer layer, final int order, final boolean kmz, final boolean
0331: * vectorResult) throws IOException, AbortedException { Style style =
0332: * layer.getStyle();
0333: *
0334: * try { FeatureType featureType = features.getSchema();
0335: *
0336: * setUpWriterHandler(featureType); FeatureTypeStyle[] fts =
0337: * style.getFeatureTypeStyles(); if (!kmz) processStylers(features, fts,
0338: * layer, order); else processStylersKMZ(features, fts, layer, order,
0339: * vectorResult);
0340: *
0341: *
0342: * LOGGER.fine(new StringBuffer("encoded
0343: * ").append(featureType.getTypeName()).toString()); } catch
0344: * (NoSuchElementException ex) { throw new
0345: * DataSourceException(ex.getMessage(), ex); } catch
0346: * (IllegalAttributeException ex) { throw new
0347: * DataSourceException(ex.getMessage(), ex); } }
0348: */
0349:
0350: /**
0351: * Start a new KML folder. From the spec 2.0: A top-level, optional tag used
0352: * to structure hierarchical arrangement of other folders, placemarks,
0353: * ground overlays, and screen overlays. Use this tag to structure and
0354: * organize your information in the Google Earth client.
0355: *
0356: * In this context we should be using a Folder per map layer.
0357: *
0358: * @param name
0359: * A String to label this folder with, if null the name tag will
0360: * be ommited
0361: * @param description
0362: * Supplies descriptive information. This description appears in
0363: * the Places window when the user clicks on the folder or ground
0364: * overlay, and in a pop-up window when the user clicks on either
0365: * the Placemark name in the Places window, or the placemark
0366: * icon. The description element supports plain text as well as
0367: * HTML formatting. A valid URL string for the World Wide Web is
0368: * automatically converted to a hyperlink to that URL (e.g.
0369: * http://www.google.com). if null the description tag will be
0370: * ommited
0371: */
0372: public void startFolder(String name, String description)
0373: throws IOException {
0374: write("<Folder>");
0375:
0376: if (name != null) {
0377: write("<name>" + name + "</name>");
0378: }
0379:
0380: if (description != null) {
0381: write("<description>" + description + "</description>");
0382: }
0383: }
0384:
0385: public void startDocument(String name, String description)
0386: throws IOException {
0387: write("<Document>");
0388:
0389: if (name != null) {
0390: write("<name>" + name + "</name>");
0391: }
0392:
0393: if (description != null) {
0394: write("<description>" + description + "</description>");
0395: }
0396: }
0397:
0398: public void endFolder() throws IOException {
0399: write("</Folder>");
0400: }
0401:
0402: public void endDocument() throws IOException {
0403: write("</Document>");
0404: }
0405:
0406: /**
0407: * Gather any information needed to write the KML document.
0408: *
0409: * @TODO: support writing of 'Schema' tags based on featureType
0410: */
0411: private void setUpWriterHandler(FeatureType featureType)
0412: throws IOException {
0413: String typeName = featureType.getTypeName();
0414:
0415: /*
0416: * REVISIT: To use attributes properly we need to be using the 'schema'
0417: * part of KML to contain custom data..
0418: */
0419: List atts = new ArrayList(0); // config.getAttributes(typeName);
0420: }
0421:
0422: /**
0423: * Write out the geometry. Contains workaround for the fact that KML2.0 does
0424: * not support multipart geometries in the same way that GML does.
0425: *
0426: * @param geom
0427: * The Geometry to be encoded, multi part geometries will be
0428: * written as a sequence.
0429: * @param trans
0430: * A GeometryTransformer to produce the gml output, its output is
0431: * post processed to remove gml namespace prefixes.
0432: */
0433: protected void writeGeometry(Geometry geom,
0434: GeometryTransformer trans) throws IOException,
0435: TransformerException {
0436: if (isMultiPart(geom)) {
0437: for (int i = 0; i < geom.getNumGeometries(); i++) {
0438: writeGeometry(geom.getGeometryN(i), trans);
0439: }
0440: } else {
0441: // remove gml prefixing as KML does not accept them
0442: StringWriter tempWriter = new StringWriter();
0443: // trans.setNumDecimals(config.getNumDecimals());
0444: trans.transform(geom, tempWriter);
0445:
0446: String tempBuffer = tempWriter.toString();
0447: // @REVISIT: should check which prefix is being used, this will only
0448: // work for the default (99.9%) of cases.
0449: write(tempBuffer.replaceAll("gml:", ""));
0450: }
0451: }
0452:
0453: protected void writeLookAt(Geometry geom, GeometryTransformer trans)
0454: throws IOException, TransformerException {
0455: final Coordinate[] coordinates = getCentroid(geom)
0456: .getCoordinates();
0457: write("<LookAt>");
0458: write("<longitude>" + coordinates[0].x + "</longitude>");
0459: write("<latitude>" + coordinates[0].y + "</latitude>");
0460: write("<range>700</range>");
0461: write("<tilt>10.0</tilt>");
0462: write("<heading>10.0</heading>");
0463: write("</LookAt>");
0464: }
0465:
0466: protected void writePlaceMarkPoint(Geometry geom,
0467: GeometryTransformer trans) throws IOException,
0468: TransformerException {
0469: final Coordinate[] coordinates = getCentroid(geom)
0470: .getCoordinates();
0471: write("<Point><coordinates>" + coordinates[0].x + ","
0472: + coordinates[0].y + "," + coordinates[0].z
0473: + "</coordinates></Point>");
0474: }
0475:
0476: /**
0477: * Test to see if the geometry is a Multi geometry
0478: *
0479: * @return true if geom instance of MultiPolygon, MultiPoint or
0480: * MultiLineString
0481: */
0482: protected boolean isMultiPart(Geometry geom) {
0483: Class geomClass = geom.getClass();
0484:
0485: return (geomClass.equals(MultiPolygon.class)
0486: || geomClass.equals(MultiPoint.class) || geomClass
0487: .equals(MultiLineString.class));
0488: }
0489:
0490: /**
0491: * Applies each feature type styler in turn to all of the features.
0492: *
0493: * @param features
0494: * A FeatureCollection contatining the features to be rendered
0495: * @param featureStylers
0496: * An array of feature stylers to be applied
0497: * @throws IOException
0498: * @throws IllegalAttributeException
0499: * @TODO: multiple features types result in muliple data passes, could be
0500: * split into separate tempory files then joined.
0501: */
0502: private void processStylersVector(final FeatureCollection features,
0503: final FeatureTypeStyle[] featureStylers,
0504: final MapLayer layer) throws IOException,
0505: IllegalAttributeException {
0506: final int ftsLength = featureStylers.length;
0507:
0508: for (int i = 0; i < ftsLength; i++) {
0509: FeatureTypeStyle fts = featureStylers[i];
0510: final String typeName = features.getSchema().getTypeName();
0511:
0512: if ((typeName != null)
0513: && (features.getSchema().isDescendedFrom(null,
0514: fts.getFeatureTypeName()) || typeName
0515: .equalsIgnoreCase(fts.getFeatureTypeName()))) {
0516: // get applicable rules at the current scale
0517: Rule[] rules = fts.getRules();
0518: List ruleList = new ArrayList();
0519: List elseRuleList = new ArrayList();
0520: populateRuleLists(rules, ruleList, elseRuleList, false);
0521:
0522: if ((ruleList.size() == 0)
0523: && (elseRuleList.size() == 0)) {
0524: return; // bail out early if no rules made it (because of
0525: // scale denominators)
0526: }
0527:
0528: // REVISIT: once scaleDemominator can actualy be determined
0529: // re-evaluate sensible ranges for GE
0530: NumberRange scaleRange = new NumberRange(
0531: scaleDenominator, scaleDenominator);
0532: FeatureIterator reader = features.features();
0533:
0534: while (true) {
0535: try {
0536: if (!reader.hasNext()) {
0537: break;
0538: }
0539:
0540: boolean doElse = true;
0541:
0542: Feature feature = reader.next();
0543: StringBuffer featureLabel = new StringBuffer(""); // this
0544: // gets
0545: // filled
0546: // in
0547: // if
0548: // there
0549: // is a
0550: // textsymbolizer
0551: String id = feature.getID();
0552: id = id.replaceAll("&", "");
0553: id = id.replaceAll(">", "");
0554: id = id.replaceAll("<", "");
0555: id = id.replaceAll("%", "");
0556: startDocument(id, layer.getTitle());
0557:
0558: // start writing out the styles
0559: write("<Style id=\"GeoServerStyle"
0560: + feature.getID() + "\">");
0561:
0562: // applicable rules
0563: for (Iterator it = ruleList.iterator(); it
0564: .hasNext();) {
0565: Rule r = (Rule) it.next();
0566: LOGGER.finer(new StringBuffer(
0567: "applying rule: ").append(
0568: r.toString()).toString());
0569:
0570: Filter filter = r.getFilter();
0571:
0572: // if there is no filter or the filter says to do
0573: // the feature anyways, render it
0574: if ((filter == null)
0575: || filter.evaluate(feature)) {
0576: doElse = false;
0577: LOGGER
0578: .finer("processing Symobolizer ...");
0579:
0580: Symbolizer[] symbolizers = r
0581: .getSymbolizers();
0582: processVectorSymbolizers(feature,
0583: symbolizers, scaleRange,
0584: featureLabel);
0585: }
0586: }
0587:
0588: if (doElse) {
0589: // rules with an else filter
0590: LOGGER.finer("rules with an else filter");
0591:
0592: for (Iterator it = elseRuleList.iterator(); it
0593: .hasNext();) {
0594: Rule r = (Rule) it.next();
0595: Symbolizer[] symbolizers = r
0596: .getSymbolizers();
0597: LOGGER
0598: .finer("processing Symobolizer ...");
0599: processVectorSymbolizers(feature,
0600: symbolizers, scaleRange,
0601: featureLabel);
0602: }
0603: }
0604:
0605: write("</Style>"); // close off styles
0606:
0607: // we have written out the style, so now lets write out
0608: // the geometry
0609: String fTitle = featureLabel.toString();
0610:
0611: if (fTitle.equals("")) {
0612: fTitle = feature.getID();
0613: }
0614:
0615: write("<Placemark>");
0616: write("<name><![CDATA[" + featureLabel
0617: + "]]></name>"); // CDATA
0618: // needed
0619: // for
0620: // ampersands
0621:
0622: final FeatureType schema = features.getSchema();
0623:
0624: // if there are supposed to be detailed descriptions,
0625: // write them out
0626: write("<description><![CDATA[");
0627: writeDescription(feature, schema);
0628: write("]]></description>");
0629:
0630: writeLookAt(findGeometry(feature), transformer);
0631: write("<styleUrl>#GeoServerStyle"
0632: + feature.getID() + "</styleUrl>");
0633: write("<MultiGeometry>");
0634: writePlaceMarkPoint(findGeometry(feature),
0635: transformer);
0636: writeGeometry(findGeometry(feature),
0637: transformer);
0638: write("</MultiGeometry>");
0639: write("</Placemark>");
0640: newline();
0641:
0642: endDocument(); // </Document>
0643: } catch (Exception e) {
0644: // that feature failed but others may still work
0645: // REVISIT: don't like eating exceptions, even with a
0646: // log.
0647: // e.printStackTrace();
0648: LOGGER.warning(new StringBuffer(
0649: "KML transform for feature failed ")
0650: .append(e.getMessage()).toString());
0651: }
0652: }
0653:
0654: // FeatureIterators may be backed by a stream so this tidies
0655: // things up.
0656: features.close(reader);
0657: }
0658: }
0659: }
0660:
0661: /**
0662: * Applies each feature type styler in turn to all of the features.
0663: *
0664: * @param features
0665: * A FeatureCollection contatining the features to be rendered
0666: * @param featureStylers
0667: * An array of feature stylers to be applied
0668: * @throws IOException
0669: * @throws IllegalAttributeException
0670: * @TODO: multiple features types result in muliple data passes, could be
0671: * split into separate tempory files then joined.
0672: */
0673: private void processStylersCoverage(
0674: final FeatureCollection features,
0675: final FeatureTypeStyle[] featureStylers,
0676: final MapLayer layer) throws IOException,
0677: IllegalAttributeException {
0678: final int ftStylesLength = featureStylers.length;
0679:
0680: for (int i = 0; i < ftStylesLength; i++) { // for each style
0681:
0682: FeatureTypeStyle fts = featureStylers[i];
0683: String typeName = features.getSchema().getTypeName();
0684:
0685: if ((typeName != null)
0686: && (features.getSchema().isDescendedFrom(null,
0687: fts.getFeatureTypeName()) || typeName
0688: .equalsIgnoreCase(fts.getFeatureTypeName()))) {
0689: // get applicable rules at the current scale
0690: Rule[] rules = fts.getRules();
0691: List ruleList = new ArrayList();
0692: List elseRuleList = new ArrayList();
0693: populateRuleLists(rules, ruleList, elseRuleList, false);
0694:
0695: if ((ruleList.size() == 0)
0696: && (elseRuleList.size() == 0)) {
0697: return;
0698: }
0699:
0700: FeatureIterator reader = features.features();
0701:
0702: // we aren't going to iterate through the features because we
0703: // just need to prepare
0704: // the kml document for one feature; it is a raster result.
0705: try {
0706: if (!reader.hasNext()) {
0707: continue; // no features, so move on
0708: }
0709:
0710: boolean doElse = true;
0711: Feature feature = reader.next();
0712:
0713: // applicable rules
0714: for (Iterator it = ruleList.iterator(); it
0715: .hasNext();) {
0716: Rule r = (Rule) it.next();
0717: LOGGER
0718: .finer(new StringBuffer(
0719: "applying rule: ").append(
0720: r.toString()).toString());
0721:
0722: Filter filter = r.getFilter();
0723:
0724: if ((filter == null)
0725: || filter.evaluate(feature)) {
0726: doElse = false;
0727: LOGGER
0728: .finer("processing raster-result Symobolizer ...");
0729:
0730: Symbolizer[] symbolizers = r
0731: .getSymbolizers();
0732:
0733: processRasterSymbolizersForCoverage(
0734: feature, symbolizers, layer);
0735: }
0736: }
0737:
0738: if (doElse) {
0739: // rules with an else filter
0740: LOGGER.finer("rules with an else filter");
0741:
0742: for (Iterator it = elseRuleList.iterator(); it
0743: .hasNext();) {
0744: Rule r = (Rule) it.next();
0745: Symbolizer[] symbolizers = r
0746: .getSymbolizers();
0747: LOGGER
0748: .finer("processing raster-result Symobolizer ...");
0749:
0750: processRasterSymbolizersForCoverage(
0751: feature, symbolizers, layer);
0752: }
0753: }
0754: } catch (Exception e) {
0755: // that feature failed but others may still work
0756: // REVISIT: don't like eating exceptions, even with a log.
0757: LOGGER.warning(new StringBuffer(
0758: "KML transform for feature failed ")
0759: .append(e.getMessage()).toString());
0760: }
0761:
0762: // FeatureIterators may be backed by a stream so this tidies
0763: // things up.
0764: features.close(reader);
0765: } // end if
0766: } // end for loop
0767: }
0768:
0769: /**
0770: *
0771: * @param features
0772: * @param featureStylers
0773: * @param layer
0774: * @param order
0775: * @throws IOException
0776: * @throws IllegalAttributeException
0777: */
0778: private void processStylersRaster(final FeatureCollection features,
0779: final FeatureTypeStyle[] featureStylers,
0780: final MapLayer layer, final int order) throws IOException,
0781: IllegalAttributeException {
0782: startFolder("layer_" + order, layer.getTitle());
0783:
0784: int layerCounter = order;
0785:
0786: final int ftStylesLength = featureStylers.length;
0787:
0788: for (int i = 0; i < ftStylesLength; i++) { // for each style
0789:
0790: FeatureTypeStyle fts = featureStylers[i];
0791: String typeName = features.getSchema().getTypeName();
0792:
0793: if ((typeName != null)
0794: && (features.getSchema().isDescendedFrom(null,
0795: fts.getFeatureTypeName()) || typeName
0796: .equalsIgnoreCase(fts.getFeatureTypeName()))) {
0797: // get applicable rules at the current scale
0798: Rule[] rules = fts.getRules();
0799: List ruleList = new ArrayList();
0800: List elseRuleList = new ArrayList();
0801: populateRuleLists(rules, ruleList, elseRuleList, true);
0802:
0803: if ((ruleList.size() == 0)
0804: && (elseRuleList.size() == 0)) {
0805: return;
0806: }
0807:
0808: FeatureIterator reader = features.features();
0809:
0810: // we aren't going to iterate through the features because we
0811: // just need to prepare
0812: // the kml document for one feature; it is a raster result.
0813: try {
0814: if (!reader.hasNext()) {
0815: continue; // no features, so move on
0816: }
0817:
0818: boolean doElse = true;
0819: Feature feature = reader.next();
0820:
0821: // applicable rules
0822: for (Iterator it = ruleList.iterator(); it
0823: .hasNext();) {
0824: Rule r = (Rule) it.next();
0825: LOGGER
0826: .finer(new StringBuffer(
0827: "applying rule: ").append(
0828: r.toString()).toString());
0829:
0830: Filter filter = r.getFilter();
0831:
0832: if ((filter == null)
0833: || filter.evaluate(feature)) {
0834: doElse = false;
0835: LOGGER
0836: .finer("processing raster-result Symobolizer ...");
0837:
0838: Symbolizer[] symbolizers = r
0839: .getSymbolizers();
0840:
0841: processRasterSymbolizers(feature,
0842: symbolizers, order);
0843: layerCounter++;
0844: }
0845: }
0846:
0847: if (doElse) {
0848: // rules with an else filter
0849: LOGGER.finer("rules with an else filter");
0850:
0851: for (Iterator it = elseRuleList.iterator(); it
0852: .hasNext();) {
0853: Rule r = (Rule) it.next();
0854: Symbolizer[] symbolizers = r
0855: .getSymbolizers();
0856: LOGGER
0857: .finer("processing raster-result Symobolizer ...");
0858:
0859: processRasterSymbolizers(feature,
0860: symbolizers, order);
0861: layerCounter++;
0862: }
0863: }
0864: } catch (Exception e) {
0865: // that feature failed but others may still work
0866: // REVISIT: don't like eating exceptions, even with a log.
0867: LOGGER.warning(new StringBuffer(
0868: "KML transform for feature failed ")
0869: .append(e.getMessage()).toString());
0870: }
0871:
0872: // FeatureIterators may be backed by a stream so this tidies
0873: // things up.
0874: features.close(reader);
0875: } // end if
0876: } // end for loop
0877:
0878: endFolder(); // close the folder </Folder>
0879: }
0880:
0881: /**
0882: * Sorts the rules into "If" rules and "Else" rules. The rules are sorted
0883: * into their respective lists.
0884: *
0885: * @param rules
0886: * @param ruleList
0887: * @param elseRuleList
0888: * @param ignoreScale
0889: * ignore the scale denominator
0890: */
0891: private void populateRuleLists(Rule[] rules, List ruleList,
0892: List elseRuleList, boolean ignoreScale) {
0893: final int rulesLength = rules.length;
0894:
0895: for (int j = 0; j < rulesLength; j++) {
0896: Rule r = rules[j];
0897:
0898: if (ignoreScale) {
0899: if (r.hasElseFilter()) {
0900: elseRuleList.add(r);
0901: } else {
0902: ruleList.add(r);
0903: }
0904: } else {
0905: if (isWithinScale(r)) {
0906: if (r.hasElseFilter()) {
0907: elseRuleList.add(r);
0908: } else {
0909: ruleList.add(r);
0910: }
0911: }
0912: }
0913: }
0914: }
0915:
0916: private void writeDescription(Feature feature,
0917: final FeatureType schema) throws IOException {
0918: if (mapContext.getRequest().getKMattr()) {
0919: // descriptions are "templatable" by users, so see if there is a
0920: // template available for use
0921: GeoServerTemplateLoader templateLoader = new GeoServerTemplateLoader(
0922: getClass());
0923: templateLoader.setFeatureType(schema);
0924:
0925: Template template = null;
0926:
0927: // Configuration is not thread safe
0928: synchronized (templateConfig) {
0929: templateConfig.setTemplateLoader(templateLoader);
0930: template = templateConfig
0931: .getTemplate("kmlDescription.ftl");
0932: }
0933:
0934: try {
0935: template.setEncoding("UTF-8");
0936: template.process(feature, this );
0937: } catch (TemplateException e) {
0938: String msg = "Error occured processing template.";
0939: throw (IOException) new IOException(msg).initCause(e);
0940: }
0941: }
0942: }
0943:
0944: private void processVectorSymbolizers(final Feature feature,
0945: final Symbolizer[] symbolizers, Range scaleRange,
0946: StringBuffer featureLabel) throws IOException,
0947: TransformerException {
0948: final int length = symbolizers.length;
0949:
0950: // for each Symbolizer (text, polygon, line etc...)
0951: for (int m = 0; m < length; m++) {
0952: LOGGER.finer(new StringBuffer("applying symbolizer ")
0953: .append(symbolizers[m]).toString());
0954:
0955: if (symbolizers[m] instanceof TextSymbolizer) {
0956: TextSymbolizer ts = (TextSymbolizer) symbolizers[m];
0957: Expression ex = ts.getLabel();
0958: featureLabel.append((String) ex.evaluate(feature,
0959: String.class)); // attach
0960: // the
0961: // lable
0962: // title
0963:
0964: Style2D style = styleFactory.createStyle(feature,
0965: symbolizers[m], scaleRange);
0966: writeStyle(style, feature.getID(), symbolizers[m]);
0967: } else { // all other symbolizers
0968:
0969: Style2D style = styleFactory.createStyle(feature,
0970: symbolizers[m], scaleRange);
0971: writeStyle(style, feature.getID(), symbolizers[m]);
0972: }
0973: } // end for loop
0974: }
0975:
0976: /**
0977: * Writes out the KML for a ground overlay. The image is processed later on.
0978: *
0979: * This will style the KML for raster output. There are no descriptions with
0980: * vector output, as that would make the result really large (assuming that
0981: * they chose raster output because a lot of features were requested).
0982: *
0983: *
0984: * @param feature
0985: * @param symbolizers
0986: * @param order
0987: * @throws IOException
0988: * @throws TransformerException
0989: */
0990: private void processRasterSymbolizers(final Feature feature,
0991: final Symbolizer[] symbolizers, final int order)
0992: throws IOException, TransformerException {
0993: if (symbolizers.length < 1) {
0994: return; // no symbolizers so return
0995: }
0996:
0997: LOGGER.finer("applying one symbolizer: "
0998: + symbolizers[0].toString());
0999:
1000: com.vividsolutions.jts.geom.Envelope envelope = this .mapContext
1001: .getRequest().getBbox();
1002: write(new StringBuffer("<GroundOverlay>").append("<name>")
1003: .append(feature.getID()).append("</name>").append(
1004: "<drawOrder>").append(order).append(
1005: "</drawOrder>").append("<Icon>").toString());
1006:
1007: final double[] BBOX = new double[] { envelope.getMinX(),
1008: envelope.getMinY(), envelope.getMaxX(),
1009: envelope.getMaxY() };
1010: write(new StringBuffer("<href>layer_").append(order).append(
1011: ".png</href>").append(
1012: "<viewRefreshMode>never</viewRefreshMode>").append(
1013: "<viewBoundScale>0.75</viewBoundScale>").append(
1014: "</Icon>").append("<LatLonBox>").append("<north>")
1015: .append(BBOX[3]).append("</north>").append("<south>")
1016: .append(BBOX[1]).append("</south>").append("<east>")
1017: .append(BBOX[2]).append("</east>").append("<west>")
1018: .append(BBOX[0]).append("</west>").append(
1019: "</LatLonBox>").append("</GroundOverlay>")
1020: .toString());
1021: }
1022:
1023: /**
1024: * Writes out the KML for a ground overlay. The image is processed later on.
1025: *
1026: * @param feature
1027: * @param symbolizers
1028: * @param order
1029: * @throws IOException
1030: * @throws TransformerException
1031: */
1032: private void processRasterSymbolizersForCoverage(
1033: final Feature feature, final Symbolizer[] symbolizers,
1034: final MapLayer layer) throws IOException,
1035: TransformerException {
1036: if (symbolizers.length < 1) {
1037: return; // no symbolizers so return
1038: }
1039:
1040: LOGGER.finer("applying one symbolizer: "
1041: + symbolizers[0].toString());
1042:
1043: final AbstractGridCoverage2DReader gcReader = (AbstractGridCoverage2DReader) feature
1044: .getAttribute("grid");
1045:
1046: // TODO add read parameters feature.getAttribute("params")
1047: final HttpServletRequest request = this .mapContext.getRequest()
1048: .getHttpServletRequest();
1049: final String baseURL = org.vfny.geoserver.util.Requests
1050: .getBaseUrl(request, null);
1051:
1052: com.vividsolutions.jts.geom.Envelope envelope = this .mapContext
1053: .getRequest().getBbox();
1054: write(new StringBuffer("<GroundOverlay>").append("<name>")
1055: .append(feature.getID()).append("</name>").append(
1056: "<Icon>").toString());
1057:
1058: final double[] BBOX = new double[] { envelope.getMinX(),
1059: envelope.getMinY(), envelope.getMaxX(),
1060: envelope.getMaxY() };
1061:
1062: final StringBuffer getMapRequest = new StringBuffer(baseURL)
1063: .append("wms?bbox=")
1064: .append(BBOX[0])
1065: .append(",")
1066: .append(BBOX[1])
1067: .append(",")
1068: .append(BBOX[2])
1069: .append(",")
1070: .append(BBOX[3])
1071: .append("&styles=")
1072: .append(layer.getStyle().getName())
1073: .append(
1074: "&Format=image/png&request=GetMap&layers=")
1075: .append(layer.getTitle())
1076: .append(
1077: "&width="
1078: + this .mapContext.getMapWidth()
1079: + "&height="
1080: + this .mapContext.getMapHeight()
1081: + "&srs=EPSG:4326&transparent=true&");
1082:
1083: write(new StringBuffer("<href>").append(getMapRequest).append(
1084: "</href>").append(
1085: "<viewRefreshMode>never</viewRefreshMode>").append(
1086: "<viewBoundScale>0.75</viewBoundScale>").append(
1087: "</Icon>").append("<LatLonBox>").append("<north>")
1088: .append(BBOX[3]).append("</north>").append("<south>")
1089: .append(BBOX[1]).append("</south>").append("<east>")
1090: .append(BBOX[2]).append("</east>").append("<west>")
1091: .append(BBOX[0]).append("</west>").append(
1092: "</LatLonBox>").append("</GroundOverlay>")
1093: .toString());
1094: }
1095:
1096: /**
1097: * Applies each of a set of symbolizers in turn to a given feature.
1098: * <p>
1099: * This is an internal method and should only be called by processStylers.
1100: * </p>
1101: *
1102: * The KML color tag: The order of expression is alpha, blue, green, red
1103: * (ABGR). The range of values for any one color is 0 to 255 (00 to ff). For
1104: * opacity, 00 is fully transparent and ff is fully opaque.
1105: *
1106: * @param feature
1107: * The feature to be rendered
1108: * @param symbolizers
1109: * An array of symbolizers which actually perform the rendering.
1110: * @param scaleRange
1111: * The scale range we are working on... provided in order to make
1112: * the style factory happy
1113: */
1114: private boolean processSymbolizers(
1115: final FeatureCollection features, final Feature feature,
1116: final Symbolizer[] symbolizers, Range scaleRange,
1117: final MapLayer layer, final int order,
1118: final int layerCounter, StringBuffer title,
1119: boolean vectorResult) throws IOException,
1120: TransformerException {
1121: boolean res = false;
1122:
1123: // String title=null;
1124: final int length = symbolizers.length;
1125:
1126: // for each Symbolizer (text, polygon, line etc...)
1127: for (int m = 0; m < length; m++) {
1128: LOGGER.finer(new StringBuffer("applying symbolizer ")
1129: .append(symbolizers[m]).toString());
1130:
1131: if (symbolizers[m] instanceof RasterSymbolizer) {
1132: // LOGGER.info("Removed by bao for testing");
1133: /*
1134: * final GridCoverage gc = (GridCoverage)
1135: * feature.getAttribute("grid"); final HttpServletRequest
1136: * request =
1137: * this.mapContext.getRequest().getHttpServletRequest(); final
1138: * String baseURL =
1139: * org.vfny.geoserver.util.Requests.getBaseUrl(request);
1140: * com.vividsolutions.jts.geom.Envelope envelope =
1141: * this.mapContext.getRequest().getBbox();
1142: */
1143:
1144: /**
1145: * EXAMPLE OUTPUT: <GroundOverlay> <name>Google Earth - New
1146: * Image Overlay</name> <Icon>
1147: * <href>http://localhost:8081/geoserver/wms?bbox=-130,24,-66,50&styles=raster&Format=image/tiff&request=GetMap&layers=nurc:Img_Sample&width=550&height=250&srs=EPSG:4326&</href>
1148: * <viewRefreshMode>never</viewRefreshMode>
1149: * <viewBoundScale>0.75</viewBoundScale> </Icon> <LatLonBox>
1150: * <north>50.0</north> <south>24.0</south> <east>-66.0</east>
1151: * <west>-130.0</west> </LatLonBox> </GroundOverlay>
1152: */
1153:
1154: /*
1155: * write(new StringBuffer("<GroundOverlay>"). append("<name>").append(((GridCoverage2D)gc).getName()).append("</name>").
1156: * append("<drawOrder>").append(order).append("</drawOrder>").
1157: * append("<Icon>").toString()); final double[] BBOX = new
1158: * double[] { envelope.getMinX(), envelope.getMinY(),
1159: * envelope.getMaxX(), envelope.getMaxY() }; if (layerCounter<0) {
1160: * final StringBuffer getMapRequest = new
1161: * StringBuffer(baseURL).append("wms?bbox=").append(BBOX[0]).append(",").
1162: * append(BBOX[1]).append(",").append(BBOX[2]).append(",").append(BBOX[3]).append("&styles=").
1163: * append(layer.getStyle().getName()).append("&Format=image/png&request=GetMap&layers=").
1164: * append(layer.getTitle()).append("&width="+this.mapContext.getMapWidth()+"&height="+this.mapContext.getMapHeight()+"&srs=EPSG:4326&");
1165: * write("<href>"+getMapRequest.toString()+"</href>"); } else {
1166: * write("<href>layer_"+order+".png</href>"); } write(new
1167: * StringBuffer("<viewRefreshMode>never</viewRefreshMode>").
1168: * append("<viewBoundScale>0.75</viewBoundScale>"). append("</Icon>").
1169: * append("<LatLonBox>"). append("<north>").append(BBOX[3]).append("</north>").
1170: * append("<south>").append(BBOX[1]).append("</south>").
1171: * append("<east>").append(BBOX[2]).append("</east>").
1172: * append("<west>").append(BBOX[0]).append("</west>").
1173: * append("</LatLonBox>"). append("</GroundOverlay>").toString());
1174: * //Geometry g = findGeometry(feature, symbolizers[m]);
1175: * //writeRasterStyle(getMapRequest.toString(),
1176: * feature.getID());
1177: */
1178: res = true;
1179: } else if (vectorResult) {
1180: // TODO: come back and sort out crs transformation
1181: // CoordinateReferenceSystem crs = findGeometryCS(feature,
1182: // symbolizers[m]);
1183: if (symbolizers[m] instanceof TextSymbolizer) {
1184: TextSymbolizer ts = (TextSymbolizer) symbolizers[m];
1185: Expression ex = ts.getLabel();
1186: String value = (String) ex.evaluate(feature,
1187: String.class);
1188: title.append(value);
1189:
1190: Style2D style = styleFactory.createStyle(feature,
1191: symbolizers[m], scaleRange);
1192: writeStyle(style, feature.getID(), symbolizers[m]);
1193: } else {
1194: Style2D style = styleFactory.createStyle(feature,
1195: symbolizers[m], scaleRange);
1196: writeStyle(style, feature.getID(), symbolizers[m]);
1197: }
1198: } else if (!vectorResult) {
1199: com.vividsolutions.jts.geom.Envelope envelope = this .mapContext
1200: .getRequest().getBbox();
1201: write(new StringBuffer("<GroundOverlay>").append(
1202: "<name>").append(feature.getID()).append(
1203: "</name>").append("<drawOrder>").append(order)
1204: .append("</drawOrder>").append("<Icon>")
1205: .toString());
1206:
1207: final double[] BBOX = new double[] {
1208: envelope.getMinX(), envelope.getMinY(),
1209: envelope.getMaxX(), envelope.getMaxY() };
1210: write(new StringBuffer("<href>layer_")
1211: .append(order)
1212: .append(".png</href>")
1213: .append(
1214: "<viewRefreshMode>never</viewRefreshMode>")
1215: .append("<viewBoundScale>0.75</viewBoundScale>")
1216: .append("</Icon>").append("<LatLonBox>")
1217: .append("<north>").append(BBOX[3]).append(
1218: "</north>").append("<south>").append(
1219: BBOX[1]).append("</south>").append(
1220: "<east>").append(BBOX[2]).append(
1221: "</east>").append("<west>").append(
1222: BBOX[0]).append("</west>").append(
1223: "</LatLonBox>").append(
1224: "</GroundOverlay>").toString());
1225: } else {
1226: LOGGER
1227: .info("KMZ processSymbolizerz unknown case. Please report error.");
1228: }
1229: }
1230:
1231: return res;
1232: }
1233:
1234: /**
1235: * Adds the <style> tag to the KML document.
1236: *
1237: * @param style
1238: * @param id
1239: * @throws IOException
1240: */
1241: private void writeStyle(final Style2D style, final String id,
1242: Symbolizer sym) throws IOException {
1243: if (style instanceof PolygonStyle2D
1244: && sym instanceof PolygonSymbolizer) {
1245: if ((((PolygonStyle2D) style).getFill() == null)
1246: && (((PolygonStyle2D) style).getStroke() == null)) {
1247: LOGGER
1248: .info("Empty PolygonSymbolizer, using default fill and stroke.");
1249: }
1250:
1251: final StringBuffer styleString = new StringBuffer();
1252:
1253: PolygonSymbolizer polySym = (PolygonSymbolizer) sym;
1254:
1255: // ** LABEL **
1256: styleString.append("<IconStyle>");
1257:
1258: if (!mapContext.getRequest().getKMattr()) { // if they don't want
1259: // attributes
1260: styleString.append("<color>#00ffffff</color>"); // fully
1261: // transparent
1262: }
1263:
1264: styleString
1265: .append("<Icon><href>root://icons/palette-3.png</href><x>224</x><w>32</w><h>32</h></Icon>");
1266: styleString.append("</IconStyle>");
1267:
1268: // ** FILL **
1269: styleString.append("<PolyStyle><color>");
1270:
1271: if (polySym.getFill() != null) // if they specified a fill
1272: {
1273: int opacity = 255; // default to full opacity
1274:
1275: if (polySym.getFill().getOpacity() != null) {
1276: float op = getOpacity(polySym.getFill()
1277: .getOpacity());
1278: opacity = (new Float(255 * op)).intValue();
1279: }
1280:
1281: Paint p = ((PolygonStyle2D) style).getFill();
1282:
1283: if (p instanceof Color) {
1284: styleString.append("#").append(intToHex(opacity))
1285: .append(colorToHex((Color) p)); // transparancy needs to
1286: // come from the opacity
1287: // value.
1288: } else {
1289: styleString.append("#ffaaaaaa"); // should not occure in
1290: // normal parsing
1291: }
1292: } else { // no fill specified, make transparent
1293: styleString.append("#00aaaaaa");
1294: }
1295:
1296: // if there is an outline, specify that we have one, then style it
1297: styleString.append("</color>");
1298:
1299: if (polySym.getStroke() != null) {
1300: styleString.append("<outline>1</outline>");
1301: } else {
1302: styleString.append("<outline>0</outline>");
1303: }
1304:
1305: styleString.append("</PolyStyle>");
1306:
1307: // ** OUTLINE **
1308: if (polySym.getStroke() != null) // if there is an outline
1309: {
1310: styleString.append("<LineStyle><color>");
1311:
1312: int opacity = 255; // default to full opacity
1313:
1314: if (polySym.getStroke().getOpacity() != null) {
1315: float op = getOpacity(polySym.getStroke()
1316: .getOpacity());
1317: opacity = (new Float(255 * op)).intValue();
1318: }
1319:
1320: Paint p = ((PolygonStyle2D) style).getContour();
1321:
1322: if (p instanceof Color) {
1323: styleString.append("#").append(intToHex(opacity))
1324: .append(colorToHex((Color) p)); // transparancy needs to
1325: // come from the opacity
1326: // value.
1327: } else {
1328: styleString.append("#ffaaaaaa"); // should not occure in
1329: // normal parsing
1330: }
1331:
1332: styleString.append("</color>");
1333:
1334: // stroke width
1335: if (polySym.getStroke().getWidth() != null) {
1336: int width = getWidth(polySym.getStroke().getWidth());
1337: styleString.append("<width>").append(width).append(
1338: "</width>");
1339: }
1340:
1341: styleString.append("</LineStyle>");
1342: }
1343:
1344: write(styleString.toString());
1345: } else if (style instanceof LineStyle2D
1346: && sym instanceof LineSymbolizer) {
1347: if (((LineStyle2D) style).getStroke() == null) {
1348: LOGGER
1349: .info("Empty LineSymbolizer, using default stroke.");
1350: }
1351:
1352: LineSymbolizer lineSym = (LineSymbolizer) sym;
1353:
1354: // ** LABEL **
1355: final StringBuffer styleString = new StringBuffer();
1356: styleString.append("<IconStyle>");
1357:
1358: if (!mapContext.getRequest().getKMattr()) { // if they don't want
1359: // attributes
1360: styleString.append("<color>#00ffffff</color>"); // fully
1361: // transparent
1362: }
1363:
1364: styleString.append("</IconStyle>");
1365:
1366: // ** LINE **
1367: styleString.append("<LineStyle><color>");
1368:
1369: if (lineSym.getStroke() != null) {
1370: int opacity = 255;
1371:
1372: if (lineSym.getStroke().getOpacity() != null) {
1373: float op = getOpacity(lineSym.getStroke()
1374: .getOpacity());
1375: opacity = (new Float(255 * op)).intValue();
1376: }
1377:
1378: Paint p = ((LineStyle2D) style).getContour();
1379:
1380: if (p instanceof Color) {
1381: styleString.append("#").append(intToHex(opacity))
1382: .append(colorToHex((Color) p)); // transparancy needs to
1383: // come from the opacity
1384: // value.
1385: } else {
1386: styleString.append("#ffaaaaaa"); // should not occure in
1387: // normal parsing
1388: }
1389:
1390: styleString.append("</color>");
1391:
1392: // stroke width
1393: if (lineSym.getStroke().getWidth() != null) {
1394: int width = getWidth(lineSym.getStroke().getWidth());
1395: styleString.append("<width>").append(width).append(
1396: "</width>");
1397: }
1398: } else // no style defined, so use default
1399: {
1400: styleString.append("#ffaaaaaa");
1401: styleString.append("</color><width>1</width>");
1402: }
1403:
1404: styleString.append("</LineStyle>");
1405:
1406: write(styleString.toString());
1407: } else if (style instanceof TextStyle2D
1408: && sym instanceof TextSymbolizer) {
1409: final StringBuffer styleString = new StringBuffer();
1410: TextSymbolizer textSym = (TextSymbolizer) sym;
1411:
1412: styleString.append("<LabelStyle><color>");
1413:
1414: if (textSym.getFill() != null) {
1415: int opacity = 255;
1416:
1417: if (textSym.getFill().getOpacity() != null) {
1418: float op = getOpacity(textSym.getFill()
1419: .getOpacity());
1420: opacity = (new Float(255 * op)).intValue();
1421: }
1422:
1423: Paint p = ((TextStyle2D) style).getFill();
1424:
1425: if (p instanceof Color) {
1426: styleString.append("#").append(intToHex(opacity))
1427: .append(colorToHex((Color) p)); // transparancy needs to
1428: // come from the opacity
1429: // value.
1430: } else {
1431: styleString.append("#ffaaaaaa"); // should not occure in
1432: // normal parsing
1433: }
1434:
1435: styleString.append("</color></LabelStyle>");
1436: } else {
1437: styleString.append("#ffaaaaaa");
1438: styleString.append("</color></LabelStyle>");
1439: }
1440:
1441: write(styleString.toString());
1442: } else if (style instanceof MarkStyle2D
1443: && sym instanceof PointSymbolizer) {
1444: // we can sorta style points. Just with color however.
1445: final StringBuffer styleString = new StringBuffer();
1446: PointSymbolizer pointSym = (PointSymbolizer) sym;
1447:
1448: styleString.append("<IconStyle><color>");
1449:
1450: if ((pointSym.getGraphic() != null)
1451: && (pointSym.getGraphic().getMarks() != null)) {
1452: Mark[] marks = pointSym.getGraphic().getMarks();
1453:
1454: if ((marks.length > 0) && (marks[0] != null)) {
1455: Mark mark = marks[0];
1456:
1457: int opacity = 255;
1458:
1459: if (mark.getFill().getOpacity() != null) {
1460: float op = getOpacity(mark.getFill()
1461: .getOpacity());
1462: opacity = (new Float(255 * op)).intValue();
1463: }
1464:
1465: Paint p = ((MarkStyle2D) style).getFill();
1466:
1467: if (p instanceof Color) {
1468: styleString.append("#").append(
1469: intToHex(opacity)).append(
1470: colorToHex((Color) p)); // transparancy
1471: // needs to come
1472: // from the
1473: // opacity
1474: // value.
1475: } else {
1476: styleString.append("#ffaaaaaa"); // should not occure
1477: // in normal parsing
1478: }
1479: } else {
1480: styleString.append("#ffaaaaaa");
1481: }
1482: } else {
1483: styleString.append("#ffaaaaaa");
1484: }
1485:
1486: styleString.append("</color>");
1487: styleString.append("<colorMode>normal</colorMode>");
1488: styleString
1489: .append("<Icon><href>root://icons/palette-4.png</href>");
1490: styleString
1491: .append("<x>32</x><y>128</y><w>32</w><h>32</h></Icon>");
1492:
1493: styleString.append("</IconStyle>");
1494:
1495: write(styleString.toString());
1496: }
1497: }
1498:
1499: /**
1500: * @param href
1501: * @param id
1502: * @throws IOException
1503: */
1504: private void writeRasterStyle(final String href, final String id)
1505: throws IOException {
1506: final StringBuffer styleString = new StringBuffer();
1507: styleString.append("<Style id=\"GeoServerStyle").append(id)
1508: .append("\">");
1509: styleString
1510: .append("<IconStyle><Icon><href>")
1511: .append(href)
1512: .append(
1513: "</href><viewRefreshMode>never</viewRefreshMode>")
1514: .append("<viewBoundScale>0.75</viewBoundScale><w>")
1515: .append(this .mapContext.getMapWidth())
1516: .append("</w><h>").append(
1517: this .mapContext.getMapHeight()).append(
1518: "</h></Icon></IconStyle>");
1519: styleString
1520: .append("<PolyStyle><fill>0</fill><outline>0</outline></PolyStyle>");
1521: styleString.append("</Style>");
1522:
1523: write(styleString.toString());
1524: }
1525:
1526: private boolean isWithinScale(Rule r) {
1527: double min = r.getMinScaleDenominator();
1528: double max = r.getMaxScaleDenominator();
1529:
1530: if (((min - TOLERANCE) <= scaleDenominator)
1531: && ((max + TOLERANCE) >= scaleDenominator)) {
1532: return true;
1533: } else {
1534: return false;
1535: }
1536: }
1537:
1538: /**
1539: * Finds the geometric attribute requested by the symbolizer
1540: *
1541: * @param f
1542: * The feature
1543: * @param s
1544: * The symbolizer
1545: * @return The geometry requested in the symbolizer, or the default geometry
1546: * if none is specified
1547: */
1548: private com.vividsolutions.jts.geom.Geometry findGeometry(
1549: Feature f, Symbolizer s) {
1550: String geomName = getGeometryPropertyName(s);
1551:
1552: // get the geometry
1553: Geometry geom;
1554:
1555: if (geomName == null) {
1556: geom = f.getDefaultGeometry();
1557: } else {
1558: geom = (com.vividsolutions.jts.geom.Geometry) f
1559: .getAttribute(geomName);
1560: }
1561:
1562: // if the symbolizer is a point symbolizer generate a suitable location
1563: // to place the
1564: // point in order to avoid recomputing that location at each rendering
1565: // step
1566: if (s instanceof PointSymbolizer) {
1567: geom = getCentroid(geom); // djb: major simpificatioN
1568: }
1569:
1570: return geom;
1571: }
1572:
1573: /**
1574: * Returns the default geometry in the feature.
1575: *
1576: * @param f
1577: * feature to find the geometry in
1578: * @return
1579: */
1580: private com.vividsolutions.jts.geom.Geometry findGeometry(Feature f) {
1581: // get the geometry
1582: Geometry geom = f.getDefaultGeometry();
1583:
1584: // CoordinateReferenceSystem sourceCRS =
1585: // f.getFeatureType().getDefaultGeometry().getCoordinateSystem();
1586: if (!CRS.equalsIgnoreMetadata(sourceCrs, this .mapContext
1587: .getCoordinateReferenceSystem())) {
1588: try {
1589: MathTransform transform = CRS.findMathTransform(
1590: sourceCrs, this .mapContext
1591: .getCoordinateReferenceSystem(), true);
1592: geom = JTS.transform(geom, transform);
1593: } catch (MismatchedDimensionException e) {
1594: LOGGER.severe(e.getLocalizedMessage());
1595: } catch (TransformException e) {
1596: LOGGER.severe(e.getLocalizedMessage());
1597: } catch (FactoryException e) {
1598: LOGGER.severe(e.getLocalizedMessage());
1599: }
1600: }
1601:
1602: return geom;
1603: }
1604:
1605: /**
1606: * Finds the centroid of the input geometry if input = point, line, polygon
1607: * --> return a point that represents the centroid of that geom if input =
1608: * geometry collection --> return a multipoint that represents the centoid
1609: * of each sub-geom
1610: *
1611: * @param g
1612: * @return
1613: */
1614: public Geometry getCentroid(Geometry g) {
1615: if (g instanceof GeometryCollection) {
1616: GeometryCollection gc = (GeometryCollection) g;
1617: Coordinate[] pts = new Coordinate[gc.getNumGeometries()];
1618:
1619: for (int t = 0; t < gc.getNumGeometries(); t++) {
1620: pts[t] = gc.getGeometryN(t).getCentroid()
1621: .getCoordinate();
1622: }
1623:
1624: return g.getFactory().createMultiPoint(pts);
1625: } else {
1626: return g.getCentroid();
1627: }
1628: }
1629:
1630: /**
1631: * Finds the geometric attribute coordinate reference system
1632: *
1633: * @param f
1634: * The feature
1635: * @param s
1636: * The symbolizer
1637: * @return The geometry requested in the symbolizer, or the default geometry
1638: * if none is specified
1639: */
1640: private org.opengis.referencing.crs.CoordinateReferenceSystem findGeometryCS(
1641: Feature f, Symbolizer s) {
1642: String geomName = getGeometryPropertyName(s);
1643:
1644: if (geomName != null) {
1645: return ((GeometryAttributeType) f.getFeatureType()
1646: .getAttributeType(geomName)).getCoordinateSystem();
1647: } else {
1648: return ((GeometryAttributeType) f.getFeatureType()
1649: .getDefaultGeometry()).getCoordinateSystem();
1650: }
1651: }
1652:
1653: /**
1654: * Utility method to find which geometry property is referenced by a given
1655: * symbolizer.
1656: *
1657: * @param s
1658: * The symbolizer
1659: * @TODO: this is c&p from lite renderer code as the method was private
1660: * consider moving to a public unility class.
1661: */
1662: private String getGeometryPropertyName(Symbolizer s) {
1663: String geomName = null;
1664:
1665: // TODO: fix the styles, the getGeometryPropertyName should probably be
1666: // moved into an
1667: // interface...
1668: if (s instanceof PolygonSymbolizer) {
1669: geomName = ((PolygonSymbolizer) s)
1670: .getGeometryPropertyName();
1671: } else if (s instanceof PointSymbolizer) {
1672: geomName = ((PointSymbolizer) s).getGeometryPropertyName();
1673: } else if (s instanceof LineSymbolizer) {
1674: geomName = ((LineSymbolizer) s).getGeometryPropertyName();
1675: } else if (s instanceof TextSymbolizer) {
1676: geomName = ((TextSymbolizer) s).getGeometryPropertyName();
1677: }
1678:
1679: return geomName;
1680: }
1681:
1682: /**
1683: * Utility method to convert an int into hex, padded to two characters.
1684: * handy for generating colour strings.
1685: *
1686: * @param i
1687: * Int to convert
1688: * @return String a two character hex representation of i NOTE: this is a
1689: * utility method and should be put somewhere more useful.
1690: */
1691: protected static String intToHex(int i) {
1692: String prelim = Integer.toHexString(i);
1693:
1694: if (prelim.length() < 2) {
1695: prelim = "0" + prelim;
1696: }
1697:
1698: return prelim;
1699: }
1700:
1701: /**
1702: * Utility method to convert a Color into a KML color ref
1703: *
1704: * @param c
1705: * The color to convert
1706: * @return A string in BBGGRR format - note alpha must be prefixed seperatly
1707: * before use.
1708: */
1709: private String colorToHex(Color c) {
1710: return intToHex(c.getBlue()) + intToHex(c.getGreen())
1711: + intToHex(c.getRed());
1712: }
1713:
1714: /**
1715: * Borrowed from StreamingRenderer
1716: *
1717: * @param sym
1718: * @return
1719: */
1720: private float getOpacity(final Symbolizer sym) {
1721: float alpha = 1.0f;
1722: Expression exp = null;
1723:
1724: if (sym instanceof PolygonSymbolizer) {
1725: exp = ((PolygonSymbolizer) sym).getFill().getOpacity();
1726: } else if (sym instanceof LineSymbolizer) {
1727: exp = ((LineSymbolizer) sym).getStroke().getOpacity();
1728: } else if (sym instanceof PointSymbolizer) {
1729: exp = ((PointSymbolizer) sym).getGraphic().getOpacity();
1730: } else if (sym instanceof TextSymbolizer) {
1731: exp = ((TextSymbolizer) sym).getFill().getOpacity();
1732: } else {
1733: LOGGER.info("Symbolizer not matched; was of class: " + sym);
1734: }
1735:
1736: if (exp == null) {
1737: LOGGER
1738: .info("Could not determine proper symbolizer opacity.");
1739:
1740: return alpha;
1741: }
1742:
1743: Float number = (Float) exp.evaluate(null, Float.class);
1744:
1745: if (number == null) {
1746: return alpha;
1747: }
1748:
1749: return number.floatValue();
1750: }
1751:
1752: private float getOpacity(final Expression exp) {
1753: float alpha = 1.0f;
1754:
1755: Float number = (Float) exp.evaluate(null, Float.class);
1756:
1757: if (number == null) {
1758: return alpha;
1759: }
1760:
1761: return number.floatValue();
1762: }
1763:
1764: private int getWidth(final Expression exp) {
1765: int defaultWidth = 1;
1766:
1767: Integer number = (Integer) exp.evaluate(null, Integer.class);
1768:
1769: if (number == null) {
1770: return defaultWidth;
1771: }
1772:
1773: return number.intValue();
1774: }
1775: }
|