0001: /*
0002: * Geotools2 - OpenSource mapping toolkit
0003: * http://geotools.org
0004: * (C) 2002, 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;
0009: * version 2.1 of the License.
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: */
0017: package org.geotools.renderer.shape;
0018:
0019: import java.awt.Graphics2D;
0020: import java.awt.Rectangle;
0021: import java.awt.RenderingHints;
0022: import java.awt.Shape;
0023: import java.awt.font.GlyphVector;
0024: import java.awt.geom.AffineTransform;
0025: import java.awt.geom.NoninvertibleTransformException;
0026: import java.io.File;
0027: import java.io.IOException;
0028: import java.net.URL;
0029: import java.util.ArrayList;
0030: import java.util.Collections;
0031: import java.util.HashMap;
0032: import java.util.HashSet;
0033: import java.util.Iterator;
0034: import java.util.List;
0035: import java.util.Map;
0036: import java.util.Set;
0037: import java.util.logging.Level;
0038: import java.util.logging.Logger;
0039:
0040: import javax.media.jai.util.Range;
0041:
0042: import org.geotools.data.DataStore;
0043: import org.geotools.data.DefaultQuery;
0044: import org.geotools.data.Diff;
0045: import org.geotools.data.FIDReader;
0046: import org.geotools.data.FeatureStore;
0047: import org.geotools.data.Query;
0048: import org.geotools.data.Transaction;
0049: import org.geotools.data.TransactionStateDiff;
0050: import org.geotools.data.shapefile.ShapefileDataStore;
0051: import org.geotools.data.shapefile.ShapefileRendererUtil;
0052: import org.geotools.data.shapefile.dbf.DbaseFileHeader;
0053: import org.geotools.data.shapefile.dbf.DbaseFileReader;
0054: import org.geotools.data.shapefile.dbf.IndexedDbaseFileReader;
0055: import org.geotools.data.shapefile.shp.ShapeType;
0056: import org.geotools.data.shapefile.shp.ShapefileReader;
0057: import org.geotools.data.shapefile.shp.ShapefileReader.Record;
0058: import org.geotools.feature.AttributeType;
0059: import org.geotools.feature.Feature;
0060: import org.geotools.feature.FeatureType;
0061: import org.geotools.feature.FeatureTypeBuilder;
0062: import org.geotools.feature.GeometryAttributeType;
0063: import org.geotools.feature.SchemaException;
0064: import org.geotools.filter.FilterAttributeExtractor;
0065: import org.geotools.filter.Filters;
0066: import org.geotools.geometry.jts.Decimator;
0067: import org.geotools.geometry.jts.JTS;
0068: import org.geotools.geometry.jts.LiteCoordinateSequenceFactory;
0069: import org.geotools.geometry.jts.LiteShape2;
0070: import org.geotools.geometry.jts.ReferencedEnvelope;
0071: import org.geotools.index.quadtree.StoreException;
0072: import org.geotools.map.DefaultMapContext;
0073: import org.geotools.map.MapContext;
0074: import org.geotools.map.MapLayer;
0075: import org.geotools.referencing.CRS;
0076: import org.geotools.referencing.ReferencingFactoryFinder;
0077: import org.geotools.referencing.crs.DefaultGeographicCRS;
0078: import org.geotools.referencing.operation.matrix.GeneralMatrix;
0079: import org.geotools.renderer.GTRenderer;
0080: import org.geotools.renderer.RenderListener;
0081: import org.geotools.renderer.lite.LabelCache;
0082: import org.geotools.renderer.lite.LabelCacheDefault;
0083: import org.geotools.renderer.lite.ListenerList;
0084: import org.geotools.renderer.lite.RendererUtilities;
0085: import org.geotools.renderer.lite.StreamingRenderer;
0086: import org.geotools.renderer.style.SLDStyleFactory;
0087: import org.geotools.renderer.style.Style2D;
0088: import org.geotools.styling.FeatureTypeStyle;
0089: import org.geotools.styling.LineSymbolizer;
0090: import org.geotools.styling.PointSymbolizer;
0091: import org.geotools.styling.PolygonSymbolizer;
0092: import org.geotools.styling.Rule;
0093: import org.geotools.styling.Style;
0094: import org.geotools.styling.StyleAttributeExtractor;
0095: import org.geotools.styling.Symbolizer;
0096: import org.geotools.styling.TextSymbolizer;
0097: import org.geotools.styling.visitor.DuplicatingStyleVisitor;
0098: import org.geotools.util.NumberRange;
0099: import org.opengis.filter.Filter;
0100: import org.opengis.referencing.FactoryException;
0101: import org.opengis.referencing.crs.CoordinateReferenceSystem;
0102: import org.opengis.referencing.operation.CoordinateOperation;
0103: import org.opengis.referencing.operation.MathTransform;
0104: import org.opengis.referencing.operation.Operation;
0105: import org.opengis.referencing.operation.TransformException;
0106:
0107: import com.vividsolutions.jts.geom.Coordinate;
0108: import com.vividsolutions.jts.geom.Envelope;
0109: import com.vividsolutions.jts.geom.Geometry;
0110: import com.vividsolutions.jts.geom.GeometryFactory;
0111: import com.vividsolutions.jts.geom.LineString;
0112: import com.vividsolutions.jts.geom.LinearRing;
0113: import com.vividsolutions.jts.geom.MultiLineString;
0114: import com.vividsolutions.jts.geom.MultiPoint;
0115: import com.vividsolutions.jts.geom.MultiPolygon;
0116: import com.vividsolutions.jts.geom.Point;
0117: import com.vividsolutions.jts.geom.Polygon;
0118:
0119: /**
0120: * A LiteRenderer Implementations that is optimized for shapefiles.
0121: *
0122: * @author jeichar
0123: * @since 2.1.x
0124: * @source $URL:
0125: * http://svn.geotools.org/geotools/branches/2.2.x/ext/shaperenderer/src/org/geotools/renderer/shape/ShapefileRenderer.java $
0126: */
0127: public class ShapefileRenderer implements GTRenderer {
0128: public static final Logger LOGGER = org.geotools.util.logging.Logging
0129: .getLogger("org.geotools.renderer.shape");
0130:
0131: /** Tolerance used to compare doubles for equality */
0132: private static final double TOLERANCE = 1e-6;
0133: private static final GeometryFactory geomFactory = new GeometryFactory(
0134: new LiteCoordinateSequenceFactory());
0135: private static final Coordinate[] COORDS;
0136: private static final MultiPolygon MULTI_POLYGON_GEOM;
0137: private static final Polygon POLYGON_GEOM;
0138: private static final LinearRing LINE_GEOM;
0139: private static final MultiLineString MULTI_LINE_GEOM;
0140: private static final Point POINT_GEOM;
0141: private static final MultiPoint MULTI_POINT_GEOM;
0142:
0143: /**
0144: * Computes the scale as the ratio between map distances and real world distances,
0145: * assuming 90dpi and taking into consideration projection deformations and actual
0146: * earth shape. <br>
0147: * Use this method only when in need of accurate computation. Will break if the
0148: * data extent is outside of the currenct projection definition area.
0149: */
0150: public static final String SCALE_ACCURATE = "ACCURATE";
0151:
0152: /**
0153: * Very simple and lenient scale computation method that conforms to the OGC SLD
0154: * specification 1.0, page 26. <br>This method is quite approximative, but should
0155: * never break and ensure constant scale even on lat/lon unprojected maps (because
0156: * in that case scale is computed as if the area was along the equator no matter
0157: * what the real position is).
0158: */
0159: public static final String SCALE_OGC = "OGC";
0160:
0161: private String scaleComputationMethodDEFAULT = SCALE_ACCURATE;
0162: static {
0163: COORDS = new Coordinate[5];
0164: COORDS[0] = new Coordinate(0.0, 0.0);
0165: COORDS[1] = new Coordinate(5.0, 0.0);
0166: COORDS[2] = new Coordinate(5.0, 5.0);
0167: COORDS[3] = new Coordinate(0.0, 5.0);
0168: COORDS[4] = new Coordinate(0.0, 0.0);
0169: LINE_GEOM = geomFactory.createLinearRing(COORDS);
0170: MULTI_LINE_GEOM = geomFactory
0171: .createMultiLineString(new LineString[] { LINE_GEOM });
0172: POLYGON_GEOM = geomFactory.createPolygon(LINE_GEOM,
0173: new LinearRing[0]);
0174: MULTI_POLYGON_GEOM = geomFactory
0175: .createMultiPolygon(new Polygon[] { POLYGON_GEOM });
0176: POINT_GEOM = geomFactory.createPoint(COORDS[2]);
0177: MULTI_POINT_GEOM = geomFactory.createMultiPoint(COORDS);
0178: }
0179:
0180: /**
0181: * This listener is added to the list of listeners automatically. It should be removed if the
0182: * default logging is not needed.
0183: */
0184: public static final DefaultRenderListener DEFAULT_LISTENER = new DefaultRenderListener();
0185:
0186: private static final IndexInfo STREAMING_RENDERER_INFO = new IndexInfo(
0187: (byte) 0, null, null);
0188: static int NUM_SAMPLES = 200;
0189: private RenderingHints hints;
0190:
0191: /** Factory that will resolve symbolizers into rendered styles */
0192: private SLDStyleFactory styleFactory = new SLDStyleFactory();
0193: private boolean renderingStopRequested;
0194: private boolean concatTransforms;
0195: private MapContext context;
0196: LabelCache labelCache = new LabelCacheDefault();
0197: private ListenerList renderListeners = new ListenerList();
0198: boolean caching = false;
0199: private double scaleDenominator;
0200: DbaseFileHeader dbfheader;
0201: private Object defaultGeom;
0202: IndexInfo[] layerIndexInfo;
0203:
0204: /**
0205: * Maps between the AttributeType index of the new generated FeatureType and the real
0206: * attributeType
0207: */
0208: int[] attributeIndexing;
0209:
0210: /** The painter class we use to depict shapes onto the screen */
0211: private StyledShapePainter painter = new StyledShapePainter(
0212: labelCache);
0213: private Map decimators = new HashMap();
0214:
0215: /**
0216: * Text will be rendered using the usual calls gc.drawString/drawGlyphVector.
0217: * This is a little faster, and more consistent with how the platform renders
0218: * the text in other applications. The downside is that on most platform the label
0219: * and its eventual halo are not properly centered.
0220: */
0221: public static final String TEXT_RENDERING_STRING = "STRING";
0222:
0223: /**
0224: * Text will be rendered using the associated {@link GlyphVector} outline, that is, a {@link Shape}.
0225: * This ensures perfect centering between the text and the halo, but introduces more text aliasing.
0226: */
0227: public static final String TEXT_RENDERING_OUTLINE = "OUTLINE";
0228:
0229: /**
0230: * The text rendering method, either TEXT_RENDERING_OUTLINE or TEXT_RENDERING_STRING
0231: */
0232: public static final String TEXT_RENDERING_KEY = "textRenderingMethod";
0233: private String textRenderingModeDEFAULT = TEXT_RENDERING_STRING;
0234:
0235: public static final String LABEL_CACHE_KEY = "labelCache";
0236: public static final String FORCE_CRS_KEY = "forceCRS";
0237: public static final String DPI_KEY = "dpi";
0238: public static final String DECLARED_SCALE_DENOM_KEY = "declaredScaleDenominator";
0239: public static final String MEMORY_PRE_LOADING_KEY = "memoryPreloadingEnabled";
0240: public static final String OPTIMIZED_DATA_LOADING_KEY = "optimizedDataLoadingEnabled";
0241: public static final String SCALE_COMPUTATION_METHOD_KEY = "scaleComputationMethod";
0242:
0243: /**
0244: * "optimizedDataLoadingEnabled" - Boolean yes/no (see default optimizedDataLoadingEnabledDEFAULT)
0245: * "memoryPreloadingEnabled" - Boolean yes/no (see default memoryPreloadingEnabledDEFAULT)
0246: * "declaredScaleDenominator" - Double the value of the scale denominator to use by the renderer.
0247: * by default the value is calculated based on the screen size
0248: * and the displayed area of the map.
0249: * "dpi" - Integer number of dots per inch of the display 90 DPI is the default (as declared by OGC)
0250: * "forceCRS" - CoordinateReferenceSystem declares to the renderer that all layers are of the CRS declared in this hint
0251: * "labelCache" - Declares the label cache that will be used by the renderer.
0252: */
0253: private Map rendererHints = null;
0254:
0255: public ShapefileRenderer(MapContext context) {
0256: setContext(context);
0257: }
0258:
0259: public ShapefileRenderer() {
0260: }
0261:
0262: public void paint(Graphics2D graphics, Rectangle paintArea,
0263: ReferencedEnvelope mapArea) {
0264: if (mapArea == null || paintArea == null) {
0265: LOGGER.info("renderer passed null arguments");
0266: return;
0267: } // Other arguments get checked later
0268: paint(graphics, paintArea, mapArea, RendererUtilities
0269: .worldToScreenTransform(mapArea, paintArea));
0270: }
0271:
0272: private DbaseFileHeader getDBFHeader(ShapefileDataStore ds) {
0273: DbaseFileReader reader = null;
0274:
0275: try {
0276: reader = ShapefileRendererUtil.getDBFReader(ds);
0277:
0278: return reader.getHeader();
0279: } catch (IOException e) {
0280: e.printStackTrace();
0281: } finally {
0282: if (reader != null) {
0283: try {
0284: reader.close();
0285: } catch (IOException e) {
0286: // TODO Auto-generated catch block
0287: e.printStackTrace();
0288: }
0289: }
0290: }
0291:
0292: return null;
0293: }
0294:
0295: private void processStylers(Graphics2D graphics,
0296: ShapefileDataStore datastore, Query query, Envelope bbox,
0297: Rectangle screenSize, MathTransform mt, Style style,
0298: IndexInfo info, Transaction transaction, String layerId)
0299: throws IOException {
0300: if (LOGGER.isLoggable(Level.FINE)) {
0301: LOGGER.fine("processing "
0302: + style.getFeatureTypeStyles().length + " stylers");
0303: }
0304:
0305: FeatureTypeStyle[] featureStylers = style
0306: .getFeatureTypeStyles();
0307: FeatureType type;
0308:
0309: try {
0310: type = createFeatureType(query, style, datastore);
0311: } catch (Exception e) {
0312: fireErrorEvent(e);
0313:
0314: return;
0315: }
0316:
0317: for (int i = 0; i < featureStylers.length; i++) {
0318: if (LOGGER.isLoggable(Level.FINE)) {
0319: LOGGER.fine("processing style " + i);
0320: }
0321:
0322: FeatureTypeStyle fts = featureStylers[i];
0323: String typeName = datastore.getSchema().getTypeName();
0324:
0325: if ((typeName != null)
0326: && (datastore.getSchema().isDescendedFrom(null,
0327: fts.getFeatureTypeName()) || typeName
0328: .equalsIgnoreCase(fts.getFeatureTypeName()))) {
0329: // get applicable rules at the current scale
0330: Rule[] rules = fts.getRules();
0331: List ruleList = new ArrayList();
0332: List elseRuleList = new ArrayList();
0333:
0334: // TODO process filter for geometry expressions and restrict bbox further based on
0335: // the result
0336:
0337: for (int j = 0; j < rules.length; j++) {
0338: if (LOGGER.isLoggable(Level.FINE)) {
0339: LOGGER.fine("processing rule " + j);
0340: }
0341:
0342: Rule r = rules[j];
0343:
0344: // make copy so I don't accidentally modify style
0345: DuplicatingStyleVisitor duplicator = new DuplicatingStyleVisitor();
0346: r.accept(duplicator);
0347: r = (Rule) duplicator.getCopy();
0348: if (r.getFilter() != null) {
0349: // now reproject the geometries in filter because geoms are retrieved projected to screen space
0350: FilterTransformer transformer = new FilterTransformer(
0351: mt);
0352: r.setFilter((Filter) r.getFilter().accept(
0353: transformer, null));
0354: }
0355: if (isWithInScale(r)) {
0356: if (r.hasElseFilter()) {
0357: elseRuleList.add(r);
0358: } else {
0359: ruleList.add(r);
0360: }
0361: }
0362: }
0363:
0364: // process the features according to the rules
0365: // TODO: find a better way to declare the scale ranges so that
0366: // we
0367: // get style caching also between multiple rendering runs
0368: NumberRange scaleRange = new NumberRange(
0369: scaleDenominator, scaleDenominator);
0370:
0371: Set modifiedFIDs = processTransaction(graphics, bbox,
0372: mt, datastore, transaction, typeName, query,
0373: ruleList, elseRuleList, scaleRange, layerId);
0374:
0375: // don't try to read the shapefile if there is nothing to draw
0376: if (ruleList.size() > 0 || elseRuleList.size() > 0)
0377: processShapefile(graphics, datastore, bbox,
0378: screenSize, mt, info, type, query,
0379: ruleList, elseRuleList, modifiedFIDs,
0380: scaleRange, layerId);
0381: }
0382: }
0383: }
0384:
0385: private Set processTransaction(Graphics2D graphics, Envelope bbox,
0386: MathTransform transform, DataStore ds,
0387: Transaction transaction, String typename, Query query,
0388: List ruleList, List elseRuleList, NumberRange scaleRange,
0389: String layerId) {
0390: if (transaction == Transaction.AUTO_COMMIT) {
0391: return Collections.EMPTY_SET;
0392: }
0393:
0394: TransactionStateDiff state = (TransactionStateDiff) transaction
0395: .getState(ds);
0396:
0397: if (state == null) {
0398: return Collections.EMPTY_SET;
0399: }
0400:
0401: Set fids = new HashSet();
0402: Map modified = null;
0403: Map added = null;
0404: Diff diff = null;
0405:
0406: try {
0407: diff = state.diff(typename);
0408: modified = diff.modified2;
0409: added = diff.added;
0410: fids = new HashSet();
0411: } catch (IOException e) {
0412: fids = Collections.EMPTY_SET;
0413: return fids;
0414: }
0415:
0416: if (!diff.isEmpty()) {
0417: Feature feature;
0418:
0419: for (Iterator modifiedIter = modified.keySet().iterator(), addedIter = added
0420: .values().iterator(); modifiedIter.hasNext()
0421: || addedIter.hasNext();) {
0422: if (renderingStopRequested) {
0423: break;
0424: }
0425:
0426: boolean doElse = true;
0427: if (modifiedIter.hasNext()) {
0428: String fid = (String) modifiedIter.next();
0429: feature = (Feature) modified.get(fid);
0430: fids.add(fid);
0431: } else {
0432: feature = (Feature) addedIter.next();
0433: }
0434:
0435: if (!query.getFilter().evaluate(feature))
0436: continue;
0437:
0438: if (feature != TransactionStateDiff.NULL) {
0439: // applicable rules
0440: for (Iterator it = ruleList.iterator(); it
0441: .hasNext();) {
0442: Rule r = (Rule) it.next();
0443:
0444: if (LOGGER.isLoggable(Level.FINER)) {
0445: LOGGER.finer("applying rule: "
0446: + r.toString());
0447: }
0448:
0449: if (LOGGER.isLoggable(Level.FINER)) {
0450: LOGGER.finer("this rule applies ...");
0451: }
0452:
0453: Filter filter = r.getFilter();
0454:
0455: if ((filter == null)
0456: || filter.evaluate(feature)) {
0457: doElse = false;
0458:
0459: if (LOGGER.isLoggable(Level.FINER)) {
0460: LOGGER
0461: .finer("processing Symobolizer ...");
0462: }
0463:
0464: Symbolizer[] symbolizers = r
0465: .getSymbolizers();
0466:
0467: try {
0468: processSymbolizers(graphics, feature,
0469: symbolizers, scaleRange,
0470: transform, layerId);
0471: } catch (Exception e) {
0472: fireErrorEvent(e);
0473:
0474: continue;
0475: }
0476:
0477: if (LOGGER.isLoggable(Level.FINER)) {
0478: LOGGER.finer("... done!");
0479: }
0480: }
0481: }
0482:
0483: if (doElse) {
0484: // rules with an else filter
0485: if (LOGGER.isLoggable(Level.FINER)) {
0486: LOGGER.finer("rules with an else filter");
0487: }
0488:
0489: for (Iterator it = elseRuleList.iterator(); it
0490: .hasNext();) {
0491: Rule r = (Rule) it.next();
0492: Symbolizer[] symbolizers = r
0493: .getSymbolizers();
0494:
0495: if (LOGGER.isLoggable(Level.FINER)) {
0496: LOGGER
0497: .finer("processing Symobolizer ...");
0498: }
0499:
0500: try {
0501: processSymbolizers(graphics, feature,
0502: symbolizers, scaleRange,
0503: transform, layerId);
0504: } catch (Exception e) {
0505: fireErrorEvent(e);
0506:
0507: continue;
0508: }
0509:
0510: if (LOGGER.isLoggable(Level.FINER)) {
0511: LOGGER.finer("... done!");
0512: }
0513: }
0514: }
0515:
0516: if (LOGGER.isLoggable(Level.FINER)) {
0517: LOGGER.finer("feature rendered event ...");
0518: }
0519: }
0520: }
0521: }
0522:
0523: return fids;
0524: }
0525:
0526: private void processShapefile(Graphics2D graphics,
0527: ShapefileDataStore datastore, Envelope bbox,
0528: Rectangle screenSize, MathTransform mt, IndexInfo info,
0529: FeatureType type, Query query, List ruleList,
0530: List elseRuleList, Set modifiedFIDs,
0531: NumberRange scaleRange, String layerId) throws IOException {
0532: IndexedDbaseFileReader dbfreader = null;
0533:
0534: // don't waste time processing the dbf file if the only attribute loades is the geometry
0535: if (type.getAttributeCount() > 1) {
0536: try {
0537: dbfreader = ShapefileRendererUtil
0538: .getDBFReader(datastore);
0539: } catch (Exception e) {
0540: fireErrorEvent(e);
0541: }
0542: }
0543:
0544: OpacityFinder opacityFinder = new OpacityFinder(
0545: getAcceptableSymbolizers(type.getDefaultGeometry()));
0546:
0547: for (Iterator iter = ruleList.iterator(); iter.hasNext();) {
0548: Rule rule = (Rule) iter.next();
0549: rule.accept(opacityFinder);
0550: }
0551:
0552: IndexInfo.Reader shpreader = null;
0553: boolean useJTS = true;
0554:
0555: try {
0556: shpreader = new IndexInfo.Reader(info,
0557: ShapefileRendererUtil.getShpReader(datastore, bbox,
0558: screenSize, mt, opacityFinder.hasOpacity,
0559: useJTS), bbox);
0560: } catch (Exception e) {
0561: fireErrorEvent(e);
0562: return;
0563: }
0564:
0565: FIDReader fidReader = null;
0566: try {
0567: fidReader = ShapefileRendererUtil.getFidReader(datastore,
0568: shpreader);
0569: } catch (Exception e) {
0570: fireErrorEvent(e);
0571: return;
0572: }
0573:
0574: try {
0575: while (true) {
0576: try {
0577: if (renderingStopRequested) {
0578: break;
0579: }
0580:
0581: if (!shpreader.hasNext()) {
0582: break;
0583: }
0584:
0585: boolean doElse = true;
0586:
0587: if (LOGGER.isLoggable(Level.FINER)) {
0588: LOGGER.fine("trying to read geometry ...");
0589: }
0590:
0591: String nextFid = fidReader.next();
0592: if (modifiedFIDs.contains(nextFid)) {
0593: shpreader.next();
0594: if (dbfreader != null
0595: && !dbfreader.IsRandomAccessEnabled())
0596: dbfreader.skip();
0597: continue;
0598: }
0599: if (dbfreader != null
0600: && dbfreader.IsRandomAccessEnabled())
0601: dbfreader.goTo(shpreader.getRecordNumber());
0602: ShapefileReader.Record record = shpreader.next();
0603:
0604: Object geom = record.shape();
0605:
0606: if (geom == null) {
0607: LOGGER.finest("skipping geometry");
0608: if (dbfreader != null
0609: && !dbfreader.IsRandomAccessEnabled())
0610: dbfreader.skip();
0611: continue;
0612: }
0613:
0614: Feature feature = createFeature(type, record,
0615: dbfreader, nextFid);
0616: if (!query.getFilter().evaluate(feature))
0617: continue;
0618:
0619: if (renderingStopRequested) {
0620: break;
0621: }
0622:
0623: if (LOGGER.isLoggable(Level.FINEST)) {
0624: LOGGER.finest("... done: " + geom.toString());
0625: }
0626:
0627: if (LOGGER.isLoggable(Level.FINER)) {
0628: LOGGER.fine("... done: " + type.getTypeName());
0629: }
0630:
0631: // applicable rules
0632: for (Iterator it = ruleList.iterator(); it
0633: .hasNext();) {
0634: Rule r = (Rule) it.next();
0635:
0636: if (LOGGER.isLoggable(Level.FINER)) {
0637: LOGGER.finer("applying rule: "
0638: + r.toString());
0639: }
0640:
0641: if (LOGGER.isLoggable(Level.FINER)) {
0642: LOGGER.finer("this rule applies ...");
0643: }
0644:
0645: Filter filter = r.getFilter();
0646:
0647: if ((filter == null)
0648: || filter.evaluate(feature)) {
0649: doElse = false;
0650:
0651: if (LOGGER.isLoggable(Level.FINER)) {
0652: LOGGER
0653: .finer("processing Symobolizer ...");
0654: }
0655:
0656: Symbolizer[] symbolizers = r
0657: .getSymbolizers();
0658:
0659: processSymbolizers(graphics, feature, geom,
0660: symbolizers, scaleRange, useJTS,
0661: layerId);
0662:
0663: if (LOGGER.isLoggable(Level.FINER)) {
0664: LOGGER.finer("... done!");
0665: }
0666: }
0667: }
0668:
0669: if (doElse) {
0670: // rules with an else filter
0671: if (LOGGER.isLoggable(Level.FINER)) {
0672: LOGGER.finer("rules with an else filter");
0673: }
0674:
0675: for (Iterator it = elseRuleList.iterator(); it
0676: .hasNext();) {
0677: Rule r = (Rule) it.next();
0678: Symbolizer[] symbolizers = r
0679: .getSymbolizers();
0680:
0681: if (LOGGER.isLoggable(Level.FINER)) {
0682: LOGGER
0683: .finer("processing Symobolizer ...");
0684: }
0685:
0686: processSymbolizers(graphics, feature, geom,
0687: symbolizers, scaleRange, useJTS,
0688: layerId);
0689:
0690: if (LOGGER.isLoggable(Level.FINER)) {
0691: LOGGER.finer("... done!");
0692: }
0693: }
0694: }
0695:
0696: if (LOGGER.isLoggable(Level.FINER)) {
0697: LOGGER.finer("feature rendered event ...");
0698: }
0699: } catch (Exception e) {
0700: fireErrorEvent(e);
0701: }
0702: }
0703: } finally {
0704: try {
0705: if (dbfreader != null) {
0706: dbfreader.close();
0707: }
0708: } finally {
0709: try {
0710: if (shpreader != null) {
0711: shpreader.close();
0712: }
0713: } finally {
0714: if (fidReader == null)
0715: fidReader.close();
0716: }
0717: }
0718: }
0719: }
0720:
0721: private Class[] getAcceptableSymbolizers(
0722: GeometryAttributeType defaultGeometry) {
0723: if (Polygon.class.isAssignableFrom(defaultGeometry.getType())
0724: || MultiPolygon.class.isAssignableFrom(defaultGeometry
0725: .getType())) {
0726: return new Class[] { PointSymbolizer.class,
0727: LineSymbolizer.class, PolygonSymbolizer.class };
0728: }
0729:
0730: return new Class[] { PointSymbolizer.class,
0731: LineSymbolizer.class };
0732: }
0733:
0734: Feature createFeature(FeatureType type, Record record,
0735: DbaseFileReader dbfreader, String id) throws Exception {
0736: if (type.getAttributeCount() == 1) {
0737: return type.create(new Object[] { getGeom(record.shape(),
0738: type.getDefaultGeometry()) }, id);
0739: } else {
0740: DbaseFileHeader header = dbfreader.getHeader();
0741:
0742: Object[] all = dbfreader.readEntry();
0743: Object[] values = new Object[type.getAttributeCount()];
0744:
0745: for (int i = 0; i < (values.length - 1); i++) {
0746: values[i] = all[attributeIndexing[i]];
0747:
0748: if (header.getFieldName(attributeIndexing[i]).equals(
0749: type.getAttributeType(i))) {
0750: System.out.println("ok");
0751: }
0752: }
0753:
0754: values[values.length - 1] = getGeom(record.shape(), type
0755: .getDefaultGeometry());
0756:
0757: return type.create(values, id);
0758: }
0759: }
0760:
0761: /**
0762: * Return provided geom; or use a default value if null.
0763: *
0764: * @param geom Provided Geometry as read from record.shape()
0765: * @param defaultGeometry GeometryAttributeType used to determine default value
0766: * @return provided geom or default value if null
0767: */
0768: private Object getGeom(Object geom,
0769: GeometryAttributeType defaultGeometry) {
0770: if (geom instanceof Geometry) {
0771: return geom;
0772: }
0773: return getGeom(defaultGeometry);
0774: }
0775:
0776: /**
0777: * This class keeps a couple of default geometries on hand to use
0778: * when making a feature with default values.
0779: *
0780: * @param defaultGeometry
0781: * @return placeholder to use as a default while waiting for a real geometry.
0782: */
0783: private Object getGeom(GeometryAttributeType defaultGeometry) {
0784: if (MultiPolygon.class.isAssignableFrom(defaultGeometry
0785: .getType())) {
0786: return MULTI_POLYGON_GEOM;
0787: } else if (MultiLineString.class
0788: .isAssignableFrom(defaultGeometry.getType())) {
0789: return MULTI_LINE_GEOM;
0790: } else if (Point.class.isAssignableFrom(defaultGeometry
0791: .getType())) {
0792: return POINT_GEOM;
0793: } else if (MultiPoint.class.isAssignableFrom(defaultGeometry
0794: .getType())) {
0795: return MULTI_POINT_GEOM;
0796: }
0797: return null; // we don't have a good default value - null will need to do
0798: }
0799:
0800: /**
0801: * DOCUMENT ME!
0802: *
0803: * @param query
0804: * @param style
0805: * @param schema DOCUMENT ME!
0806: * @return
0807: * @throws FactoryConfigurationError
0808: * @throws SchemaException
0809: */
0810: FeatureType createFeatureType(Query query, Style style,
0811: ShapefileDataStore ds) throws SchemaException, IOException {
0812: FeatureType schema = ds.getSchema();
0813: String[] attributes = findStyleAttributes(
0814: (query == null) ? Query.ALL : query, style, schema);
0815: AttributeType[] types = new AttributeType[attributes.length];
0816: attributeIndexing = new int[attributes.length];
0817:
0818: if (attributes.length == 1
0819: && attributes[0].equals(schema.getDefaultGeometry()
0820: .getLocalName())) {
0821: types[0] = schema.getAttributeType(attributes[0]);
0822: } else {
0823: dbfheader = getDBFHeader(ds);
0824: for (int i = 0; i < types.length; i++) {
0825: types[i] = schema.getAttributeType(attributes[i]);
0826:
0827: for (int j = 0; j < dbfheader.getNumFields(); j++) {
0828: if (dbfheader.getFieldName(j).equals(attributes[i])) {
0829: attributeIndexing[i] = j;
0830:
0831: break;
0832: }
0833: }
0834: }
0835: }
0836:
0837: FeatureType type = FeatureTypeBuilder.newFeatureType(types,
0838: schema.getTypeName(), schema.getNamespace(), false,
0839: null, schema.getDefaultGeometry());
0840:
0841: return type;
0842: }
0843:
0844: /**
0845: * Inspects the <code>MapLayer</code>'s style and retrieves it's needed attribute names,
0846: * returning at least the default geometry attribute name.
0847: *
0848: * @param query DOCUMENT ME!
0849: * @param style the <code>Style</code> to determine the needed attributes from
0850: * @param schema the featuresource schema
0851: * @return the minimun set of attribute names needed to render <code>layer</code>
0852: */
0853: private String[] findStyleAttributes(final Query query,
0854: Style style, FeatureType schema) {
0855: StyleAttributeExtractor sae = new StyleAttributeExtractor() {
0856: public void visit(Rule rule) {
0857:
0858: DuplicatingStyleVisitor dupeStyleVisitor = new DuplicatingStyleVisitor();
0859: dupeStyleVisitor.visit(rule);
0860: Rule clone = (Rule) dupeStyleVisitor.getCopy();
0861:
0862: super .visit(clone);
0863: }
0864: };
0865:
0866: sae.visit(style);
0867:
0868: FilterAttributeExtractor qae = new FilterAttributeExtractor();
0869: query.getFilter().accept(qae, null);
0870: Set ftsAttributes = new HashSet(sae.getAttributeNameSet());
0871: ftsAttributes.addAll(qae.getAttributeNameSet());
0872: // the code following assumes we won't extract the default geometry, and that's
0873: // most of the time true, but fails if the filter or the style uses it.
0874: ftsAttributes
0875: .remove(schema.getDefaultGeometry().getLocalName());
0876: return (String[]) ftsAttributes.toArray(new String[0]);
0877: }
0878:
0879: /**
0880: * DOCUMENT ME!
0881: *
0882: * @param graphics
0883: * @param feature DOCUMENT ME!
0884: * @param geom
0885: * @param symbolizers
0886: * @param scaleRange
0887: * @param layerId
0888: */
0889: private void processSymbolizers(Graphics2D graphics,
0890: Feature feature, Object geom, Symbolizer[] symbolizers,
0891: NumberRange scaleRange, boolean isJTS, String layerId) {
0892: for (int m = 0; m < symbolizers.length; m++) {
0893: if (LOGGER.isLoggable(Level.FINER)) {
0894: LOGGER.finer("applying symbolizer " + symbolizers[m]);
0895: }
0896:
0897: if (renderingStopRequested) {
0898: break;
0899: }
0900:
0901: if (symbolizers[m] instanceof TextSymbolizer) {
0902: try {
0903: labelCache.put(layerId,
0904: (TextSymbolizer) symbolizers[m], feature,
0905: new LiteShape2(
0906: feature.getDefaultGeometry(), null,
0907: null, false, false), scaleRange);
0908: } catch (Exception e) {
0909: fireErrorEvent(e);
0910: }
0911: } else {
0912: try {
0913: Style2D style = styleFactory.createStyle(feature,
0914: symbolizers[m], scaleRange);
0915: if (isJTS) {
0916: painter.paint(graphics, new LiteShape2(
0917: (Geometry) geom, null, null, false,
0918: false), style, scaleDenominator);
0919: } else {
0920: painter.paint(graphics,
0921: getShape((SimpleGeometry) geom), style,
0922: scaleDenominator);
0923: }
0924: } catch (Exception e) {
0925: fireErrorEvent(e);
0926: }
0927: }
0928:
0929: }
0930: fireFeatureRenderedEvent(feature);
0931: }
0932:
0933: /**
0934: * Applies each of a set of symbolizers in turn to a given feature.
0935: * <p>
0936: * This is an internal method and should only be called by processStylers.
0937: * </p>
0938: *
0939: * @param graphics
0940: * @param feature The feature to be rendered
0941: * @param symbolizers An array of symbolizers which actually perform the rendering.
0942: * @param scaleRange The scale range we are working on... provided in order to make the style
0943: * factory happy
0944: * @param transform DOCUMENT ME!
0945: * @param layerId
0946: * @throws TransformException
0947: * @throws FactoryException
0948: */
0949: private void processSymbolizers(final Graphics2D graphics,
0950: final Feature feature, final Symbolizer[] symbolizers,
0951: Range scaleRange, MathTransform transform, String layerId)
0952: throws TransformException, FactoryException {
0953: LiteShape2 shape;
0954:
0955: for (int m = 0; m < symbolizers.length; m++) {
0956: if (LOGGER.isLoggable(Level.FINER)) {
0957: LOGGER.finer("applying symbolizer " + symbolizers[m]);
0958: }
0959:
0960: Geometry g = feature.getDefaultGeometry();
0961: shape = new LiteShape2(g, transform,
0962: getDecimator(transform), false);
0963:
0964: if (symbolizers[m] instanceof TextSymbolizer) {
0965: labelCache.put(layerId,
0966: (TextSymbolizer) symbolizers[m], feature,
0967: shape, scaleRange);
0968: } else {
0969: Style2D style = styleFactory.createStyle(feature,
0970: symbolizers[m], scaleRange);
0971: painter.paint(graphics, shape, style, scaleDenominator);
0972: }
0973: }
0974:
0975: fireFeatureRenderedEvent(feature);
0976: }
0977:
0978: /**
0979: * DOCUMENT ME!
0980: *
0981: * @param mathTransform DOCUMENT ME!
0982: * @return
0983: * @throws org.opengis.referencing.operation.NoninvertibleTransformException
0984: */
0985: private Decimator getDecimator(MathTransform mathTransform)
0986: throws org.opengis.referencing.operation.NoninvertibleTransformException {
0987: Decimator decimator = null;
0988:
0989: if (mathTransform != null)
0990: decimator = (Decimator) decimators.get(mathTransform);
0991:
0992: if (decimator == null) {
0993: decimator = new Decimator(mathTransform.inverse());
0994:
0995: decimators.put(mathTransform, decimator);
0996: }
0997:
0998: return decimator;
0999: }
1000:
1001: //
1002: // /**
1003: // * Creates a JTS shape that is an approximation of the SImpleGeometry. This is ONLY use for
1004: // * labelling and is only created if a text symbolizer is part of the current style.
1005: // *
1006: // * @param geom the geometry to wrap
1007: // * @return
1008: // * @throws TransformException
1009: // * @throws FactoryException
1010: // * @throws RuntimeException DOCUMENT ME!
1011: // */
1012: // LiteShape2 getLiteShape2( SimpleGeometry geom ) throws TransformException, FactoryException {
1013: // Geometry jtsGeom;
1014: // if ((geom.type == ShapeType.POLYGON) || (geom.type == ShapeType.POLYGONM)
1015: // || (geom.type == ShapeType.POLYGONZ)) {
1016: // double[] points = getPointSample(geom, true);
1017: // CoordinateSequence seq = new LiteCoordinateSequence(points);
1018: // Polygon poly;
1019: //
1020: // try {
1021: // poly = geomFactory.createPolygon(geomFactory.createLinearRing(seq),
1022: // new LinearRing[]{});
1023: // } catch (Exception e) {
1024: // throw new RuntimeException(e);
1025: // }
1026: //
1027: // jtsGeom = geomFactory.createMultiPolygon(new Polygon[]{poly});
1028: // } else if ((geom.type == ShapeType.ARC) || (geom.type == ShapeType.ARCM)
1029: // || (geom.type == ShapeType.ARCZ)) {
1030: // double[] points = getPointSample(geom, false);
1031: // CoordinateSequence seq = new LiteCoordinateSequence(points);
1032: // jtsGeom = geomFactory.createMultiLineString(new LineString[]{geomFactory
1033: // .createLineString(seq)});
1034: // } else if ((geom.type == ShapeType.MULTIPOINT) || (geom.type == ShapeType.MULTIPOINTM)
1035: // || (geom.type == ShapeType.MULTIPOINTZ)) {
1036: // double[] points = getPointSample(geom, false);
1037: // CoordinateSequence seq = new LiteCoordinateSequence(points);
1038: // jtsGeom = geomFactory.createMultiPoint(seq);
1039: // } else {
1040: // jtsGeom = geomFactory.createPoint(new Coordinate(geom.coords[0][0], geom.coords[0][1]));
1041: // }
1042: //
1043: // LiteShape2 shape = new LiteShape2(jtsGeom, null, null, false);
1044: //
1045: // return shape;
1046: // }
1047:
1048: // /**
1049: // * takes a random sampling from the geometry. Only uses the larges part of the geometry.
1050: // *
1051: // * @param geom
1052: // * @param isPolygon DOCUMENT ME!
1053: // * @return
1054: // */
1055: // private double[] getPointSample( SimpleGeometry geom, boolean isPolygon ) {
1056: // int largestPart = 0;
1057: //
1058: // for( int i = 0; i < geom.coords.length; i++ ) {
1059: // if (geom.coords[i].length > geom.coords[largestPart].length) {
1060: // largestPart = i;
1061: // }
1062: // }
1063: //
1064: // return geom.coords[largestPart];
1065: // }
1066:
1067: /**
1068: * DOCUMENT ME!
1069: *
1070: * @param geom
1071: * @return
1072: */
1073: private Shape getShape(SimpleGeometry geom) {
1074: if ((geom.type == ShapeType.ARC)
1075: || (geom.type == ShapeType.ARCM)
1076: || (geom.type == ShapeType.ARCZ)) {
1077: return new MultiLineShape(geom);
1078: }
1079:
1080: if ((geom.type == ShapeType.POLYGON)
1081: || (geom.type == ShapeType.POLYGONM)
1082: || (geom.type == ShapeType.POLYGONZ)) {
1083: return new PolygonShape(geom);
1084: }
1085:
1086: if ((geom.type == ShapeType.POINT)
1087: || (geom.type == ShapeType.POINTM)
1088: || (geom.type == ShapeType.POINTZ)
1089: || (geom.type == ShapeType.MULTIPOINT)
1090: || (geom.type == ShapeType.MULTIPOINTM)
1091: || (geom.type == ShapeType.MULTIPOINTZ)) {
1092: return new MultiPointShape(geom);
1093: }
1094:
1095: return null;
1096: }
1097:
1098: /**
1099: * Checks if a rule can be triggered at the current scale level
1100: *
1101: * @param r The rule
1102: * @return true if the scale is compatible with the rule settings
1103: */
1104: private boolean isWithInScale(Rule r) {
1105: return ((r.getMinScaleDenominator() - TOLERANCE) <= scaleDenominator)
1106: && ((r.getMaxScaleDenominator() + TOLERANCE) > scaleDenominator);
1107: }
1108:
1109: /**
1110: * adds a listener that responds to error events of feature rendered events.
1111: *
1112: * @param listener the listener to add.
1113: * @see RenderListener
1114: */
1115: public void addRenderListener(RenderListener listener) {
1116: renderListeners.add(listener);
1117: }
1118:
1119: /**
1120: * Removes a render listener.
1121: *
1122: * @param listener the listener to remove.
1123: * @see RenderListener
1124: */
1125: public void removeRenderListener(RenderListener listener) {
1126: renderListeners.remove(listener);
1127: }
1128:
1129: private void fireFeatureRenderedEvent(Feature feature) {
1130: Object[] objects = renderListeners.getListeners();
1131:
1132: for (int i = 0; i < objects.length; i++) {
1133: RenderListener listener = (RenderListener) objects[i];
1134: listener.featureRenderer(feature);
1135: }
1136: }
1137:
1138: private void fireErrorEvent(Exception e) {
1139: Object[] objects = renderListeners.getListeners();
1140:
1141: for (int i = 0; i < objects.length; i++) {
1142: RenderListener listener = (RenderListener) objects[i];
1143: listener.errorOccurred(e);
1144: }
1145: }
1146:
1147: /**
1148: * Setter for property scaleDenominator.
1149: *
1150: * @param scaleDenominator New value of property scaleDenominator.
1151: */
1152: protected void setScaleDenominator(double scaleDenominator) {
1153: this .scaleDenominator = scaleDenominator;
1154: }
1155:
1156: /**
1157: * If you call this method from another thread than the one that called <code>paint</code> or
1158: * <code>render</code> the rendering will be forcefully stopped before termination
1159: */
1160: public void stopRendering() {
1161: renderingStopRequested = true;
1162: labelCache.stop();
1163: }
1164:
1165: /**
1166: * DOCUMENT ME!
1167: *
1168: * @return Returns the caching.
1169: */
1170: public boolean isCaching() {
1171: return caching;
1172: }
1173:
1174: /**
1175: * DOCUMENT ME!
1176: *
1177: * @param caching The caching to set.
1178: */
1179: public void setCaching(boolean caching) {
1180: this .caching = caching;
1181: }
1182:
1183: public MapContext getContext() {
1184: return context;
1185: }
1186:
1187: public boolean isConcatTransforms() {
1188: return concatTransforms;
1189: }
1190:
1191: public void setConcatTransforms(boolean concatTransforms) {
1192: this .concatTransforms = concatTransforms;
1193: }
1194:
1195: public IndexInfo useIndex(ShapefileDataStore ds)
1196: throws IOException, StoreException {
1197: IndexInfo info;
1198: String filename = null;
1199: URL url = ShapefileRendererUtil.getshpURL(ds);
1200:
1201: if (url == null) {
1202: throw new NullPointerException(
1203: "Null URL for ShapefileDataSource");
1204: }
1205:
1206: try {
1207: filename = java.net.URLDecoder.decode(url.toString(),
1208: "US-ASCII");
1209: } catch (java.io.UnsupportedEncodingException use) {
1210: throw new java.net.MalformedURLException(
1211: "Unable to decode " + url + " cause "
1212: + use.getMessage());
1213: }
1214:
1215: filename = filename.substring(0, filename.length() - 4);
1216:
1217: String grxext = ".grx";
1218: String qixext = ".qix";
1219:
1220: if (ds.isLocal()) {
1221: File grxTree = new File(new URL(filename + grxext)
1222: .getPath());
1223: File qixTree = new File(new URL(filename + qixext)
1224: .getPath());
1225: URL shx = new URL(filename + ".shx");
1226:
1227: if (!new File(shx.getPath()).exists()) {
1228: info = new IndexInfo(IndexInfo.TREE_NONE, null, null);
1229: } else if (qixTree.exists()) {
1230: info = new IndexInfo(IndexInfo.QUAD_TREE, new URL(
1231: filename + qixext), shx);
1232: LOGGER.fine("Using quad tree");
1233: } else if (grxTree.exists()) {
1234: info = new IndexInfo(IndexInfo.R_TREE, new URL(filename
1235: + grxext), shx);
1236: LOGGER.fine("Using r-tree");
1237: } else {
1238: info = new IndexInfo(IndexInfo.TREE_NONE, null, null);
1239: LOGGER.fine("No indexing");
1240: }
1241: } else {
1242: info = new IndexInfo(IndexInfo.TREE_NONE, null, null);
1243: }
1244:
1245: return info;
1246: }
1247:
1248: /**
1249: * By default ignores all feature renderered events and logs all exceptions as severe.
1250: */
1251: private static class DefaultRenderListener implements
1252: RenderListener {
1253: /**
1254: * @see org.geotools.renderer.lite.RenderListener#featureRenderer(org.geotools.feature.Feature)
1255: */
1256: public void featureRenderer(Feature feature) {
1257: // do nothing.
1258: }
1259:
1260: /**
1261: * @see org.geotools.renderer.lite.RenderListener#errorOccurred(java.lang.Exception)
1262: */
1263: public void errorOccurred(Exception e) {
1264: LOGGER.log(Level.SEVERE, e.getMessage(), e);
1265: }
1266: }
1267:
1268: public void setJava2DHints(RenderingHints hints) {
1269: this .hints = hints;
1270: }
1271:
1272: public RenderingHints getJava2DHints() {
1273: return hints;
1274: }
1275:
1276: public void setRendererHints(Map hints) {
1277: if (hints != null && hints.containsKey(LABEL_CACHE_KEY)) {
1278: LabelCache cache = (LabelCache) hints.get(LABEL_CACHE_KEY);
1279: if (cache == null)
1280: throw new NullPointerException(
1281: "Label_Cache_Hint has a null value for the labelcache");
1282:
1283: this .labelCache = cache;
1284: this .painter = new StyledShapePainter(cache);
1285: }
1286: rendererHints = hints;
1287: }
1288:
1289: public Map getRendererHints() {
1290: return rendererHints;
1291: }
1292:
1293: public void setContext(MapContext context) {
1294: if (context == null) {
1295: context = new DefaultMapContext(DefaultGeographicCRS.WGS84);
1296: }
1297:
1298: this .context = context;
1299:
1300: MapLayer[] layers = context.getLayers();
1301: layerIndexInfo = new IndexInfo[layers.length];
1302:
1303: for (int i = 0; i < layers.length; i++) {
1304: DataStore ds = layers[i].getFeatureSource().getDataStore();
1305: if (ds instanceof ShapefileDataStore) {
1306:
1307: ShapefileDataStore sds = (ShapefileDataStore) ds;
1308:
1309: try {
1310: layerIndexInfo[i] = useIndex(sds);
1311: } catch (Exception e) {
1312: layerIndexInfo[i] = new IndexInfo(
1313: IndexInfo.TREE_NONE, null, null);
1314: LOGGER.fine("Exception while trying to use index"
1315: + e.getLocalizedMessage());
1316: }
1317: } else {
1318: layerIndexInfo[i] = STREAMING_RENDERER_INFO;
1319: }
1320: }
1321: }
1322:
1323: public void paint(Graphics2D graphics, Rectangle paintArea,
1324: AffineTransform worldToScreen) {
1325: if (worldToScreen == null || paintArea == null) {
1326: LOGGER.info("renderer passed null arguments");
1327: return;
1328: } // Other arguments get checked later
1329: // First, create the bbox in real world coordinates
1330: ReferencedEnvelope mapArea;
1331: try {
1332: mapArea = RendererUtilities.createMapEnvelope(paintArea,
1333: worldToScreen, getContext()
1334: .getCoordinateReferenceSystem());
1335: paint(graphics, paintArea, mapArea, worldToScreen);
1336: } catch (NoninvertibleTransformException e) {
1337: fireErrorEvent(new Exception(
1338: "Can't create pixel to world transform", e));
1339: }
1340: }
1341:
1342: public void paint(Graphics2D graphics, Rectangle paintArea,
1343: ReferencedEnvelope envelope, AffineTransform transform) {
1344:
1345: if (hints != null) {
1346: graphics.setRenderingHints(hints);
1347: }
1348:
1349: if ((graphics == null) || (paintArea == null)) {
1350: LOGGER.info("renderer passed null arguments");
1351:
1352: return;
1353: }
1354:
1355: // reset the abort flag
1356: renderingStopRequested = false;
1357:
1358: if (LOGGER.isLoggable(Level.FINE)) {
1359: LOGGER.fine("Affine Transform is " + transform);
1360: }
1361:
1362: /*
1363: * If we are rendering to a component which has already set up some form of transformation
1364: * then we can concatenate our transformation to it. An example of this is the ZoomPane
1365: * component of the swinggui module.
1366: */
1367: if (concatTransforms) {
1368: AffineTransform atg = graphics.getTransform();
1369:
1370: // graphics.setTransform(new AffineTransform());
1371: atg.concatenate(transform);
1372: transform = atg;
1373: }
1374:
1375: try {
1376: setScaleDenominator(computeScale(envelope, context
1377: .getCoordinateReferenceSystem(), paintArea,
1378: this .rendererHints));
1379: } catch (Exception e) // probably either (1) no CRS (2) error xforming
1380: {
1381: LOGGER
1382: .throwing(
1383: "RendererUtilities",
1384: "calculateScale(envelope, coordinateReferenceSystem, imageWidth, imageHeight, hints)",
1385: e);
1386: setScaleDenominator(1 / transform.getScaleX()); // DJB old method - the best we can do
1387: }
1388:
1389: MapLayer[] layers = context.getLayers();
1390:
1391: // get detstination CRS
1392: CoordinateReferenceSystem destinationCrs = context
1393: .getCoordinateReferenceSystem();
1394: labelCache.start();
1395: labelCache.clear();
1396: if (labelCache instanceof LabelCacheDefault) {
1397: boolean outlineEnabled = TEXT_RENDERING_OUTLINE
1398: .equals(getTextRenderingMethod());
1399: ((LabelCacheDefault) labelCache)
1400: .setOutlineRenderingEnabled(outlineEnabled);
1401: }
1402: for (int i = 0; i < layers.length; i++) {
1403: MapLayer currLayer = layers[i];
1404:
1405: if (!currLayer.isVisible()) {
1406: // Only render layer when layer is visible
1407: continue;
1408: }
1409:
1410: if (renderingStopRequested) {
1411: return;
1412: }
1413:
1414: if (layerIndexInfo[i] == STREAMING_RENDERER_INFO) {
1415: renderWithStreamingRenderer(currLayer, graphics,
1416: paintArea, envelope, transform);
1417: continue;
1418: }
1419: labelCache.startLayer("" + i);
1420:
1421: ReferencedEnvelope bbox = envelope;
1422:
1423: try {
1424: GeometryAttributeType geom = currLayer
1425: .getFeatureSource().getSchema()
1426: .getDefaultGeometry();
1427:
1428: CoordinateReferenceSystem dataCRS;
1429: if (getForceCRSHint() == null)
1430: dataCRS = geom.getCoordinateSystem();
1431: else
1432: dataCRS = getForceCRSHint();
1433:
1434: MathTransform mt;
1435: CoordinateOperation op;
1436:
1437: try {
1438: op = CRS.getCoordinateOperationFactory(true)
1439: .createOperation(dataCRS, destinationCrs);
1440: mt = op.getMathTransform();
1441: bbox = bbox.transform(dataCRS, true, 10);
1442: } catch (Exception e) {
1443: op = null;
1444: mt = null;
1445: }
1446:
1447: MathTransform at = ReferencingFactoryFinder
1448: .getMathTransformFactory(null)
1449: .createAffineTransform(
1450: new GeneralMatrix(transform));
1451:
1452: if (mt == null) {
1453: mt = at;
1454: } else {
1455: mt = ReferencingFactoryFinder
1456: .getMathTransformFactory(null)
1457: .createConcatenatedTransform(mt, at);
1458: }
1459:
1460: // dbfheader must be set so that the attributes required for theming can be read in.
1461: ShapefileDataStore ds = (ShapefileDataStore) currLayer
1462: .getFeatureSource().getDataStore();
1463:
1464: // graphics.setTransform(transform);
1465: // extract the feature type stylers from the style object
1466: // and process them
1467:
1468: Transaction transaction = Transaction.AUTO_COMMIT;
1469:
1470: if (currLayer.getFeatureSource() instanceof FeatureStore) {
1471: transaction = ((FeatureStore) currLayer
1472: .getFeatureSource()).getTransaction();
1473: }
1474:
1475: DefaultQuery query = new DefaultQuery(currLayer
1476: .getQuery());
1477: if (query.getFilter() != null) {
1478: // now reproject the geometries in filter because geoms are retrieved projected to screen space
1479: FilterTransformer transformer = new FilterTransformer(
1480: dataCRS, destinationCrs, mt);
1481: query.setFilter((Filter) query.getFilter().accept(
1482: transformer, null));
1483: }
1484:
1485: // by processing the filter we can further restrict the maximum bounds that are
1486: // required. For example if a filter
1487: //BoundsExtractor extractor=new BoundsExtractor(bbox);
1488: //if( query.getFilter()!=null )
1489: // query.getFilter().accept(extractor);
1490: //
1491: //processStylers(graphics, ds, query, extractor.getIntersection(), paintArea,
1492: // mt, currLayer.getStyle(), layerIndexInfo[i], transaction);
1493: processStylers(graphics, ds, query, bbox, paintArea,
1494: mt, currLayer.getStyle(), layerIndexInfo[i],
1495: transaction, "" + i);
1496: } catch (Exception exception) {
1497: fireErrorEvent(new Exception(
1498: "Exception rendering layer " + currLayer,
1499: exception));
1500: }
1501:
1502: labelCache.endLayer("" + i, graphics, paintArea);
1503: }
1504:
1505: labelCache.end(graphics, paintArea);
1506: LOGGER.fine("Style cache hit ratio: "
1507: + styleFactory.getHitRatio() + " , hits "
1508: + styleFactory.getHits() + ", requests "
1509: + styleFactory.getRequests());
1510: }
1511:
1512: /**
1513: * Returns the text rendering method
1514: */
1515: private String getTextRenderingMethod() {
1516: if (rendererHints == null)
1517: return textRenderingModeDEFAULT;
1518: String result = (String) rendererHints.get(TEXT_RENDERING_KEY);
1519: if (result == null)
1520: return textRenderingModeDEFAULT;
1521: return result;
1522: }
1523:
1524: /**
1525: * <p>
1526: * Returns scale computation algorithm to be used.
1527: * </p>
1528: */
1529: private String getScaleComputationMethod() {
1530: if (rendererHints == null)
1531: return scaleComputationMethodDEFAULT;
1532: String result = (String) rendererHints
1533: .get(SCALE_COMPUTATION_METHOD_KEY);
1534: if (result == null)
1535: return scaleComputationMethodDEFAULT;
1536: return result;
1537: }
1538:
1539: private double computeScale(ReferencedEnvelope envelope,
1540: CoordinateReferenceSystem crs, Rectangle paintArea,
1541: Map hints) {
1542: if (getScaleComputationMethod().equals(SCALE_ACCURATE)) {
1543: try {
1544: return RendererUtilities.calculateScale(envelope,
1545: paintArea.width, paintArea.height, hints);
1546: } catch (Exception e) // probably either (1) no CRS (2) error xforming
1547: {
1548: LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e);
1549: }
1550: }
1551: return RendererUtilities.calculateOGCScale(envelope,
1552: paintArea.width, hints);
1553: }
1554:
1555: private void renderWithStreamingRenderer(MapLayer layer,
1556: Graphics2D graphics, Rectangle paintArea,
1557: ReferencedEnvelope envelope, AffineTransform transform) {
1558: MapContext context = null;
1559: try {
1560: context = new DefaultMapContext(new MapLayer[] { layer },
1561: envelope.getCoordinateReferenceSystem());
1562: StreamingRenderer renderer = new StreamingRenderer();
1563: renderer.setContext(context);
1564: renderer.setJava2DHints(getJava2DHints());
1565: Map rendererHints2 = new HashMap(
1566: getRendererHints() != null ? getRendererHints()
1567: : Collections.EMPTY_MAP);
1568: rendererHints2.put(LABEL_CACHE_KEY,
1569: new IntegratingLabelCache(labelCache));
1570: renderer.setRendererHints(rendererHints2);
1571: renderer.paint(graphics, paintArea, envelope, transform);
1572: } finally {
1573: if (context != null)
1574: context.clearLayerList();
1575: }
1576: }
1577:
1578: /**
1579: * If the forceCRS hint is set then return the value.
1580: * @return the value of the forceCRS hint or null
1581: */
1582: private CoordinateReferenceSystem getForceCRSHint() {
1583: if (rendererHints == null)
1584: return null;
1585: Object crs = this .rendererHints.get("forceCRS");
1586: if (crs instanceof CoordinateReferenceSystem)
1587: return (CoordinateReferenceSystem) crs;
1588:
1589: return null;
1590: }
1591:
1592: /**
1593: * @deprecated
1594: */
1595: public void paint(Graphics2D graphics, Rectangle paintArea,
1596: Envelope mapArea) {
1597: paint(graphics, paintArea, new ReferencedEnvelope(mapArea,
1598: context.getCoordinateReferenceSystem()));
1599: }
1600:
1601: /**
1602: * @deprecated
1603: */
1604: public void paint(Graphics2D graphics, Rectangle paintArea,
1605: Envelope mapArea, AffineTransform worldToScreen) {
1606: paint(graphics, paintArea, new ReferencedEnvelope(mapArea,
1607: context.getCoordinateReferenceSystem()), worldToScreen);
1608: }
1609: }
|