0001: /*
0002: * GeoTools - OpenSource mapping toolkit
0003: * http://geotools.org
0004: * (C) 2004-2006, Geotools Project Managment Committee (PMC)
0005: *
0006: * This library is free software; you can redistribute it and/or
0007: * modify it under the terms of the GNU Lesser General Public
0008: * License as published by the Free Software Foundation; either
0009: * version 2.1 of the License, or (at your option) any later version.
0010: *
0011: * This library is distributed in the hope that it will be useful,
0012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * Lesser General Public License for more details.
0015: */
0016: package org.geotools.renderer.lite;
0017:
0018: import java.awt.Graphics2D;
0019: import java.awt.Rectangle;
0020: import java.awt.RenderingHints;
0021: import java.awt.Shape;
0022: import java.awt.Transparency;
0023: import java.awt.font.GlyphVector;
0024: import java.awt.geom.AffineTransform;
0025: import java.awt.geom.NoninvertibleTransformException;
0026: import java.awt.image.BufferedImage;
0027: import java.io.IOException;
0028: import java.text.NumberFormat;
0029: import java.util.ArrayList;
0030: import java.util.Arrays;
0031: import java.util.Collection;
0032: import java.util.HashMap;
0033: import java.util.IdentityHashMap;
0034: import java.util.Iterator;
0035: import java.util.LinkedList;
0036: import java.util.List;
0037: import java.util.Map;
0038: import java.util.logging.Level;
0039: import java.util.logging.Logger;
0040:
0041: import javax.imageio.ImageIO;
0042: import javax.media.jai.Interpolation;
0043: import javax.media.jai.InterpolationBicubic;
0044: import javax.media.jai.InterpolationBilinear;
0045: import javax.media.jai.InterpolationNearest;
0046: import javax.media.jai.JAI;
0047: import javax.media.jai.util.Range;
0048:
0049: import org.geotools.coverage.grid.GeneralGridRange;
0050: import org.geotools.coverage.grid.GridCoverage2D;
0051: import org.geotools.coverage.grid.GridGeometry2D;
0052: import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
0053: import org.geotools.coverage.grid.io.AbstractGridFormat;
0054: import org.geotools.data.DataUtilities;
0055: import org.geotools.data.DefaultQuery;
0056: import org.geotools.data.FeatureSource;
0057: import org.geotools.data.Query;
0058: import org.geotools.data.collection.ResourceCollection;
0059: import org.geotools.data.crs.ForceCoordinateSystemFeatureResults;
0060: import org.geotools.data.memory.CollectionSource;
0061: import org.geotools.factory.CommonFactoryFinder;
0062: import org.geotools.factory.Hints;
0063: import org.geotools.feature.AttributeType;
0064: import org.geotools.feature.Feature;
0065: import org.geotools.feature.FeatureCollection;
0066: import org.geotools.feature.FeatureType;
0067: import org.geotools.feature.GeometryAttributeType;
0068: import org.geotools.feature.IllegalAttributeException;
0069: import org.geotools.filter.IllegalFilterException;
0070: import org.geotools.geometry.GeneralEnvelope;
0071: import org.geotools.geometry.jts.Decimator;
0072: import org.geotools.geometry.jts.JTS;
0073: import org.geotools.geometry.jts.LiteCoordinateSequenceFactory;
0074: import org.geotools.geometry.jts.LiteShape2;
0075: import org.geotools.geometry.jts.ReferencedEnvelope;
0076: import org.geotools.map.MapContext;
0077: import org.geotools.map.MapLayer;
0078: import org.geotools.parameter.Parameter;
0079: import org.geotools.referencing.CRS;
0080: import org.geotools.referencing.operation.BufferedCoordinateOperationFactory;
0081: import org.geotools.referencing.operation.matrix.XAffineTransform;
0082: import org.geotools.referencing.operation.transform.ConcatenatedTransform;
0083: import org.geotools.referencing.operation.transform.ProjectiveTransform;
0084: import org.geotools.renderer.GTRenderer;
0085: import org.geotools.renderer.RenderListener;
0086: import org.geotools.renderer.lite.gridcoverage2d.GridCoverageRenderer;
0087: import org.geotools.renderer.style.SLDStyleFactory;
0088: import org.geotools.renderer.style.Style2D;
0089: import org.geotools.styling.FeatureTypeStyle;
0090: import org.geotools.styling.LineSymbolizer;
0091: import org.geotools.styling.PointSymbolizer;
0092: import org.geotools.styling.PolygonSymbolizer;
0093: import org.geotools.styling.RasterSymbolizer;
0094: import org.geotools.styling.Rule;
0095: import org.geotools.styling.StyleAttributeExtractor;
0096: import org.geotools.styling.Symbolizer;
0097: import org.geotools.styling.TextSymbolizer;
0098: import org.geotools.util.NumberRange;
0099: import org.opengis.coverage.grid.GridCoverage;
0100: import org.opengis.coverage.grid.GridCoverageReader;
0101: import org.opengis.filter.Filter;
0102: import org.opengis.filter.FilterFactory;
0103: import org.opengis.filter.expression.PropertyName;
0104: import org.opengis.filter.spatial.BBOX;
0105: import org.opengis.parameter.GeneralParameterValue;
0106: import org.opengis.referencing.FactoryException;
0107: import org.opengis.referencing.crs.CoordinateReferenceSystem;
0108: import org.opengis.referencing.operation.CoordinateOperation;
0109: import org.opengis.referencing.operation.CoordinateOperationFactory;
0110: import org.opengis.referencing.operation.MathTransform;
0111: import org.opengis.referencing.operation.MathTransform2D;
0112: import org.opengis.referencing.operation.OperationNotFoundException;
0113: import org.opengis.referencing.operation.TransformException;
0114:
0115: import com.vividsolutions.jts.geom.Coordinate;
0116: import com.vividsolutions.jts.geom.Envelope;
0117: import com.vividsolutions.jts.geom.Geometry;
0118: import com.vividsolutions.jts.geom.GeometryCollection;
0119:
0120: /**
0121: * A streaming implementation of the GTRenderer interface.
0122: * <ul>
0123: * <li>The code is relatively simple to understand, so it can be used as a
0124: * simple example of an SLD compliant rendering code</li>
0125: * <li>Uses as little memory as possible</li>
0126: * </ul>
0127: * Use this class if you need a stateless renderer that provides low memory
0128: * footprint and decent rendering performance on the first call but don't need
0129: * good optimal performance on subsequent calls on the same data. Notice: for
0130: * the time being, this class doesn't support GridCoverage stylers, that will be
0131: * rendered using the non geophisics version of the GridCoverage, if available,
0132: * with the geophisics one, otherwise.
0133: *
0134: * <p>
0135: * At the moment the streaming renderer is not thread safe
0136: *
0137: * @author James Macgill
0138: * @author dblasby
0139: * @author jessie eichar
0140: * @author Simone Giannecchini
0141: * @author Andrea Aime
0142: * @author Alessio Fabiani
0143: *
0144: * @source $URL:
0145: * http://svn.geotools.org/geotools/trunk/gt/module/render/src/org/geotools/renderer/lite/StreamingRenderer.java $
0146: * @version $Id: StreamingRenderer.java 29443 2008-02-25 10:04:01Z jgarnett $
0147: */
0148: public final class StreamingRenderer implements GTRenderer {
0149:
0150: private final static int defaultMaxFiltersToSendToDatastore = 5; // default
0151:
0152: /**
0153: * Computes the scale as the ratio between map distances and real world distances,
0154: * assuming 90dpi and taking into consideration projection deformations and actual
0155: * earth shape. <br>
0156: * Use this method only when in need of accurate computation. Will break if the
0157: * data extent is outside of the currenct projection definition area.
0158: */
0159: public static final String SCALE_ACCURATE = "ACCURATE";
0160:
0161: /**
0162: * Very simple and lenient scale computation method that conforms to the OGC SLD
0163: * specification 1.0, page 26. <br>This method is quite approximative, but should
0164: * never break and ensure constant scale even on lat/lon unprojected maps (because
0165: * in that case scale is computed as if the area was along the equator no matter
0166: * what the real position is).
0167: */
0168: public static final String SCALE_OGC = "OGC";
0169:
0170: /** Tolerance used to compare doubles for equality */
0171: private static final double TOLERANCE = 1e-6;
0172:
0173: /** The logger for the rendering module. */
0174: private static final Logger LOGGER = org.geotools.util.logging.Logging
0175: .getLogger("org.geotools.rendering");
0176:
0177: int error = 0;
0178:
0179: /** Filter factory for creating bounding box filters */
0180: private final static FilterFactory filterFactory = CommonFactoryFinder
0181: .getFilterFactory(null);
0182:
0183: private final static PropertyName gridPropertyName = filterFactory
0184: .property("grid");
0185:
0186: private final static PropertyName paramsPropertyName = filterFactory
0187: .property("params");
0188:
0189: private final static PropertyName defaultGeometryPropertyName = filterFactory
0190: .property("");
0191:
0192: /**
0193: * Context which contains the layers and the bouning box which needs to be
0194: * rendered.
0195: */
0196: private MapContext context;
0197:
0198: /**
0199: * Flag which determines if the renderer is interactive or not. An
0200: * interactive renderer will return rather than waiting for time consuming
0201: * operations to complete (e.g. Image Loading). A non-interactive renderer
0202: * (e.g. a SVG or PDF renderer) will block for these operations.
0203: */
0204: private boolean interactive = true;
0205:
0206: /**
0207: * Flag which controls behaviour for applying affine transformation to the
0208: * graphics object. If true then the transform will be concatenated to the
0209: * existing transform. If false it will be replaced.
0210: */
0211: private boolean concatTransforms = false;
0212:
0213: /** Geographic map extent, eventually expanded to consider buffer area around the map */
0214: private ReferencedEnvelope mapExtent;
0215:
0216: /** Geographic map extent, as provided by the caller */
0217: private ReferencedEnvelope originalMapExtent;
0218:
0219: /** The size of the output area in output units. */
0220: private Rectangle screenSize;
0221:
0222: /**
0223: * This flag is set to false when starting rendering, and will be checked
0224: * during the rendering loop in order to make it stop forcefully
0225: */
0226: private boolean renderingStopRequested = false;
0227:
0228: /**
0229: * The ratio required to scale the features to be rendered so that they fit
0230: * into the output space.
0231: */
0232: private double scaleDenominator;
0233:
0234: /** Maximun displacement for generalization during rendering */
0235: private double generalizationDistance = 1.0;
0236:
0237: /** Factory that will resolve symbolizers into rendered styles */
0238: private SLDStyleFactory styleFactory = new SLDStyleFactory();
0239:
0240: protected LabelCache labelCache = new LabelCacheDefault();
0241:
0242: /** The painter class we use to depict shapes onto the screen */
0243: private StyledShapePainter painter = new StyledShapePainter(
0244: labelCache);
0245:
0246: private IndexedFeatureResults indexedFeatureResults;
0247:
0248: private ListenerList renderListeners = new ListenerList();
0249:
0250: private RenderingHints java2dHints;
0251:
0252: private boolean optimizedDataLoadingEnabledDEFAULT = false;
0253:
0254: private boolean memoryPreloadingEnabledDEFAULT = false;
0255:
0256: private int renderingBufferDEFAULT = 0;
0257:
0258: private String scaleComputationMethodDEFAULT = SCALE_OGC;
0259:
0260: /**
0261: * Text will be rendered using the usual calls gc.drawString/drawGlyphVector.
0262: * This is a little faster, and more consistent with how the platform renders
0263: * the text in other applications. The downside is that on most platform the label
0264: * and its eventual halo are not properly centered.
0265: */
0266: public static final String TEXT_RENDERING_STRING = "STRING";
0267:
0268: /**
0269: * Text will be rendered using the associated {@link GlyphVector} outline, that is, a {@link Shape}.
0270: * This ensures perfect centering between the text and the halo, but introduces more text aliasing.
0271: */
0272: public static final String TEXT_RENDERING_OUTLINE = "OUTLINE";
0273:
0274: /**
0275: * The text rendering method, either TEXT_RENDERING_OUTLINE or TEXT_RENDERING_STRING
0276: */
0277: public static final String TEXT_RENDERING_KEY = "textRenderingMethod";
0278: private String textRenderingModeDEFAULT = TEXT_RENDERING_STRING;
0279:
0280: public static final String LABEL_CACHE_KEY = "labelCache";
0281: public static final String FORCE_CRS_KEY = "forceCRS";
0282: public static final String DPI_KEY = "dpi";
0283: public static final String DECLARED_SCALE_DENOM_KEY = "declaredScaleDenominator";
0284: public static final String MEMORY_PRE_LOADING_KEY = "memoryPreloadingEnabled";
0285: public static final String OPTIMIZED_DATA_LOADING_KEY = "optimizedDataLoadingEnabled";
0286: public static final String SCALE_COMPUTATION_METHOD_KEY = "scaleComputationMethod";
0287:
0288: /**
0289: * "optimizedDataLoadingEnabled" - Boolean yes/no (see default optimizedDataLoadingEnabledDEFAULT)
0290: * "memoryPreloadingEnabled" - Boolean yes/no (see default memoryPreloadingEnabledDEFAULT)
0291: * "declaredScaleDenominator" - Double the value of the scale denominator to use by the renderer.
0292: * by default the value is calculated based on the screen size
0293: * and the displayed area of the map.
0294: * "dpi" - Integer number of dots per inch of the display 90 DPI is the default (as declared by OGC)
0295: * "forceCRS" - CoordinateReferenceSystem declares to the renderer that all layers are of the CRS declared in this hint
0296: * "labelCache" - Declares the label cache that will be used by the renderer.
0297: */
0298: private Map rendererHints = null;
0299:
0300: private AffineTransform worldToScreenTransform = null;
0301:
0302: private CoordinateReferenceSystem destinationCrs;
0303:
0304: private boolean canTransform;
0305:
0306: /**
0307: * Creates a new instance of LiteRenderer without a context. Use it only to
0308: * gain access to utility methods of this class or if you want to render
0309: * random feature collections instead of using the map context interface
0310: */
0311: public StreamingRenderer() {
0312:
0313: }
0314:
0315: /**
0316: * Sets the flag which controls behaviour for applying affine transformation
0317: * to the graphics object.
0318: *
0319: * @param flag
0320: * If true then the transform will be concatenated to the
0321: * existing transform. If false it will be replaced.
0322: */
0323: public void setConcatTransforms(boolean flag) {
0324: concatTransforms = flag;
0325: }
0326:
0327: /**
0328: * Flag which controls behaviour for applying affine transformation to the
0329: * graphics object.
0330: *
0331: * @return a boolean flag. If true then the transform will be concatenated
0332: * to the existing transform. If false it will be replaced.
0333: */
0334: public boolean getConcatTransforms() {
0335: return concatTransforms;
0336: }
0337:
0338: /**
0339: * adds a listener that responds to error events of feature rendered events.
0340: *
0341: * @see RenderListener
0342: *
0343: * @param listener
0344: * the listener to add.
0345: */
0346: public void addRenderListener(RenderListener listener) {
0347: renderListeners.add(listener);
0348: }
0349:
0350: /**
0351: * Removes a render listener.
0352: *
0353: * @see RenderListener
0354: *
0355: * @param listener
0356: * the listener to remove.
0357: */
0358: public void removeRenderListener(RenderListener listener) {
0359: renderListeners.remove(listener);
0360: }
0361:
0362: private void fireFeatureRenderedEvent(Object feature) {
0363: if (!(feature instanceof Feature)) {
0364: return;
0365: }
0366: final Object[] objects = renderListeners.getListeners();
0367: final int length = objects.length;
0368: RenderListener listener;
0369: for (int i = 0; i < length; i++) {
0370: listener = (RenderListener) objects[i];
0371: listener.featureRenderer((Feature) feature);
0372: }
0373: }
0374:
0375: private void fireErrorEvent(Exception e) {
0376: Object[] objects = renderListeners.getListeners();
0377: final int length = objects.length;
0378: RenderListener listener;
0379: for (int i = 0; i < length; i++) {
0380: listener = (RenderListener) objects[i];
0381: listener.errorOccurred(e);
0382: }
0383: }
0384:
0385: /**
0386: * If you call this method from another thread than the one that called
0387: * <code>paint</code> or <code>render</code> the rendering will be
0388: * forcefully stopped before termination
0389: */
0390: public void stopRendering() {
0391: renderingStopRequested = true;
0392: labelCache.stop();
0393: }
0394:
0395: /**
0396: * Renders features based on the map layers and their styles as specified in
0397: * the map context using <code>setContext</code>. <p/> This version of
0398: * the method assumes that the size of the output area and the
0399: * transformation from coordinates to pixels are known. The latter
0400: * determines the map scale. The viewport (the visible part of the map) will
0401: * be calculated internally.
0402: *
0403: * @param graphics
0404: * The graphics object to draw to.
0405: * @param paintArea
0406: * The size of the output area in output units (eg: pixels).
0407: * @param worldToScreen
0408: * A transform which converts World coordinates to Screen
0409: * coordinates.
0410: * @task Need to check if the Layer CoordinateSystem is different to the
0411: * BoundingBox rendering CoordinateSystem and if so, then transform
0412: * the coordinates.
0413: * @deprecated Use paint(Graphics2D graphics, Rectangle paintArea,
0414: * ReferencedEnvelope mapArea) or paint(Graphics2D graphics,
0415: * Rectangle paintArea, ReferencedEnvelope mapArea,
0416: * AffineTransform worldToScreen) instead.
0417: */
0418: public void paint(Graphics2D graphics, Rectangle paintArea,
0419: AffineTransform worldToScreen) {
0420: if (worldToScreen == null || paintArea == null) {
0421: LOGGER.info("renderer passed null arguments");
0422: return;
0423: } // Other arguments get checked later
0424: // First, create the bbox in real world coordinates
0425: Envelope mapArea;
0426: try {
0427: mapArea = RendererUtilities.createMapEnvelope(paintArea,
0428: worldToScreen);
0429: paint(graphics, paintArea, mapArea, worldToScreen);
0430: } catch (NoninvertibleTransformException e) {
0431: LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
0432: fireErrorEvent(new Exception(
0433: "Can't create pixel to world transform", e));
0434: }
0435: }
0436:
0437: /**
0438: * Renders features based on the map layers and their styles as specified in
0439: * the map context using <code>setContext</code>. <p/> This version of
0440: * the method assumes that the area of the visible part of the map and the
0441: * size of the output area are known. The transform between the two is
0442: * calculated internally.
0443: *
0444: * @param graphics
0445: * The graphics object to draw to.
0446: * @param paintArea
0447: * The size of the output area in output units (eg: pixels).
0448: * @param mapArea
0449: * the map's visible area (viewport) in map coordinates.
0450: * @deprecated Use paint(Graphics2D graphics, Rectangle paintArea,
0451: * ReferencedEnvelope mapArea) or paint(Graphics2D graphics,
0452: * Rectangle paintArea, ReferencedEnvelope mapArea,
0453: * AffineTransform worldToScreen) instead.
0454: */
0455: public void paint(Graphics2D graphics, Rectangle paintArea,
0456: Envelope mapArea) {
0457: if (mapArea == null || paintArea == null) {
0458: LOGGER.info("renderer passed null arguments");
0459: return;
0460: } // Other arguments get checked later
0461: paint(graphics, paintArea, mapArea, RendererUtilities
0462: .worldToScreenTransform(mapArea, paintArea));
0463: }
0464:
0465: /**
0466: * Renders features based on the map layers and their styles as specified in
0467: * the map context using <code>setContext</code>. <p/> This version of
0468: * the method assumes that the area of the visible part of the map and the
0469: * size of the output area are known. The transform between the two is
0470: * calculated internally.
0471: *
0472: * @param graphics
0473: * The graphics object to draw to.
0474: * @param paintArea
0475: * The size of the output area in output units (eg: pixels).
0476: * @param mapArea
0477: * the map's visible area (viewport) in map coordinates.
0478: */
0479: public void paint(Graphics2D graphics, Rectangle paintArea,
0480: ReferencedEnvelope mapArea) {
0481: if (mapArea == null || paintArea == null) {
0482: LOGGER.info("renderer passed null arguments");
0483: return;
0484: } // Other arguments get checked later
0485: paint(graphics, paintArea, mapArea, RendererUtilities
0486: .worldToScreenTransform(mapArea, paintArea));
0487: }
0488:
0489: /**
0490: * Renders features based on the map layers and their styles as specified in
0491: * the map context using <code>setContext</code>. <p/> This version of
0492: * the method assumes that paint area, enelope and worldToScreen transform
0493: * are already computed. Use this method to avoid recomputation. <b>Note
0494: * however that no check is performed that they are really in sync!<b/>
0495: *
0496: * @param graphics
0497: * The graphics object to draw to.
0498: * @param paintArea
0499: * The size of the output area in output units (eg: pixels).
0500: * @param mapArea
0501: * the map's visible area (viewport) in map coordinates.
0502: * @param worldToScreen
0503: * A transform which converts World coordinates to Screen
0504: * coordinates.
0505: * @deprecated Use paint(Graphics2D graphics, Rectangle paintArea,
0506: * ReferencedEnvelope mapArea) or paint(Graphics2D graphics,
0507: * Rectangle paintArea, ReferencedEnvelope mapArea,
0508: * AffineTransform worldToScreen) instead.
0509: */
0510: public void paint(Graphics2D graphics, Rectangle paintArea,
0511: Envelope mapArea, AffineTransform worldToScreen) {
0512: paint(graphics, paintArea, new ReferencedEnvelope(mapArea,
0513: context.getCoordinateReferenceSystem()), worldToScreen);
0514: }
0515:
0516: private double computeScale(ReferencedEnvelope envelope,
0517: Rectangle paintArea, Map hints) {
0518: if (getScaleComputationMethod().equals(SCALE_ACCURATE)) {
0519: try {
0520: return RendererUtilities.calculateScale(envelope,
0521: paintArea.width, paintArea.height, hints);
0522: } catch (Exception e) // probably either (1) no CRS (2) error xforming
0523: {
0524: LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e);
0525: }
0526: }
0527: return RendererUtilities.calculateOGCScale(envelope,
0528: paintArea.width, hints);
0529: }
0530:
0531: /**
0532: * Renders features based on the map layers and their styles as specified in
0533: * the map context using <code>setContext</code>. <p/> This version of
0534: * the method assumes that paint area, enelope and worldToScreen transform
0535: * are already computed. Use this method to avoid recomputation. <b>Note
0536: * however that no check is performed that they are really in sync!<b/>
0537: *
0538: * @param graphics
0539: * The graphics object to draw to.
0540: * @param paintArea
0541: * The size of the output area in output units (eg: pixels).
0542: * @param mapArea
0543: * the map's visible area (viewport) in map coordinates. Its
0544: * associate CRS is ALWAYS 2D
0545: * @param worldToScreen
0546: * A transform which converts World coordinates to Screen
0547: * coordinates.
0548: */
0549: public void paint(Graphics2D graphics, Rectangle paintArea,
0550: ReferencedEnvelope mapArea, AffineTransform worldToScreen) {
0551: // ////////////////////////////////////////////////////////////////////
0552: //
0553: // Check for null arguments, recompute missing ones if possible
0554: //
0555: // ////////////////////////////////////////////////////////////////////
0556: if (graphics == null || paintArea == null) {
0557: LOGGER.severe("renderer passed null arguments");
0558: throw new NullPointerException(
0559: "renderer passed null arguments");
0560: } else if (mapArea == null && paintArea == null) {
0561: LOGGER.severe("renderer passed null arguments");
0562: throw new NullPointerException(
0563: "renderer passed null arguments");
0564: } else if (mapArea == null) {
0565:
0566: LOGGER.severe("renderer passed null arguments");
0567: throw new NullPointerException(
0568: "renderer passed null arguments");
0569: } else if (worldToScreen == null) {
0570: worldToScreen = RendererUtilities.worldToScreenTransform(
0571: mapArea, paintArea);
0572: if (worldToScreen == null)
0573: return;
0574: }
0575:
0576: // ////////////////////////////////////////////////////////////////////
0577: //
0578: // Setting base information
0579: //
0580: // TODO the way this thing is built is a mess if you try to use it in a
0581: // multithreaded environment. I will fix this at the end.
0582: //
0583: // ////////////////////////////////////////////////////////////////////
0584: destinationCrs = mapArea.getCoordinateReferenceSystem();
0585: mapExtent = new ReferencedEnvelope(mapArea);
0586: this .screenSize = paintArea;
0587: this .worldToScreenTransform = worldToScreen;
0588: error = 0;
0589: if (java2dHints != null)
0590: graphics.setRenderingHints(java2dHints);
0591: // reset the abort flag
0592: renderingStopRequested = false;
0593:
0594: // ////////////////////////////////////////////////////////////////////
0595: //
0596: // Managing transformations , CRSs and scales
0597: //
0598: // If we are rendering to a component which has already set up some form
0599: // of transformation then we can concatenate our transformation to it.
0600: // An example of this is the ZoomPane component of the swinggui module.
0601: // ////////////////////////////////////////////////////////////////////
0602: if (concatTransforms) {
0603: AffineTransform atg = graphics.getTransform();
0604: atg.concatenate(worldToScreenTransform);
0605: worldToScreenTransform = atg;
0606: graphics.setTransform(worldToScreenTransform);
0607: }
0608:
0609: // compute scale according to the user specified method
0610: scaleDenominator = computeScale(mapArea, paintArea,
0611: rendererHints);
0612:
0613: //////////////////////////////////////////////////////////////////////
0614: //
0615: // Consider expanding the map extent so that a few more geometries
0616: // will be considered, in order to catch those outside of the rendering
0617: // bounds whose stroke is so thick that it countributes rendered area
0618: //
0619: //////////////////////////////////////////////////////////////////////
0620: int buffer = getRenderingBuffer();
0621: originalMapExtent = mapExtent;
0622: if (buffer > 0) {
0623: mapExtent = new ReferencedEnvelope(expandEnvelope(
0624: mapExtent, worldToScreen, buffer), mapExtent
0625: .getCoordinateReferenceSystem());
0626: }
0627:
0628: // ////////////////////////////////////////////////////////////////////
0629: //
0630: // Processing all the map layers in the context using the accompaining
0631: // styles
0632: //
0633: // ////////////////////////////////////////////////////////////////////
0634: final MapLayer[] layers = context.getLayers();
0635: labelCache.start();
0636: if (labelCache instanceof LabelCacheDefault) {
0637: boolean outlineEnabled = TEXT_RENDERING_OUTLINE
0638: .equals(getTextRenderingMethod());
0639: ((LabelCacheDefault) labelCache)
0640: .setOutlineRenderingEnabled(outlineEnabled);
0641: }
0642: final int layersNumber = layers.length;
0643: MapLayer currLayer;
0644: for (int i = 0; i < layersNumber; i++) // DJB: for each layer (ie. one
0645: {
0646: currLayer = layers[i];
0647:
0648: if (!currLayer.isVisible()) {
0649: // Only render layer when layer is visible
0650: continue;
0651: }
0652:
0653: if (renderingStopRequested) {
0654: return;
0655: }
0656: labelCache.startLayer(i + "");
0657: try {
0658:
0659: // extract the feature type stylers from the style object
0660: // and process them
0661: processStylers(graphics, currLayer,
0662: worldToScreenTransform, destinationCrs,
0663: mapExtent, screenSize, i + "");
0664: } catch (Throwable t) {
0665: LOGGER.log(Level.SEVERE, t.getLocalizedMessage(), t);
0666: fireErrorEvent(new Exception(new StringBuffer(
0667: "Exception rendering layer ").append(currLayer)
0668: .toString(), t));
0669: }
0670:
0671: labelCache.endLayer(i + "", graphics, screenSize);
0672: }
0673:
0674: labelCache.end(graphics, paintArea);
0675:
0676: if (LOGGER.isLoggable(Level.FINE))
0677: LOGGER.fine(new StringBuffer("Style cache hit ratio: ")
0678: .append(styleFactory.getHitRatio()).append(
0679: " , hits ").append(styleFactory.getHits())
0680: .append(", requests ").append(
0681: styleFactory.getRequests()).toString());
0682: if (error > 0) {
0683: LOGGER
0684: .warning(new StringBuffer(
0685: "Number of Errors during paint(Graphics2D, AffineTransform) = ")
0686: .append(error).toString());
0687: }
0688: }
0689:
0690: /**
0691: * Extends the provided {@link Envelope} in order to add the number of pixels
0692: * specified by <code>buffer</code> in every direction.
0693: *
0694: * @param envelope to extend.
0695: * @param worldToScreen by means of which doing the extension.
0696: * @param buffer to use for the extension.
0697: * @return an extende version of the provided {@link Envelope}.
0698: */
0699: private Envelope expandEnvelope(Envelope envelope,
0700: AffineTransform worldToScreen, int buffer) {
0701: assert buffer > 0;
0702: double bufferX = Math.abs(buffer * 1.0
0703: / XAffineTransform.getScaleX0(worldToScreen));
0704: double bufferY = Math.abs(buffer * 1.0
0705: / XAffineTransform.getScaleY0(worldToScreen));
0706: return new Envelope(envelope.getMinX() - bufferX, envelope
0707: .getMaxX()
0708: + bufferX, envelope.getMinY() - bufferY, envelope
0709: .getMaxY()
0710: + bufferY);
0711: }
0712:
0713: /**
0714: * Queries a given layer's <code>Source</code> instance to be rendered.
0715: * <p>
0716: * <em><strong>Note: This is proof-of-concept quality only!</strong> At
0717: * the moment the query is not filtered, that means all objects with all
0718: * fields are read from the datastore for every call to this method. This
0719: * method should work like
0720: * {@link #queryLayer(MapLayer, FeatureSource, FeatureType, LiteFeatureTypeStyle[], Envelope, CoordinateReferenceSystem, CoordinateReferenceSystem, Rectangle, GeometryAttributeType)}
0721: * and eventually replace it.</em>
0722: * </p>
0723: *
0724: * @param currLayer The actually processed layer for rendering
0725: * @param source Source to read data from
0726: */
0727: //TODO: Implement filtering for bbox and read in only the need attributes
0728: Collection queryLayer(MapLayer currLayer, CollectionSource source) {
0729:
0730: Collection results = null;
0731: DefaultQuery query = new DefaultQuery(DefaultQuery.ALL);
0732: Query definitionQuery;
0733:
0734: definitionQuery = currLayer.getQuery();
0735:
0736: if (definitionQuery != Query.ALL) {
0737: if (query == Query.ALL) {
0738: query = new DefaultQuery(definitionQuery);
0739: } else {
0740: query = new DefaultQuery(DataUtilities.mixQueries(
0741: definitionQuery, query, "liteRenderer"));
0742: }
0743: }
0744:
0745: results = source.content(query.getFilter());
0746:
0747: return results;
0748: }
0749:
0750: /**
0751: * Queries a given layer's features to be rendered based on the target
0752: * rendering bounding box.
0753: * <p>
0754: * If <code>optimizedDataLoadingEnabled</code> attribute has been set to
0755: * <code>true</code>, the following optimization will be performed in
0756: * order to limit the number of features returned:
0757: * <ul>
0758: * <li>Just the features whose geometric attributes lies within
0759: * <code>envelope</code> will be queried</li>
0760: * <li>The queried attributes will be limited to just those needed to
0761: * perform the rendering, based on the requiered geometric and non geometric
0762: * attributes found in the Layer's style rules</li>
0763: * <li>If a <code>Query</code> has been set to limit the resulting
0764: * layer's features, the final filter to obtain them will respect it. This
0765: * means that the bounding box filter and the Query filter will be combined,
0766: * also including maxFeatures from Query</li>
0767: * <li>At least that the layer's definition query explicitly says to
0768: * retrieve some attribute, no attributes will be requested from it, for
0769: * performance reassons. So it is desirable to not use a Query for filtering
0770: * a layer wich includes attributes. Note that including the attributes in
0771: * the result is not necessary for the query's filter to get properly
0772: * processed. </li>
0773: * </ul>
0774: * </p>
0775: * <p>
0776: * <b>NOTE </b>: This is an internal method and should only be called by
0777: * <code>paint(Graphics2D, Rectangle, AffineTransform)</code>. It is
0778: * package protected just to allow unit testing it.
0779: * </p>
0780: *
0781: * @param currLayer
0782: * the actually processing layer for renderition
0783: * @param schema
0784: * @param source
0785: * @param envelope
0786: * the spatial extent wich is the target area fo the rendering
0787: * process
0788: * @param destinationCrs
0789: * DOCUMENT ME!
0790: * @param sourceCrs
0791: * @param screenSize
0792: * @param geometryAttribute
0793: * @return the set of features resulting from <code>currLayer</code> after
0794: * quering its feature source
0795: * @throws IllegalFilterException
0796: * if something goes wrong constructing the bbox filter
0797: * @throws IOException
0798: * @throws IllegalAttributeException
0799: * @see MapLayer#setQuery(org.geotools.data.Query)
0800: */
0801: /*
0802: * Default visibility for testing purposes
0803: */
0804:
0805: FeatureCollection queryLayer(MapLayer currLayer,
0806: FeatureSource source, FeatureType schema,
0807: LiteFeatureTypeStyle[] styles, Envelope mapArea,
0808: CoordinateReferenceSystem mapCRS,
0809: CoordinateReferenceSystem featCrs, Rectangle screenSize,
0810: GeometryAttributeType geometryAttribute,
0811: AffineTransform worldToScreenTransform)
0812: throws IllegalFilterException, IOException,
0813: IllegalAttributeException {
0814: FeatureCollection results = null;
0815: DefaultQuery query = new DefaultQuery(DefaultQuery.ALL);
0816: Query definitionQuery;
0817: String[] attributes;
0818: AttributeType[] ats;
0819: final int length;
0820: Filter filter = null;
0821:
0822: // if map extent are not already expanded by a constant buffer, try to compute a layer
0823: // specific one based on stroke widths
0824: if (getRenderingBuffer() == 0) {
0825: int buffer = findRenderingBuffer(styles);
0826: if (buffer > 0) {
0827: mapArea = expandEnvelope(mapArea,
0828: worldToScreenTransform, buffer);
0829: LOGGER.fine("Expanding rendering area by " + buffer
0830: + " pixels to consider stroke width");
0831: }
0832: }
0833: ReferencedEnvelope envelope = new ReferencedEnvelope(mapArea,
0834: mapCRS);
0835: if (isOptimizedDataLoadingEnabled()) {
0836: // see what attributes we really need by exploring the styles
0837: // for testing purposes we have a null case -->
0838:
0839: if (styles == null) {
0840: ats = schema.getAttributeTypes();
0841: length = ats.length;
0842: attributes = new String[length];
0843: for (int t = 0; t < length; t++) {
0844: attributes[t] = ats[t].getName();
0845: }
0846: } else {
0847: attributes = findStyleAttributes(styles, schema);
0848: }
0849:
0850: try {
0851: // Then create the geometry filters. We have to create one for
0852: // each geometric attribute used during the rendering as the
0853: // feature may have more than one and the styles could use non
0854: // default geometric ones
0855: if (mapCRS != null && featCrs != null
0856: && !CRS.equalsIgnoreMetadata(featCrs, mapCRS)) {
0857: envelope = envelope.transform(featCrs, true, 10);
0858: }
0859:
0860: if (!isMemoryPreloadingEnabled()) {
0861: if (LOGGER.isLoggable(Level.FINE))
0862: LOGGER.fine("Querying layer "
0863: + schema.getTypeName() + " with bbox: "
0864: + envelope);
0865: filter = createBBoxFilters(schema, attributes,
0866: envelope);
0867: } else {
0868: filter = Filter.INCLUDE;
0869: }
0870:
0871: // now build the query using only the attributes and the
0872: // bounding box needed
0873: query = new DefaultQuery(schema.getTypeName());
0874: query.setFilter(filter);
0875: query.setPropertyNames(attributes);
0876: processRuleForQuery(styles, query);
0877:
0878: } catch (Exception e) {
0879: fireErrorEvent(new Exception("Error transforming bbox",
0880: e));
0881: canTransform = false;
0882: query = new DefaultQuery(schema.getTypeName());
0883: query.setPropertyNames(attributes);
0884: Envelope bounds = source.getBounds();
0885: if (bounds != null && envelope.intersects(bounds)) {
0886: LOGGER
0887: .fine(new StringBuffer(
0888: "Got a tranform exception while trying to de-project the current ")
0889: .append(
0890: "envelope, bboxs intersect therefore using envelope)")
0891: .toString());
0892: filter = null;
0893: filter = createBBoxFilters(schema, attributes,
0894: envelope);
0895: query.setFilter(filter);
0896: } else {
0897: LOGGER
0898: .fine(new StringBuffer(
0899: "Got a tranform exception while trying to de-project the current ")
0900: .append(
0901: "envelope, falling back on full data loading (no bbox query)")
0902: .toString());
0903: query.setFilter(Filter.INCLUDE);
0904: }
0905: processRuleForQuery(styles, query);
0906:
0907: }
0908: }
0909:
0910: // now, if a definition query has been established for this layer, be
0911: // sure to respect it by combining it with the bounding box one.
0912: definitionQuery = currLayer.getQuery();
0913:
0914: if (definitionQuery != Query.ALL) {
0915: if (query == Query.ALL) {
0916: query = new DefaultQuery(definitionQuery);
0917: } else {
0918: query = new DefaultQuery(DataUtilities.mixQueries(
0919: definitionQuery, query, "liteRenderer"));
0920: }
0921: }
0922: query.setCoordinateSystem(featCrs);
0923: Hints hints = new Hints(Hints.JTS_COORDINATE_SEQUENCE_FACTORY,
0924: new LiteCoordinateSequenceFactory(), Hints.FEATURE_2D,
0925: Boolean.TRUE);
0926: query.setHints(hints);
0927:
0928: if (isMemoryPreloadingEnabled()) {
0929: // TODO: attache a feature listener, we must erase the memory cache
0930: // if
0931: // anything changes in the data store
0932: if (indexedFeatureResults == null) {
0933: indexedFeatureResults = new IndexedFeatureResults(
0934: source.getFeatures(query));
0935: }
0936: indexedFeatureResults.setQueryBounds(envelope);
0937: results = indexedFeatureResults;
0938: } else { // insert a debug point here to check your query
0939: results = source.getFeatures(query);
0940: }
0941:
0942: // commenting this out for now, since it's causing connections to be
0943: // left open, since it's making a transaction that is never committed.
0944: // I think perhaps not getting FIDs should be set in client software
0945: // anyways See GEOS-631 and related issues. -ch
0946: /*
0947: * if ((source instanceof FeatureStore) && (doesntHaveFIDFilter(query))) {
0948: * try { FeatureStore fs = (FeatureStore) source;
0949: *
0950: * if (fs.getTransaction() == Transaction.AUTO_COMMIT) { // play it
0951: * safe, only update the transaction info if its an // auto_commit // it
0952: * logically possible that someone could be using the // Transaction to
0953: * do future (or past) processing. // We dont want to affect a future
0954: * Query // thats not possible with an AUTO_COMMIT so its safe.
0955: * Transaction t = new DefaultTransaction();
0956: * t.putProperty("doNotGetFIDS", Boolean.TRUE); fs.setTransaction(t); } }
0957: * catch (Exception e) { if (LOGGER.isLoggable(Level.WARNING))
0958: * LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e); // we // can
0959: * carry on, but report to // user } }
0960: */
0961: return results;
0962: }
0963:
0964: /**
0965: * JE: If there is a single rule "and" its filter together with the query's
0966: * filter and send it off to datastore. This will allow as more processing
0967: * to be done on the back end... Very useful if DataStore is a database.
0968: * Problem is that worst case each filter is ran twice. Next we will modify
0969: * it to find a "Common" filter between all rules and send that to the
0970: * datastore.
0971: *
0972: * DJB: trying to be smarter. If there are no "elseRules" and no rules w/o a
0973: * filter, then it makes sense to send them off to the Datastore We limit
0974: * the number of Filters sent off to the datastore, just because it could
0975: * get a bit rediculous. In general, for a database, if you can limit 10% of
0976: * the rows being returned you're probably doing quite well. The main
0977: * problem is when your filters really mean you're secretly asking for all
0978: * the data in which case sending the filters to the Datastore actually
0979: * costs you. But, databases are *much* faster at processing the Filters
0980: * than JAVA is and can use statistical analysis to do it.
0981: *
0982: * @param styles
0983: * @param q
0984: */
0985:
0986: private void processRuleForQuery(LiteFeatureTypeStyle[] styles,
0987: DefaultQuery q) {
0988: try {
0989:
0990: // first we check to see if there are >
0991: // "getMaxFiltersToSendToDatastore" rules
0992: // if so, then we dont do anything since no matter what there's too
0993: // many to send down.
0994: // next we check for any else rules. If we find any --> dont send
0995: // anything to Datastore
0996: // next we check for rules w/o filters. If we find any --> dont send
0997: // anything to Datastore
0998: //
0999: // otherwise, we're gold and can "or" together all the fiters then
1000: // AND it with the original filter.
1001: // ie. SELECT * FROM ... WHERE (the_geom && BBOX) AND (filter1 OR
1002: // filter2 OR filter3);
1003:
1004: final int maxFilters = getMaxFiltersToSendToDatastore();
1005: final ArrayList filtersToDS = new ArrayList();
1006: final int actualFilters = 0;
1007: final int stylesLength = styles.length;
1008: int styleElseRulesLength;
1009: int styleRulesLength;
1010: LiteFeatureTypeStyle style;
1011: int u = 0;
1012: Rule r;
1013: if (stylesLength > maxFilters) // there's at least one per
1014: return;
1015: for (int t = 0; t < stylesLength; t++) // look at each
1016: // featuretypestyle
1017: {
1018: style = styles[t];
1019: styleElseRulesLength = style.elseRules.length;
1020: styleRulesLength = style.ruleList.length;
1021: if (styleElseRulesLength > 0) // uh-oh has elseRule
1022: return;
1023: for (u = 0; u < styleRulesLength; u++) // look at each
1024: // rule in the
1025: // featuretypestyle
1026: {
1027: r = style.ruleList[u];
1028: if (r.getFilter() == null)
1029: return; // uh-oh has no filter (want all rows)
1030: filtersToDS.add(r.getFilter());
1031: }
1032: }
1033: if (actualFilters > maxFilters)
1034: return;
1035:
1036: org.opengis.filter.Filter ruleFiltersCombined;
1037: Filter newFilter;
1038: // We're GOLD -- OR together all the Rule's Filters
1039: if (filtersToDS.size() == 1) // special case of 1 filter
1040: {
1041: ruleFiltersCombined = (Filter) filtersToDS.get(0);
1042: } else {
1043: // build it up
1044: ruleFiltersCombined = (Filter) filtersToDS.get(0);
1045: final int size = filtersToDS.size();
1046: for (int t = 1; t < size; t++) // NOTE: dont
1047: // redo 1st one
1048: {
1049: newFilter = (Filter) filtersToDS.get(t);
1050: ruleFiltersCombined = filterFactory.or(
1051: ruleFiltersCombined, newFilter);
1052: }
1053: }
1054: // combine with the geometry filter (preexisting)
1055: ruleFiltersCombined = filterFactory.and(q.getFilter(),
1056: ruleFiltersCombined);
1057:
1058: // set the actual filter
1059: q.setFilter(ruleFiltersCombined);
1060: } catch (Exception e) {
1061: if (LOGGER.isLoggable(Level.WARNING))
1062: LOGGER.log(Level.SEVERE,
1063: "Could not send rules to datastore due to: "
1064: + e.getLocalizedMessage(), e);
1065: }
1066: }
1067:
1068: /**
1069: * find out the maximum number of filters we're going to send off to the
1070: * datastore. See processRuleForQuery() for details.
1071: *
1072: */
1073: private int getMaxFiltersToSendToDatastore() {
1074: try {
1075: Integer result = (Integer) rendererHints
1076: .get("maxFiltersToSendToDatastore");
1077: if (result == null)
1078: return defaultMaxFiltersToSendToDatastore; // default if not
1079: // present in hints
1080: return result.intValue();
1081:
1082: } catch (Exception e) {
1083: return defaultMaxFiltersToSendToDatastore;
1084: }
1085: }
1086:
1087: /**
1088: */
1089: private boolean isMemoryPreloadingEnabled() {
1090: if (rendererHints == null)
1091: return memoryPreloadingEnabledDEFAULT;
1092: Object result = null;
1093: try {
1094: result = rendererHints.get("memoryPreloadingEnabled");
1095: } catch (ClassCastException e) {
1096:
1097: }
1098: if (result == null)
1099: return memoryPreloadingEnabledDEFAULT;
1100: return ((Boolean) result).booleanValue();
1101: }
1102:
1103: /**
1104: * Returns an estimate of the rendering buffer needed to properly display this
1105: * layer taking into consideration the constant stroke sizes in the feature type
1106: * styles.
1107: *
1108: * @param styles
1109: * the feature type styles to be applied to the layer
1110: * @return an estimate of the buffer that should be used to properly display a layer
1111: * rendered with the specified styles
1112: */
1113: private int findRenderingBuffer(LiteFeatureTypeStyle[] styles) {
1114: final MetaBufferEstimator rbe = new MetaBufferEstimator();
1115:
1116: for (int t = 0; t < styles.length; t++) {
1117: final LiteFeatureTypeStyle lfts = styles[t];
1118: Rule[] rules = lfts.elseRules;
1119: for (int j = 0; j < rules.length; j++) {
1120: rbe.visit(rules[j]);
1121: }
1122: rules = lfts.ruleList;
1123: for (int j = 0; j < rules.length; j++) {
1124: rbe.visit(rules[j]);
1125: }
1126: }
1127:
1128: if (!rbe.isEstimateAccurate())
1129: LOGGER
1130: .warning("Assuming rendering buffer = "
1131: + rbe.getBuffer()
1132: + ", but estimation is not accurate, you may want to set a buffer manually");
1133: return rbe.getBuffer();
1134: }
1135:
1136: /**
1137: * Inspects the <code>MapLayer</code>'s style and retrieves it's needed
1138: * attribute names, returning at least the default geometry attribute name.
1139: *
1140: * @param layer
1141: * the <code>MapLayer</code> to determine the needed attributes
1142: * from
1143: * @param schema
1144: * the <code>layer</code>'s featuresource schema
1145: * @return the minimun set of attribute names needed to render
1146: * <code>layer</code>
1147: */
1148: private String[] findStyleAttributes(LiteFeatureTypeStyle[] styles,
1149: FeatureType schema) {
1150: final StyleAttributeExtractor sae = new StyleAttributeExtractor();
1151:
1152: LiteFeatureTypeStyle lfts;
1153: Rule[] rules;
1154: int rulesLength;
1155: final int length = styles.length;
1156: for (int t = 0; t < length; t++) {
1157: lfts = styles[t];
1158: rules = lfts.elseRules;
1159: rulesLength = rules.length;
1160: for (int j = 0; j < rulesLength; j++) {
1161: sae.visit(rules[j]);
1162: }
1163: rules = lfts.ruleList;
1164: rulesLength = rules.length;
1165: for (int j = 0; j < rulesLength; j++) {
1166: sae.visit(rules[j]);
1167: }
1168: }
1169:
1170: String[] ftsAttributes = sae.getAttributeNames();
1171:
1172: /*
1173: * DJB: this is an old comment - erase it soon (see geos-469 and below) -
1174: * we only add the default geometry if it was used.
1175: *
1176: * GR: if as result of sae.getAttributeNames() ftsAttributes already
1177: * contains geometry attribue names, they gets duplicated, wich produces
1178: * an error in AbstracDatastore when trying to create a derivate
1179: * FeatureType. So I'll add the default geometry only if it is not
1180: * already present, but: should all the geometric attributes be added by
1181: * default? I will add them, but don't really know what's the expected
1182: * behavior
1183: */
1184: List atts = new LinkedList(Arrays.asList(ftsAttributes));
1185: AttributeType[] attTypes = schema.getAttributeTypes();
1186: String attName;
1187:
1188: final int attTypesLength = attTypes.length;
1189: for (int i = 0; i < attTypesLength; i++) {
1190: attName = attTypes[i].getName();
1191:
1192: // DJB: This geometry check was commented out. I think it should
1193: // actually be back in or
1194: // you get ALL the attributes back, which isnt what you want.
1195: // ALX: For rasters I need even the "grid" attribute.
1196:
1197: // DJB:geos-469, we do not grab all the geometry columns.
1198: // for symbolizers, if a geometry is required it is either
1199: // explicitly named
1200: // ("<Geometry><PropertyName>the_geom</PropertyName></Geometry>")
1201: // or the default geometry is assumed (no <Geometry> element).
1202: // I've modified the style attribute extractor so it tracks if the
1203: // default geometry is used. So, we no longer add EVERY geometry
1204: // column to the query!!
1205:
1206: if ((attName.equalsIgnoreCase("grid"))
1207: && !atts.contains(attName)
1208: || (attName.equalsIgnoreCase("params"))
1209: && !atts.contains(attName)) {
1210: atts.add(attName);
1211: if (LOGGER.isLoggable(Level.FINE))
1212: LOGGER.fine("added attribute " + attName);
1213: }
1214: }
1215:
1216: try {
1217: // DJB:geos-469 if the default geometry was used in the style, we
1218: // need to grab it.
1219: if (sae.getDefaultGeometryUsed()
1220: && (!atts.contains(schema.getDefaultGeometry()
1221: .getName()))) {
1222: atts.add(schema.getDefaultGeometry().getName());
1223: }
1224: } catch (Exception e) {
1225: // might not be a geometry column. That will cause problems down the
1226: // road (why render a non-geometry layer)
1227: }
1228:
1229: ftsAttributes = new String[atts.size()];
1230: atts.toArray(ftsAttributes);
1231:
1232: return ftsAttributes;
1233: }
1234:
1235: /**
1236: * Creates the bounding box filters (one for each geometric attribute)
1237: * needed to query a <code>MapLayer</code>'s feature source to return
1238: * just the features for the target rendering extent
1239: *
1240: * @param schema
1241: * the layer's feature source schema
1242: * @param attributes
1243: * set of needed attributes
1244: * @param bbox
1245: * the expression holding the target rendering bounding box
1246: * @return an or'ed list of bbox filters, one for each geometric attribute
1247: * in <code>attributes</code>. If there are just one geometric
1248: * attribute, just returns its corresponding
1249: * <code>GeometryFilter</code>.
1250: * @throws IllegalFilterException
1251: * if something goes wrong creating the filter
1252: */
1253: private Filter createBBoxFilters(FeatureType schema,
1254: String[] attributes, Envelope bbox)
1255: throws IllegalFilterException {
1256: Filter filter = null;
1257: final int length = attributes.length;
1258: AttributeType attType;
1259:
1260: for (int j = 0; j < length; j++) {
1261: attType = schema.getAttributeType(attributes[j]);
1262:
1263: // DJB: added this for better error messages!
1264: if (attType == null) {
1265: if (LOGGER.isLoggable(Level.FINE))
1266: LOGGER.fine(new StringBuffer("Could not find '")
1267: .append(attributes[j]).append(
1268: "' in the FeatureType (").append(
1269: schema.getTypeName()).append(")")
1270: .toString());
1271: throw new IllegalFilterException(new StringBuffer(
1272: "Could not find '").append(
1273: attributes[j] + "' in the FeatureType (")
1274: .append(schema.getTypeName()).append(")")
1275: .toString());
1276: }
1277:
1278: if (attType instanceof GeometryAttributeType) {
1279: BBOX gfilter = filterFactory.bbox(attType.getName(),
1280: bbox.getMinX(), bbox.getMinY(), bbox.getMaxX(),
1281: bbox.getMaxY(), null);
1282:
1283: if (filter == null) {
1284: filter = gfilter;
1285: } else {
1286: filter = filterFactory.or(filter, gfilter);
1287: }
1288: }
1289: }
1290:
1291: return filter;
1292: }
1293:
1294: /**
1295: * Checks if a rule can be triggered at the current scale level
1296: *
1297: * @param r
1298: * The rule
1299: * @return true if the scale is compatible with the rule settings
1300: */
1301: private boolean isWithInScale(Rule r) {
1302: return ((r.getMinScaleDenominator() - TOLERANCE) <= scaleDenominator)
1303: && ((r.getMaxScaleDenominator() + TOLERANCE) > scaleDenominator);
1304: }
1305:
1306: /**
1307: * <p>Creates a list of <code>LiteFeatureTypeStyle</code>s with:
1308: * <ol type="a">
1309: * <li>out-of-scale rules removed</li>
1310: * <li>incompatible FeatureTypeStyles removed</li>
1311: * </ol>
1312: * </p>
1313: *
1314: * <p><em><strong>Note:</strong> This method has a lot of duplication with
1315: * {@link #createLiteFeatureTypeStyles(FeatureTypeStyle[], FeatureType, Graphics2D)}.
1316: * </em></p>
1317: *
1318: * @param featureStyles Styles to process
1319: * @param typeDescription The type description that has to be matched
1320: * @return ArrayList<LiteFeatureTypeStyle>
1321: */
1322: //TODO: Merge the two createLiteFeatureTypeStyles() methods
1323: private ArrayList createLiteFeatureTypeStyles(
1324: FeatureTypeStyle[] featureStyles, Object typeDescription,
1325: Graphics2D graphics) throws IOException {
1326: ArrayList result = new ArrayList();
1327:
1328: Rule[] rules;
1329: ArrayList ruleList = new ArrayList();
1330: ArrayList elseRuleList = new ArrayList();
1331: Rule r;
1332: LiteFeatureTypeStyle lfts;
1333: BufferedImage image;
1334: int numOfRules;
1335: int itemNumber = 0;
1336:
1337: final int length = featureStyles.length;
1338: for (int i = 0; i < length; i++) {
1339: FeatureTypeStyle fts = featureStyles[i];
1340:
1341: if (typeDescription == null
1342: || typeDescription.toString().indexOf(
1343: fts.getFeatureTypeName()) == -1)
1344: continue;
1345:
1346: // get applicable rules at the current scale
1347: rules = fts.getRules();
1348: ruleList = new ArrayList();
1349: elseRuleList = new ArrayList();
1350:
1351: numOfRules = rules.length;
1352: for (int j = 0; j < numOfRules; j++) {
1353: // getting rule
1354: r = rules[j];
1355:
1356: if (isWithInScale(r)) {
1357: if (r.hasElseFilter()) {
1358: elseRuleList.add(r);
1359: } else {
1360: ruleList.add(r);
1361: }
1362: }
1363: }
1364: if ((ruleList.size() == 0) && (elseRuleList.size() == 0))
1365: continue; // DJB: optimization - nothing to render, dont
1366: // do anything!!
1367:
1368: if (itemNumber == 0) // we can optimize this one!
1369: {
1370: lfts = new LiteFeatureTypeStyle(graphics, ruleList,
1371: elseRuleList);
1372: } else {
1373: image = graphics.getDeviceConfiguration()
1374: .createCompatibleImage(screenSize.width,
1375: screenSize.height,
1376: Transparency.TRANSLUCENT);
1377: lfts = new LiteFeatureTypeStyle(image, graphics
1378: .getTransform(), ruleList, elseRuleList,
1379: java2dHints);
1380: }
1381: result.add(lfts);
1382: itemNumber++;
1383:
1384: }
1385:
1386: return result;
1387: }
1388:
1389: /**
1390: * creates a list of LiteFeatureTypeStyles a) out-of-scale rules removed b)
1391: * incompatible FeatureTypeStyles removed
1392: *
1393: *
1394: * @param featureStylers
1395: * @param features
1396: * @throws Exception
1397: * @return ArrayList<LiteFeatureTypeStyle>
1398: */
1399: private ArrayList createLiteFeatureTypeStyles(
1400: FeatureTypeStyle[] featureStyles, FeatureType ftype,
1401: Graphics2D graphics) throws IOException {
1402: if (LOGGER.isLoggable(Level.FINE))
1403: LOGGER.fine("creating rules for scale denominator - "
1404: + NumberFormat.getNumberInstance().format(
1405: scaleDenominator));
1406: ArrayList result = new ArrayList();
1407:
1408: int itemNumber = 0;
1409:
1410: Rule[] rules;
1411: ArrayList ruleList = new ArrayList();
1412: ArrayList elseRuleList = new ArrayList();
1413: Rule r;
1414: LiteFeatureTypeStyle lfts;
1415: BufferedImage image;
1416: int numOfRules;
1417: FeatureTypeStyle fts;
1418: final String typeName = ftype.getTypeName();
1419: final int length = featureStyles.length;
1420: for (int i = 0; i < length; i++)
1421: // DJB: for each FeatureTypeStyle in the SLD (each on is drawn
1422: // indpendently)
1423: {
1424: // getting feature styles
1425: fts = featureStyles[i];
1426:
1427: if ((typeName != null)
1428: && (ftype.isDescendedFrom(null, fts
1429: .getFeatureTypeName()) || typeName
1430: .equalsIgnoreCase(fts.getFeatureTypeName()))) {
1431: // DJB: this FTS is compatible with this FT.
1432:
1433: // get applicable rules at the current scale
1434: rules = fts.getRules();
1435: ruleList = new ArrayList();
1436: elseRuleList = new ArrayList();
1437:
1438: numOfRules = rules.length;
1439: for (int j = 0; j < numOfRules; j++) {
1440: // getting rule
1441: r = rules[j];
1442:
1443: if (isWithInScale(r)) {
1444: if (r.hasElseFilter()) {
1445: elseRuleList.add(r);
1446: } else {
1447: ruleList.add(r);
1448: }
1449: }
1450: }
1451: if ((ruleList.size() == 0)
1452: && (elseRuleList.size() == 0))
1453: continue; // DJB: optimization - nothing to render, dont
1454: // do anything!!
1455:
1456: if (itemNumber == 0) // we can optimize this one!
1457: {
1458: lfts = new LiteFeatureTypeStyle(graphics, ruleList,
1459: elseRuleList);
1460: } else {
1461: image = graphics.getDeviceConfiguration()
1462: .createCompatibleImage(screenSize.width,
1463: screenSize.height,
1464: Transparency.TRANSLUCENT);
1465: lfts = new LiteFeatureTypeStyle(image, graphics
1466: .getTransform(), ruleList, elseRuleList,
1467: java2dHints);
1468: }
1469: result.add(lfts);
1470: itemNumber++;
1471:
1472: }
1473: }
1474:
1475: return result;
1476: }
1477:
1478: private Collection prepCollection(Collection collection,
1479: CoordinateReferenceSystem sourceCrs) throws IOException {
1480: if (collection instanceof FeatureCollection) {
1481: // will force content into correct CRS if needed
1482: return prepFeatureCollection(
1483: (FeatureCollection) collection, sourceCrs);
1484: }
1485: return collection;
1486: }
1487:
1488: /**
1489: * Prepair a FeatureCollection for display, this method formally ensured that a FeatureReader
1490: * produced the correct CRS and has now been updated to work with FeatureCollection.
1491: * <p>
1492: * What is really going on is the need to set up for reprojection; but *after* decimation has
1493: * occured.
1494: * </p>
1495: *
1496: * @param features
1497: * @param sourceCrs
1498: * @return FeatureCollection that produces results with the correct CRS
1499: */
1500: private FeatureCollection prepFeatureCollection(
1501: FeatureCollection features,
1502: CoordinateReferenceSystem sourceCrs) {
1503: // DJB: dont do reprojection here - do it after decimation
1504: // but we ensure that the reader is producing geometries with
1505: // the correct CRS
1506: // NOTE: it, by default, produces ones that are are tagged with
1507: // the CRS of the datastore, which
1508: // maybe incorrect.
1509: // The correct value is in sourceCrs.
1510:
1511: // this is the reader's CRS
1512: CoordinateReferenceSystem rCS = features.getSchema()
1513: .getDefaultGeometry().getCoordinateSystem();
1514:
1515: // sourceCrs == source's real SRS
1516: // if we need to recode the incoming geometries
1517:
1518: if (rCS != sourceCrs) // not both null or both EXACTLY the
1519: // same CRS object
1520: {
1521: if (sourceCrs != null) // dont re-tag to null, keep the
1522: // DataStore's CRS (this shouldnt
1523: // really happen)
1524: {
1525: // if the datastore is producing null CRS, we recode.
1526: // if the datastore's CRS != real CRS, then we recode
1527: if ((rCS == null)
1528: || !CRS.equalsIgnoreMetadata(rCS, sourceCrs)) {
1529: // need to retag the features
1530: try {
1531: return new ForceCoordinateSystemFeatureResults(
1532: features, sourceCrs);
1533: } catch (Exception ee) {
1534: LOGGER.log(Level.WARNING, ee
1535: .getLocalizedMessage(), ee);
1536: }
1537: }
1538: }
1539: }
1540: return features;
1541: }
1542:
1543: /**
1544: * Applies each feature type styler in turn to all of the features. This
1545: * perhaps needs some explanation to make it absolutely clear.
1546: * featureStylers[0] is applied to all features before featureStylers[1] is
1547: * applied. This can have important consequences as regards the painting
1548: * order.
1549: * <p>
1550: * In most cases, this is the desired effect. For example, all line features
1551: * may be rendered with a fat line and then a thin line. This produces a
1552: * 'cased' effect without any strange overlaps.
1553: * </p>
1554: * <p>
1555: * This method is internal and should only be called by render.
1556: * </p>
1557: * <p>
1558: * </p>
1559: *
1560: * @param graphics
1561: * DOCUMENT ME!
1562: * @param features
1563: * An array of features to be rendered
1564: * @param featureStylers
1565: * An array of feature stylers to be applied
1566: * @param at
1567: * DOCUMENT ME!
1568: * @param destinationCrs -
1569: * The destination CRS, or null if no reprojection is required
1570: * @param screenSize
1571: * @param layerId
1572: * @throws IOException
1573: * @throws IllegalAttributeException
1574: * @throws IllegalFilterException
1575: */
1576: final private void processStylers(final Graphics2D graphics,
1577: MapLayer currLayer, AffineTransform at,
1578: CoordinateReferenceSystem destinationCrs, Envelope mapArea,
1579: Rectangle screenSize, String layerId)
1580: throws IllegalFilterException, IOException,
1581: IllegalAttributeException {
1582:
1583: /*
1584: * DJB: changed this a wee bit so that it now does the layer query AFTER
1585: * it has evaluated the rules for scale inclusion. This makes it so that
1586: * geometry columns (and other columns) will not be queried unless they
1587: * are actually going to be required. see geos-469
1588: */
1589: // /////////////////////////////////////////////////////////////////////
1590: //
1591: // Preparing feature information and styles
1592: //
1593: // /////////////////////////////////////////////////////////////////////
1594: final FeatureTypeStyle[] featureStylers = currLayer.getStyle()
1595: .getFeatureTypeStyles();
1596:
1597: final FeatureSource featureSource = currLayer
1598: .getFeatureSource();
1599:
1600: final Collection result;
1601: final CoordinateReferenceSystem sourceCrs;
1602: final NumberRange scaleRange = new NumberRange(
1603: scaleDenominator, scaleDenominator);
1604: final ArrayList lfts;
1605:
1606: if (featureSource != null) {
1607: final FeatureType schema = featureSource.getSchema();
1608:
1609: final GeometryAttributeType geometryAttribute = schema
1610: .getDefaultGeometry();
1611: sourceCrs = geometryAttribute.getCoordinateSystem();
1612: if (LOGGER.isLoggable(Level.FINE)) {
1613: LOGGER.fine(new StringBuffer("processing ").append(
1614: featureStylers.length).append(" stylers for ")
1615: .append(
1616: currLayer.getFeatureSource()
1617: .getSchema().getTypeName())
1618: .toString());
1619: }
1620: // transformMap = new HashMap();
1621: lfts = createLiteFeatureTypeStyles(featureStylers, schema,
1622: graphics);
1623: if (lfts.size() == 0)
1624: return;
1625:
1626: LiteFeatureTypeStyle[] featureTypeStyleArray = (LiteFeatureTypeStyle[]) lfts
1627: .toArray(new LiteFeatureTypeStyle[lfts.size()]);
1628: // /////////////////////////////////////////////////////////////////////
1629: //
1630: // DJB: get a featureresults (so you can get a feature reader) for the
1631: // data
1632: //
1633: // /////////////////////////////////////////////////////////////////////
1634:
1635: result = queryLayer(currLayer, featureSource, schema,
1636: featureTypeStyleArray, mapArea, destinationCrs,
1637: sourceCrs, screenSize, geometryAttribute, at);
1638: } else {
1639: CollectionSource source = currLayer.getSource();
1640: result = queryLayer(currLayer, currLayer.getSource());
1641: sourceCrs = null;
1642: lfts = createLiteFeatureTypeStyles(featureStylers, source
1643: .describe(), graphics);
1644: }
1645:
1646: if (lfts.size() == 0)
1647: return; // nothing to do
1648:
1649: final Collection collection = prepCollection(result, sourceCrs);
1650: final Iterator iterator = collection.iterator();
1651: int n_lfts = lfts.size();
1652: final LiteFeatureTypeStyle[] fts_array = (LiteFeatureTypeStyle[]) lfts
1653: .toArray(new LiteFeatureTypeStyle[n_lfts]);
1654:
1655: try {
1656: RenderableFeature rf = new RenderableFeature(
1657: fts_array.length > 1);
1658: rf.setLayer(currLayer);
1659: int t = 0;
1660: while (!renderingStopRequested) { // loop exit condition tested inside try catch
1661: try {
1662: if (!iterator.hasNext()) {
1663: break;
1664: }
1665: rf.setFeature(iterator.next());
1666: for (t = 0; t < n_lfts; t++) {
1667: process(rf, fts_array[t], scaleRange, at,
1668: destinationCrs, layerId);
1669: // draw the content on the image(s)
1670: }
1671: } catch (Throwable tr) {
1672: LOGGER.log(Level.SEVERE, tr.getLocalizedMessage(),
1673: tr);
1674: fireErrorEvent(new Exception(
1675: "Error rendering feature", tr));
1676: }
1677: }
1678: } finally {
1679: if (collection instanceof ResourceCollection) {
1680: ResourceCollection resource = (ResourceCollection) collection;
1681: resource.close(iterator);
1682: }
1683: }
1684: // have to re-form the image now.
1685: // graphics.setTransform( new AffineTransform() );
1686: for (int t = 0; t < n_lfts; t++) {
1687: if (fts_array[t].myImage != null) // this is the case for the
1688: // first one (ie.
1689: // fts_array[t].graphics ==
1690: // graphics)
1691: {
1692: graphics.drawImage(fts_array[t].myImage, 0, 0, null);
1693: fts_array[t].myImage.flush();
1694: fts_array[t].graphics.dispose();
1695: }
1696: }
1697:
1698: }
1699:
1700: /**
1701: * @param rf
1702: * @param feature
1703: * @param style
1704: * @param layerId
1705: */
1706: final private void process(RenderableFeature rf,
1707: LiteFeatureTypeStyle style, Range scaleRange,
1708: AffineTransform at,
1709: CoordinateReferenceSystem destinationCrs, String layerId)
1710: throws TransformException, FactoryException {
1711: boolean doElse = true;
1712: Rule[] elseRuleList = style.elseRules;
1713: Rule[] ruleList = style.ruleList;
1714: Rule r;
1715: Filter filter;
1716: Symbolizer[] symbolizers;
1717: Graphics2D graphics = style.graphics;
1718: // applicable rules
1719: final int length = ruleList.length;
1720: for (int t = 0; t < length; t++) {
1721: r = ruleList[t];
1722: filter = r.getFilter();
1723:
1724: if ((filter == null) || filter.evaluate(rf.content)) {
1725: doElse = false;
1726: symbolizers = r.getSymbolizers();
1727: processSymbolizers(graphics, rf, symbolizers,
1728: scaleRange, at, destinationCrs, layerId);
1729: }
1730: }
1731:
1732: if (doElse) {
1733: final int elseLength = elseRuleList.length;
1734: for (int tt = 0; tt < elseLength; tt++) {
1735: r = elseRuleList[tt];
1736: symbolizers = r.getSymbolizers();
1737:
1738: processSymbolizers(graphics, rf, symbolizers,
1739: scaleRange, at, destinationCrs, layerId);
1740:
1741: }
1742: }
1743: }
1744:
1745: /**
1746: * Applies each of a set of symbolizers in turn to a given feature.
1747: * <p>
1748: * This is an internal method and should only be called by processStylers.
1749: * </p>
1750: * @param currLayer
1751: *
1752: * @param graphics
1753: * @param drawMe
1754: * The feature to be rendered
1755: * @param symbolizers
1756: * An array of symbolizers which actually perform the rendering.
1757: * @param scaleRange
1758: * The scale range we are working on... provided in order to make
1759: * the style factory happy
1760: * @param shape
1761: * @param destinationCrs
1762: * @param layerId
1763: * @throws TransformException
1764: * @throws FactoryException
1765: */
1766: final private void processSymbolizers(final Graphics2D graphics,
1767: final RenderableFeature drawMe,
1768: final Symbolizer[] symbolizers, Range scaleRange,
1769: AffineTransform at,
1770: CoordinateReferenceSystem destinationCrs, String layerId)
1771: throws TransformException, FactoryException {
1772: final int length = symbolizers.length;
1773: for (int m = 0; m < length; m++) {
1774:
1775: // /////////////////////////////////////////////////////////////////
1776: //
1777: // RASTER
1778: //
1779: // /////////////////////////////////////////////////////////////////
1780: final Symbolizer symbolizer = symbolizers[m];
1781: if (symbolizer instanceof RasterSymbolizer) {
1782: renderRaster(graphics, drawMe.content,
1783: (RasterSymbolizer) symbolizer, destinationCrs,
1784: scaleRange);
1785:
1786: } else {
1787:
1788: // /////////////////////////////////////////////////////////////////
1789: //
1790: // FEATURE
1791: //
1792: // /////////////////////////////////////////////////////////////////
1793: LiteShape2 shape = drawMe.getShape(symbolizer, at);
1794: if (shape == null)
1795: continue;
1796: if (symbolizer instanceof TextSymbolizer
1797: && drawMe.content instanceof Feature) {
1798: // TODO: double back and make labelCache work with Objects
1799: labelCache
1800: .put(layerId, (TextSymbolizer) symbolizer,
1801: (Feature) drawMe.content, shape,
1802: scaleRange);
1803: } else {
1804: Style2D style = styleFactory.createStyle(
1805: drawMe.content, symbolizer, scaleRange);
1806: painter.paint(graphics, shape, style,
1807: scaleDenominator);
1808: }
1809:
1810: }
1811: }
1812: fireFeatureRenderedEvent(drawMe.content);
1813: }
1814:
1815: /**
1816: * Renders a grid coverage on the device.
1817: *
1818: * @param graphics
1819: * DOCUMENT ME!
1820: * @param drawMe
1821: * the feature that contains the GridCoverage. The grid coverage
1822: * must be contained in the "grid" attribute
1823: * @param symbolizer
1824: * The raster symbolizer
1825: * @param scaleRange
1826: * @param world2Grid
1827: * @task make it follow the symbolizer
1828: */
1829: private void renderRaster(Graphics2D graphics, Object drawMe,
1830: RasterSymbolizer symbolizer,
1831: CoordinateReferenceSystem destinationCRS, Range scaleRange) {
1832: final Object grid = gridPropertyName.evaluate(drawMe);
1833: if (LOGGER.isLoggable(Level.FINE))
1834: LOGGER.fine(new StringBuffer(
1835: "rendering Raster for feature ").append(
1836: drawMe.toString()).append(" - ").append(grid)
1837: .toString());
1838:
1839: try {
1840: // /////////////////////////////////////////////////////////////////
1841: //
1842: // If the grid object is a reader we ask him to do its best for the
1843: // requested resolution, if it is a gridcoverage instead we have to
1844: // rely on the gridocerage renderer itself.
1845: //
1846: // /////////////////////////////////////////////////////////////////
1847: // METABUFFER SUPPORT
1848: // final GeneralEnvelope metaBufferedEnvelope=handleTileBordersArtifacts(mapExtent,java2dHints,this.worldToScreenTransform);
1849: final GridCoverageRenderer gcr = new GridCoverageRenderer(
1850: destinationCRS, originalMapExtent, screenSize,
1851: java2dHints);
1852:
1853: // //
1854: // It is a grid coverage
1855: // //
1856: if (grid instanceof GridCoverage)
1857: // METABUFFER SUPPORT
1858: // gcr.paint(graphics, (GridCoverage2D) grid, symbolizer, metaBufferedEnvelope);
1859: gcr.paint(graphics, (GridCoverage2D) grid, symbolizer);
1860: else if (grid instanceof GridCoverageReader) {
1861: // //
1862: // It is an AbstractGridCoverage2DReader, let's use parameters
1863: // if we have any supplied by a user.
1864: // //
1865: // first I created the correct ReadGeometry
1866: final Parameter readGG = new Parameter(
1867: AbstractGridFormat.READ_GRIDGEOMETRY2D);
1868: // METABUFFER SUPPORT
1869: // readGG.setValue(new GridGeometry2D(new GeneralGridRange(
1870: // screenSize), metaBufferedEnvelope));
1871: readGG.setValue(new GridGeometry2D(
1872: new GeneralGridRange(screenSize), mapExtent));
1873: final GridCoverageReader reader = (GridCoverageReader) grid;
1874: // then I try to get read parameters associated with this
1875: // coverage if there are any.
1876: final Object params = paramsPropertyName
1877: .evaluate(drawMe);
1878: final GridCoverage2D coverage;
1879: if (params != null) {
1880: // //
1881: //
1882: // Getting parameters to control how to read this coverage.
1883: // Remember to check to actually have them before forwarding
1884: // them to the reader.
1885: //
1886: // //
1887: GeneralParameterValue[] readParams = (GeneralParameterValue[]) params;
1888: final int length = readParams.length;
1889: if (length > 0) {
1890: // we have a valid number of parameters, let's check if
1891: // also have a READ_GRIDGEOMETRY2D. In such case we just
1892: // override it with the one we just build for this
1893: // request.
1894: final String name = AbstractGridFormat.READ_GRIDGEOMETRY2D
1895: .getName().toString();
1896: int i = 0;
1897: for (; i < length; i++)
1898: if (readParams[i].getDescriptor().getName()
1899: .toString().equalsIgnoreCase(name))
1900: break;
1901: // did we find anything?
1902: if (i < length) {
1903: //we found another READ_GRIDGEOMETRY2D, let's override it.
1904: ((Parameter) readParams[i])
1905: .setValue(readGG);
1906: coverage = (GridCoverage2D) reader
1907: .read(readParams);
1908: } else {
1909: // add the correct read geometry to the supplied
1910: // params since we did not find anything
1911: GeneralParameterValue[] readParams2 = new GeneralParameterValue[length + 1];
1912: System.arraycopy(readParams, 0,
1913: readParams2, 0, length);
1914: readParams2[length] = readGG;
1915: coverage = (GridCoverage2D) reader
1916: .read(readParams2);
1917: }
1918: } else
1919: // we have no parameters hence we just use the read grid
1920: // geometry to get a coverage
1921: coverage = (GridCoverage2D) reader
1922: .read(new GeneralParameterValue[] { readGG });
1923: } else {
1924: coverage = (GridCoverage2D) reader
1925: .read(new GeneralParameterValue[] { readGG });
1926: }
1927: // METABUFFER SUPPORT
1928: // gcr.paint(graphics, coverage, symbolizer,metaBufferedEnvelope);
1929: gcr.paint(graphics, coverage, symbolizer);
1930: }
1931: if (LOGGER.isLoggable(Level.FINE))
1932: LOGGER.fine("Raster rendered");
1933:
1934: } catch (FactoryException e) {
1935: LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e);
1936: fireErrorEvent(e);
1937: } catch (TransformException e) {
1938: LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e);
1939: fireErrorEvent(e);
1940: } catch (NoninvertibleTransformException e) {
1941: LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e);
1942: fireErrorEvent(e);
1943: } catch (IllegalArgumentException e) {
1944: LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e);
1945: fireErrorEvent(e);
1946: } catch (IOException e) {
1947: LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e);
1948: fireErrorEvent(e);
1949: }
1950:
1951: }
1952:
1953: /**
1954: * This method captures a first attempt to handle in a coherent way the problem of having artifacts at the
1955: * borders of drown tiles when using Tiling Clients like OpenLayers with higher order interpolation.
1956: *
1957: * @param mapExtent to draw on.
1958: * @param java2dHints to control the drawing process.
1959: * @param worldToScreen transformation that maps onto the screen.
1960: * @return a {@link GeneralEnvelope} that might be expanded in order to avoid
1961: * artifacts at the borders
1962: */
1963: private GeneralEnvelope handleTileBordersArtifacts(
1964: ReferencedEnvelope mapExtent, RenderingHints java2dHints,
1965: AffineTransform worldToScreen) {
1966: // /////////////////////////////////////////////////////////////////////
1967: //
1968: // Check if interpolation is required.
1969: //
1970: // /////////////////////////////////////////////////////////////////////
1971: if (!java2dHints.containsKey(JAI.KEY_INTERPOLATION)) {
1972: if (LOGGER.isLoggable(Level.FINE))
1973: LOGGER
1974: .fine("Unable to find interpolation for this request.");
1975: return new GeneralEnvelope(mapExtent);
1976: }
1977: final Interpolation interp = (Interpolation) java2dHints
1978: .get(JAI.KEY_INTERPOLATION);
1979:
1980: // /////////////////////////////////////////////////////////////////////
1981: //
1982: // Ok, we have an interpolation, let's decide if we have to extend the
1983: // requested area accordingly.
1984: //
1985: // /////////////////////////////////////////////////////////////////////
1986: int buffer = 0;
1987: if (interp instanceof InterpolationNearest) {
1988: if (LOGGER.isLoggable(Level.FINE))
1989: LOGGER
1990: .fine("Interpolation Nearest no need for extending.");
1991: return new GeneralEnvelope(mapExtent);
1992:
1993: }
1994: if (interp instanceof InterpolationBilinear) {
1995: if (LOGGER.isLoggable(Level.FINE))
1996: LOGGER
1997: .fine("Interpolation Bilinear extending.by 1 pixel at least");
1998: buffer = 10;
1999: } else if (interp instanceof InterpolationBicubic) {
2000: if (LOGGER.isLoggable(Level.FINE))
2001: LOGGER
2002: .fine("Interpolation Bicubic extending.by 2 pixel at least");
2003: buffer = 30;
2004: }
2005: if (buffer <= 0) {
2006: return new GeneralEnvelope(mapExtent);
2007: }
2008:
2009: // /////////////////////////////////////////////////////////////////////
2010: //
2011: // Doing the extension
2012: //
2013: // /////////////////////////////////////////////////////////////////////
2014: final Envelope tempEnv = expandEnvelope(mapExtent,
2015: worldToScreen, buffer);
2016: final GeneralEnvelope newEnv = new GeneralEnvelope(
2017: new double[] { tempEnv.getMinX(), tempEnv.getMinY(), },
2018: new double[] { tempEnv.getMaxX(), tempEnv.getMaxY() });
2019: newEnv.setCoordinateReferenceSystem(mapExtent
2020: .getCoordinateReferenceSystem());
2021: return newEnv;
2022:
2023: }
2024:
2025: /**
2026: * Finds the geometric attribute requested by the symbolizer
2027: *
2028: * @param drawMe
2029: * The feature
2030: * @param s
2031: *
2032: * /** Finds the geometric attribute requested by the symbolizer
2033: *
2034: * @param drawMe
2035: * The feature
2036: * @param s
2037: * The symbolizer
2038: * @return The geometry requested in the symbolizer, or the default geometry
2039: * if none is specified
2040: */
2041: private com.vividsolutions.jts.geom.Geometry findGeometry(
2042: Object drawMe, Symbolizer s) {
2043: PropertyName geomName = getGeometryPropertyName(s);
2044:
2045: // get the geometry
2046: Geometry geom;
2047: if (geomName == null) {
2048: if (drawMe instanceof Feature)
2049: geom = ((Feature) drawMe).getDefaultGeometry();
2050: else
2051: geom = (Geometry) defaultGeometryPropertyName.evaluate(
2052: drawMe, Geometry.class);
2053: } else {
2054: geom = (Geometry) geomName.evaluate(drawMe, Geometry.class);
2055: }
2056:
2057: // if the symbolizer is a point symbolizer generate a suitable location
2058: // to place the
2059: // point in order to avoid recomputing that location at each rendering
2060: // step
2061: if (s instanceof PointSymbolizer) {
2062: geom = getCentroid(geom); // djb: major simpificatioN
2063: }
2064: return geom;
2065: }
2066:
2067: /**
2068: * Finds the centroid of the input geometry if input = point, line, polygon
2069: * --> return a point that represents the centroid of that geom if input =
2070: * geometry collection --> return a multipoint that represents the centoid
2071: * of each sub-geom
2072: *
2073: * @param g
2074: */
2075: private Geometry getCentroid(Geometry g) {
2076: if (g instanceof GeometryCollection) {
2077: final GeometryCollection gc = (GeometryCollection) g;
2078: final Coordinate[] pts = new Coordinate[gc
2079: .getNumGeometries()];
2080: final int length = gc.getNumGeometries();
2081: for (int t = 0; t < length; t++) {
2082: pts[t] = gc.getGeometryN(t).getCentroid()
2083: .getCoordinate();
2084: }
2085: return g.getFactory().createMultiPoint(pts);
2086: } else if (g != null) {
2087: return g.getCentroid();
2088: }
2089: return null;
2090: }
2091:
2092: /**
2093: * Finds the geometric attribute coordinate reference system.
2094: * @param drawMe2
2095: *
2096: * @param f The feature
2097: * @param s The symbolizer
2098: * @return The geometry requested in the symbolizer, or the default geometry if none is specified
2099: */
2100: private org.opengis.referencing.crs.CoordinateReferenceSystem findGeometryCS(
2101: MapLayer currLayer, Object drawMe, Symbolizer s) {
2102:
2103: if (drawMe instanceof Feature) {
2104: Feature f = (Feature) drawMe;
2105:
2106: PropertyName propertyName = getGeometryPropertyName(s);
2107: String geomName = propertyName != null ? propertyName
2108: .getPropertyName() : null;
2109: if (geomName == null || "".equals(geomName)) {
2110: FeatureType schema = f.getFeatureType();
2111: GeometryAttributeType geom = schema
2112: .getDefaultGeometry();
2113: return geom.getCoordinateSystem();
2114: } else {
2115: FeatureType schema = f.getFeatureType();
2116: GeometryAttributeType geom = (GeometryAttributeType) schema
2117: .getAttributeType(geomName);
2118: return geom.getCoordinateSystem();
2119: }
2120: } else if (currLayer.getSource() != null) {
2121: return currLayer.getSource().getInfo().getCRS();
2122: }
2123:
2124: return null;
2125: }
2126:
2127: private PropertyName getGeometryPropertyName(Symbolizer s) {
2128: String geomName = null;
2129:
2130: // TODO: fix the styles, the getGeometryPropertyName should probably be
2131: // moved into an
2132: // interface...
2133: if (s instanceof PolygonSymbolizer) {
2134: geomName = ((PolygonSymbolizer) s)
2135: .getGeometryPropertyName();
2136: } else if (s instanceof PointSymbolizer) {
2137: geomName = ((PointSymbolizer) s).getGeometryPropertyName();
2138: } else if (s instanceof LineSymbolizer) {
2139: geomName = ((LineSymbolizer) s).getGeometryPropertyName();
2140: } else if (s instanceof TextSymbolizer) {
2141: geomName = ((TextSymbolizer) s).getGeometryPropertyName();
2142: }
2143:
2144: if (geomName == null) {
2145: return null;
2146: }
2147: return filterFactory.property(geomName);
2148: }
2149:
2150: /**
2151: * Getter for property interactive.
2152: *
2153: * @return Value of property interactive.
2154: */
2155: public boolean isInteractive() {
2156: return interactive;
2157: }
2158:
2159: /**
2160: * Sets the interactive status of the renderer. An interactive renderer
2161: * won't wait for long image loading, preferring an alternative mark instead
2162: *
2163: * @param interactive
2164: * new value for the interactive property
2165: */
2166: public void setInteractive(boolean interactive) {
2167: this .interactive = interactive;
2168: }
2169:
2170: /**
2171: * <p>
2172: * Returns true if the optimized data loading is enabled, false otherwise.
2173: * </p>
2174: * <p>
2175: * When optimized data loading is enabled, lite renderer will try to load
2176: * only the needed feature attributes (according to styles) and to load only
2177: * the features that are in (or overlaps with)the bounding box requested for
2178: * painting
2179: * </p>
2180: *
2181: */
2182: private boolean isOptimizedDataLoadingEnabled() {
2183: if (rendererHints == null)
2184: return optimizedDataLoadingEnabledDEFAULT;
2185: Object result = null;
2186: try {
2187: result = rendererHints.get("optimizedDataLoadingEnabled");
2188: } catch (ClassCastException e) {
2189:
2190: }
2191: if (result == null)
2192: return optimizedDataLoadingEnabledDEFAULT;
2193: return ((Boolean) result).booleanValue();
2194: }
2195:
2196: /**
2197: * <p>
2198: * Returns the rendering buffer, a measure in pixels used to expand the geometry search area
2199: * enough to capture the geometries that do stay outside of the current rendering bounds but
2200: * do affect them because of their large strokes (labels and graphic symbols are handled
2201: * differently, see the label chache).
2202: * </p>
2203: *
2204: */
2205: private int getRenderingBuffer() {
2206: if (rendererHints == null)
2207: return renderingBufferDEFAULT;
2208: Number result = (Number) rendererHints.get("renderingBuffer");
2209: if (result == null)
2210: return renderingBufferDEFAULT;
2211: return result.intValue();
2212: }
2213:
2214: /**
2215: * <p>
2216: * Returns scale computation algorithm to be used.
2217: * </p>
2218: *
2219: */
2220: private String getScaleComputationMethod() {
2221: if (rendererHints == null)
2222: return scaleComputationMethodDEFAULT;
2223: String result = (String) rendererHints
2224: .get("scaleComputationMethod");
2225: if (result == null)
2226: return scaleComputationMethodDEFAULT;
2227: return result;
2228: }
2229:
2230: /**
2231: * Returns the text rendering method
2232: */
2233: private String getTextRenderingMethod() {
2234: if (rendererHints == null)
2235: return textRenderingModeDEFAULT;
2236: String result = (String) rendererHints.get(TEXT_RENDERING_KEY);
2237: if (result == null)
2238: return textRenderingModeDEFAULT;
2239: return result;
2240: }
2241:
2242: /**
2243: * Returns the generalization distance in the screen space.
2244: *
2245: */
2246: public double getGeneralizationDistance() {
2247: return generalizationDistance;
2248: }
2249:
2250: /**
2251: * <p>
2252: * Sets the generalizazion distance in the screen space.
2253: * </p>
2254: * <p>
2255: * Default value is 1, meaning that two subsequent points are collapsed to
2256: * one if their on screen distance is less than one pixel
2257: * </p>
2258: * <p>
2259: * Set the distance to 0 if you don't want any kind of generalization
2260: * </p>
2261: *
2262: * @param d
2263: */
2264: public void setGeneralizationDistance(double d) {
2265: generalizationDistance = d;
2266: }
2267:
2268: /*
2269: * (non-Javadoc)
2270: *
2271: * @see org.geotools.renderer.GTRenderer#setJava2DHints(java.awt.RenderingHints)
2272: */
2273: public void setJava2DHints(RenderingHints hints) {
2274: this .java2dHints = hints;
2275: }
2276:
2277: /*
2278: * (non-Javadoc)
2279: *
2280: * @see org.geotools.renderer.GTRenderer#getJava2DHints()
2281: */
2282: public RenderingHints getJava2DHints() {
2283: return java2dHints;
2284: }
2285:
2286: public void setRendererHints(Map hints) {
2287: if (hints != null && hints.containsKey(LABEL_CACHE_KEY)) {
2288: LabelCache cache = (LabelCache) hints.get(LABEL_CACHE_KEY);
2289: if (cache == null)
2290: throw new NullPointerException(
2291: "Label_Cache_Hint has a null value for the labelcache");
2292:
2293: this .labelCache = cache;
2294: this .painter = new StyledShapePainter(cache);
2295: }
2296: rendererHints = hints;
2297: }
2298:
2299: /*
2300: * (non-Javadoc)
2301: *
2302: * @see org.geotools.renderer.GTRenderer#getRendererHints()
2303: */
2304: public Map getRendererHints() {
2305: return rendererHints;
2306: }
2307:
2308: /*
2309: * (non-Javadoc)
2310: *
2311: * @see org.geotools.renderer.GTRenderer#setContext(org.geotools.map.MapContext)
2312: */
2313: public void setContext(MapContext context) {
2314: this .context = context;
2315: }
2316:
2317: /*
2318: * (non-Javadoc)
2319: *
2320: * @see org.geotools.renderer.GTRenderer#getContext()
2321: */
2322: public MapContext getContext() {
2323: return context;
2324: }
2325:
2326: public boolean isCanTransform() {
2327: return canTransform;
2328: }
2329:
2330: public static MathTransform getMathTransform(
2331: CoordinateReferenceSystem sourceCRS,
2332: CoordinateReferenceSystem destCRS) {
2333: try {
2334: return CRS.findMathTransform(sourceCRS, destCRS, true);
2335: } catch (OperationNotFoundException e) {
2336: LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
2337:
2338: } catch (FactoryException e) {
2339: LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
2340: }
2341: return null;
2342: }
2343:
2344: private class RenderableFeature {
2345: Object content;
2346: private MapLayer layer;
2347: private IdentityHashMap symbolizerAssociationHT = new IdentityHashMap(); // associate a value
2348: private List geometries = new ArrayList();
2349: private List shapes = new ArrayList();
2350: private boolean multiLayerRendering;
2351: private boolean clone;
2352: private HashMap decimators = new HashMap();
2353:
2354: public RenderableFeature(boolean multiLayerRendering) {
2355: this .multiLayerRendering = multiLayerRendering;
2356: }
2357:
2358: public void setLayer(MapLayer layer) {
2359: this .layer = layer;
2360: this .clone = !layer.getFeatureSource().getSupportedHints()
2361: .contains(Hints.FEATURE_DETACHED);
2362: }
2363:
2364: public void setFeature(Object feature) {
2365: this .content = feature;
2366: geometries.clear();
2367: shapes.clear();
2368: }
2369:
2370: public LiteShape2 getShape(Symbolizer symbolizer,
2371: AffineTransform at) throws FactoryException {
2372: Geometry g = findGeometry(content, symbolizer); // pulls the geometry
2373:
2374: if (g == null)
2375: return null;
2376:
2377: SymbolizerAssociation sa = (SymbolizerAssociation) symbolizerAssociationHT
2378: .get(symbolizer);
2379: MathTransform2D transform = null;
2380: if (sa == null) {
2381: sa = new SymbolizerAssociation();
2382: sa.setCRS(findGeometryCS(layer, content, symbolizer));
2383: try {
2384: if (sa.crs == null
2385: || CRS.equalsIgnoreMetadata(sa.crs,
2386: destinationCrs))
2387: transform = null;
2388: else
2389: transform = (MathTransform2D) StreamingRenderer
2390: .getMathTransform(sa.crs,
2391: destinationCrs);
2392: if (transform != null && !transform.isIdentity()) {
2393: transform = (MathTransform2D) ConcatenatedTransform
2394: .create(transform, ProjectiveTransform
2395: .create(at));
2396:
2397: } else {
2398: transform = (MathTransform2D) ProjectiveTransform
2399: .create(at);
2400: }
2401: } catch (Exception e) {
2402: // fall through
2403: LOGGER.log(Level.WARNING, e.getLocalizedMessage(),
2404: e);
2405: }
2406: sa.setXform(transform);
2407: symbolizerAssociationHT.put(symbolizer, sa);
2408: }
2409:
2410: // some shapes may be too close to projection boundaries to
2411: // get transformed, try to be lenient
2412: try {
2413: return getTransformedShape(g, sa.getXform());
2414: } catch (TransformException te) {
2415: LOGGER.log(Level.FINE, te.getLocalizedMessage(), te);
2416: fireErrorEvent(te);
2417: return null;
2418: } catch (AssertionError ae) {
2419: LOGGER.log(Level.FINE, ae.getLocalizedMessage(), ae);
2420: fireErrorEvent(new RuntimeException(ae));
2421: return null;
2422: }
2423: }
2424:
2425: private final LiteShape2 getTransformedShape(Geometry g,
2426: MathTransform2D transform) throws TransformException,
2427: FactoryException {
2428: for (int i = 0; i < geometries.size(); i++) {
2429: if (geometries.get(i) == g)
2430: return (LiteShape2) shapes.get(i);
2431: }
2432: LiteShape2 shape = new LiteShape2(g, transform,
2433: getDecimator(transform), false, clone);
2434: geometries.add(g);
2435: shapes.add(shape);
2436: return shape;
2437: }
2438:
2439: /**
2440: * @throws org.opengis.referencing.operation.NoninvertibleTransformException
2441: */
2442: private Decimator getDecimator(MathTransform2D mathTransform)
2443: throws org.opengis.referencing.operation.NoninvertibleTransformException {
2444: Decimator decimator = (Decimator) decimators
2445: .get(mathTransform);
2446: if (decimator == null) {
2447: if (mathTransform != null
2448: && !mathTransform.isIdentity())
2449: decimator = new Decimator(mathTransform.inverse(),
2450: screenSize);
2451: else
2452: decimator = new Decimator(null, screenSize);
2453:
2454: decimators.put(mathTransform, decimator);
2455: }
2456: return decimator;
2457: }
2458: }
2459: }
|