/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.renderer.shape;
import static org.geotools.data.shapefile.ShpFileType.GRX;
import static org.geotools.data.shapefile.ShpFileType.QIX;
import static org.geotools.data.shapefile.ShpFileType.SHX;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.FactoryConfigurationError;
import org.geotools.data.DataStore;
import org.geotools.data.DefaultQuery;
import org.geotools.data.Diff;
import org.geotools.data.FIDReader;
import org.geotools.data.FeatureStore;
import org.geotools.data.Query;
import org.geotools.data.Transaction;
import org.geotools.data.TransactionStateDiff;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileRendererUtil;
import org.geotools.data.shapefile.ShpFiles;
import org.geotools.data.shapefile.dbf.DbaseFileHeader;
import org.geotools.data.shapefile.dbf.DbaseFileReader;
import org.geotools.data.shapefile.dbf.IndexedDbaseFileReader;
import org.geotools.data.shapefile.indexed.IndexType;
import org.geotools.data.shapefile.shp.ShapeType;
import org.geotools.data.shapefile.shp.ShapefileReader;
import org.geotools.data.shapefile.shp.ShapefileReader.Record;
import org.geotools.feature.FeatureTypes;
import org.geotools.feature.SchemaException;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.filter.FilterAttributeExtractor;
import org.geotools.geometry.jts.Decimator;
import org.geotools.geometry.jts.LiteCoordinateSequenceFactory;
import org.geotools.geometry.jts.LiteShape2;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.index.quadtree.StoreException;
import org.geotools.map.DefaultMapContext;
import org.geotools.map.MapContext;
import org.geotools.map.MapLayer;
import org.geotools.referencing.CRS;
import org.geotools.referencing.ReferencingFactoryFinder;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.referencing.operation.matrix.GeneralMatrix;
import org.geotools.referencing.operation.matrix.XAffineTransform;
import org.geotools.renderer.GTRenderer;
import org.geotools.renderer.RenderListener;
import org.geotools.renderer.label.LabelCacheImpl;
import org.geotools.renderer.lite.LabelCache;
import org.geotools.renderer.lite.RendererUtilities;
import org.geotools.renderer.lite.StreamingRenderer;
import org.geotools.renderer.style.SLDStyleFactory;
import org.geotools.renderer.style.Style2D;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.LineSymbolizer;
import org.geotools.styling.PointSymbolizer;
import org.geotools.styling.PolygonSymbolizer;
import org.geotools.styling.Rule;
import org.geotools.styling.Style;
import org.geotools.styling.StyleAttributeExtractor;
import org.geotools.styling.Symbolizer;
import org.geotools.styling.TextSymbolizer;
import org.geotools.styling.visitor.DuplicatingStyleVisitor;
import org.geotools.util.NumberRange;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.filter.Filter;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
/**
* A LiteRenderer Implementations that is optimized for shapefiles.
*
* @author jeichar
* @since 2.1.x
* @source $URL:
* http://svn.geotools.org/geotools/branches/2.2.x/ext/shaperenderer/src/org/geotools/renderer/shape/ShapefileRenderer.java $
*/
public class ShapefileRenderer implements GTRenderer {
public static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.renderer.shape");
/** Tolerance used to compare doubles for equality */
private static final double TOLERANCE = 1e-6;
private static final GeometryFactory geomFactory = new GeometryFactory(
new LiteCoordinateSequenceFactory());
private static final Coordinate[] COORDS;
private static final MultiPolygon MULTI_POLYGON_GEOM;
private static final Polygon POLYGON_GEOM;
private static final LinearRing LINE_GEOM;
private static final MultiLineString MULTI_LINE_GEOM;
private static final Point POINT_GEOM;
private static final MultiPoint MULTI_POINT_GEOM;
/**
* Computes the scale as the ratio between map distances and real world distances,
* assuming 90dpi and taking into consideration projection deformations and actual
* earth shape. <br>
* Use this method only when in need of accurate computation. Will break if the
* data extent is outside of the currenct projection definition area.
*/
public static final String SCALE_ACCURATE = "ACCURATE";
/**
* Very simple and lenient scale computation method that conforms to the OGC SLD
* specification 1.0, page 26. <br>This method is quite approximative, but should
* never break and ensure constant scale even on lat/lon unprojected maps (because
* in that case scale is computed as if the area was along the equator no matter
* what the real position is).
*/
public static final String SCALE_OGC = "OGC";
private String scaleComputationMethodDEFAULT = SCALE_ACCURATE;
static {
COORDS = new Coordinate[5];
COORDS[0] = new Coordinate(0.0, 0.0);
COORDS[1] = new Coordinate(5.0, 0.0);
COORDS[2] = new Coordinate(5.0, 5.0);
COORDS[3] = new Coordinate(0.0, 5.0);
COORDS[4] = new Coordinate(0.0, 0.0);
LINE_GEOM = geomFactory.createLinearRing(COORDS);
MULTI_LINE_GEOM = geomFactory.createMultiLineString(new LineString[]{LINE_GEOM});
POLYGON_GEOM = geomFactory.createPolygon(LINE_GEOM, new LinearRing[0]);
MULTI_POLYGON_GEOM = geomFactory.createMultiPolygon(new Polygon[]{POLYGON_GEOM});
POINT_GEOM = geomFactory.createPoint(COORDS[2]);
MULTI_POINT_GEOM = geomFactory.createMultiPoint(COORDS);
}
/**
* This listener is added to the list of listeners automatically. It should be removed if the
* default logging is not needed.
*/
public static final DefaultRenderListener DEFAULT_LISTENER = new DefaultRenderListener();
private static final IndexInfo STREAMING_RENDERER_INFO = new IndexInfo(IndexType.NONE,null);
static int NUM_SAMPLES = 200;
private RenderingHints hints;
/** Factory that will resolve symbolizers into rendered styles */
private SLDStyleFactory styleFactory = new SLDStyleFactory();
private boolean renderingStopRequested;
private boolean concatTransforms;
private MapContext context;
LabelCache labelCache = new LabelCacheImpl();
private List<RenderListener> renderListeners = new CopyOnWriteArrayList<RenderListener>();
/** If we are caching styles; by default this is false */
boolean caching = false;
private double scaleDenominator;
DbaseFileHeader dbfheader;
private Object defaultGeom;
IndexInfo[] layerIndexInfo;
StreamingRenderer delegate;
/**
* Maps between the AttributeType index of the new generated FeatureType and the real
* attributeType
*/
int[] attributeIndexing;
/** The painter class we use to depict shapes onto the screen */
private StyledShapePainter painter = new StyledShapePainter(labelCache);
private Map decimators = new HashMap();
/**
* Text will be rendered using the usual calls gc.drawString/drawGlyphVector.
* This is a little faster, and more consistent with how the platform renders
* the text in other applications. The downside is that on most platform the label
* and its eventual halo are not properly centered.
*/
public static final String TEXT_RENDERING_STRING = "STRING";
/**
* Text will be rendered using the associated {@link GlyphVector} outline, that is, a {@link Shape}.
* This ensures perfect centering between the text and the halo, but introduces more text aliasing.
*/
public static final String TEXT_RENDERING_OUTLINE = "OUTLINE";
/**
* The text rendering method, either TEXT_RENDERING_OUTLINE or TEXT_RENDERING_STRING
*/
public static final String TEXT_RENDERING_KEY = "textRenderingMethod";
private String textRenderingModeDEFAULT = TEXT_RENDERING_STRING;
public static final String LABEL_CACHE_KEY = "labelCache";
public static final String FORCE_CRS_KEY = "forceCRS";
public static final String DPI_KEY = "dpi";
public static final String DECLARED_SCALE_DENOM_KEY = "declaredScaleDenominator";
public static final String MEMORY_PRE_LOADING_KEY = "memoryPreloadingEnabled";
public static final String OPTIMIZED_DATA_LOADING_KEY = "optimizedDataLoadingEnabled";
public static final String SCALE_COMPUTATION_METHOD_KEY = "scaleComputationMethod";
/**
* "optimizedDataLoadingEnabled" - Boolean yes/no (see default optimizedDataLoadingEnabledDEFAULT)
* "memoryPreloadingEnabled" - Boolean yes/no (see default memoryPreloadingEnabledDEFAULT)
* "declaredScaleDenominator" - Double the value of the scale denominator to use by the renderer.
* by default the value is calculated based on the screen size
* and the displayed area of the map.
* "dpi" - Integer number of dots per inch of the display 90 DPI is the default (as declared by OGC)
* "forceCRS" - CoordinateReferenceSystem declares to the renderer that all layers are of the CRS declared in this hint
* "labelCache" - Declares the label cache that will be used by the renderer.
*/
private Map rendererHints = null;
public ShapefileRenderer( MapContext context ) {
setContext(context);
}
public ShapefileRenderer() {
}
public void paint( Graphics2D graphics, Rectangle paintArea, ReferencedEnvelope mapArea ) {
if (mapArea == null || paintArea == null) {
LOGGER.info("renderer passed null arguments");
return;
} // Other arguments get checked later
paint(graphics, paintArea, mapArea, RendererUtilities.worldToScreenTransform(mapArea,
paintArea));
}
private DbaseFileHeader getDBFHeader( ShapefileDataStore ds ) {
DbaseFileReader reader = null;
try {
reader = ShapefileRendererUtil.getDBFReader(ds);
return reader.getHeader();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return null;
}
private void processStylers( Graphics2D graphics, ShapefileDataStore datastore,
Query query, Envelope bbox, Rectangle screenSize, MathTransform mt, Style style, IndexInfo info,
Transaction transaction, String layerId) throws IOException {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("processing " + style.getFeatureTypeStyles().length + " stylers");
}
FeatureTypeStyle[] featureStylers = style.getFeatureTypeStyles();
SimpleFeatureType type;
try {
type = createFeatureType(query, style, datastore);
} catch (Exception e) {
fireErrorEvent(e);
LOGGER.logp(Level.WARNING, "org.geotools.renderer.shape.ShapefileRenderer", "processStylers", "Could not prep style for rendering", e);
return;
}
for( int i = 0; i < featureStylers.length; i++ ) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("processing style " + i);
}
FeatureTypeStyle fts = featureStylers[i];
String typeName = datastore.getSchema().getTypeName();
if ((typeName != null) &&
( FeatureTypes.isDecendedFrom(datastore.getSchema(), null, fts.getFeatureTypeName())
|| typeName .equalsIgnoreCase(fts.getFeatureTypeName()))) {
// get applicable rules at the current scale
Rule[] rules = fts.getRules();
List ruleList = new ArrayList();
List elseRuleList = new ArrayList();
// TODO process filter for geometry expressions and restrict bbox further based on
// the result
for( int j = 0; j < rules.length; j++ ) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("processing rule " + j);
}
Rule r = rules[j];
Filter f = r.getFilter();
if(f != null) {
GeometryFilterChecker checker = new GeometryFilterChecker();
f.accept(checker, null);
// geometry filters are quite unlikely in SLD, but if we have any,
// we need to reproject it to screen space since geometries are
// read directly in screen space
if(checker.isGeometryFilterPresent()) {
// make copy so we don't modify the style
DuplicatingStyleVisitor duplicator = new DuplicatingStyleVisitor();
r.accept(duplicator);
r=(Rule) duplicator.getCopy();
FilterTransformer transformer= new FilterTransformer(mt);
r.setFilter((Filter) r.getFilter().accept(transformer, null));
}
}
if (isWithInScale(r)) {
if (r.hasElseFilter()) {
elseRuleList.add(r);
} else {
ruleList.add(r);
}
}
}
// process the features according to the rules
// TODO: find a better way to declare the scale ranges so that
// we
// get style caching also between multiple rendering runs
NumberRange scaleRange = new NumberRange(scaleDenominator, scaleDenominator);
Set modifiedFIDs = processTransaction(graphics, bbox, mt, datastore, transaction,
typeName, query, ruleList, elseRuleList, scaleRange, layerId);
// don't try to read the shapefile if there is nothing to draw
if(ruleList.size() > 0 || elseRuleList.size() > 0)
processShapefile(graphics, datastore, bbox,screenSize, mt, info, type, query, ruleList,
elseRuleList, modifiedFIDs, scaleRange, layerId);
}
}
}
private Set processTransaction( Graphics2D graphics, Envelope bbox, MathTransform transform,
DataStore ds, Transaction transaction, String typename, Query query, List ruleList,
List elseRuleList, NumberRange scaleRange, String layerId ) {
if (transaction == Transaction.AUTO_COMMIT) {
return Collections.EMPTY_SET;
}
TransactionStateDiff state = (TransactionStateDiff) transaction.getState(ds);
if (state == null) {
return Collections.EMPTY_SET;
}
// set of fids that has been modified (ie updated or deleted)
Set fids = new HashSet();
Map modified = null;
Map added = null;
Diff diff=null;
try {
diff = state.diff(typename);
modified = diff.modified2;
added = diff.added;
fids = new HashSet();
} catch (IOException e) {
fids = Collections.EMPTY_SET;
return fids;
}
if (!diff.isEmpty()) {
SimpleFeature feature;
for( Iterator modifiedIter = modified.keySet().iterator(),
addedIter=added.values().iterator();
modifiedIter.hasNext() || addedIter.hasNext(); ) {
try {
if (renderingStopRequested) {
break;
}
boolean doElse = true;
if( modifiedIter.hasNext() ){
String fid= (String) modifiedIter.next();
feature = (SimpleFeature) modified.get(fid);
fids.add(fid);
} else {
feature = (SimpleFeature) addedIter.next();
}
if( feature == TransactionStateDiff.NULL){
continue; // skip this feature as it is removed
}
if (!query.getFilter().evaluate(feature)){
// currently this is failing for TransactionStateDiff.NULL
continue;
}
// applicable rules
for( Iterator it = ruleList.iterator(); it.hasNext(); ) {
Rule r = (Rule) it.next();
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("applying rule: " + r.toString());
}
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("this rule applies ...");
}
Filter filter = r.getFilter();
if ((filter == null) || filter.evaluate(feature)) {
doElse = false;
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("processing Symobolizer ...");
}
Symbolizer[] symbolizers = r.getSymbolizers();
try {
processSymbolizers(graphics, feature, symbolizers, scaleRange,
transform, layerId);
} catch (Exception e) {
fireErrorEvent(e);
continue;
}
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("... done!");
}
}
}
if (doElse) {
// rules with an else filter
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("rules with an else filter");
}
for( Iterator it = elseRuleList.iterator(); it.hasNext(); ) {
Rule r = (Rule) it.next();
Symbolizer[] symbolizers = r.getSymbolizers();
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("processing Symobolizer ...");
}
try {
processSymbolizers(graphics, feature, symbolizers, scaleRange,
transform, layerId);
} catch (Exception e) {
fireErrorEvent(e);
continue;
}
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("... done!");
}
}
}
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("feature rendered event ...");
}
}
catch (RuntimeException e) {
fireErrorEvent(e);
}
}
}
return fids;
}
private void processShapefile( Graphics2D graphics, ShapefileDataStore datastore,
Envelope bbox, Rectangle screenSize, MathTransform mt, IndexInfo info, SimpleFeatureType type, Query query,
List ruleList, List elseRuleList, Set modifiedFIDs, NumberRange scaleRange, String layerId )
throws IOException {
IndexedDbaseFileReader dbfreader = null;
// clips Graphics to current drawing area before painting
graphics = (Graphics2D)graphics.create();
graphics.clip(screenSize);
// don't waste time processing the dbf file if the only attribute loades is the geometry
if(type.getAttributeCount() > 1) {
try {
dbfreader = ShapefileRendererUtil.getDBFReader(datastore);
} catch (Exception e) {
fireErrorEvent(e);
}
}
OpacityFinder opacityFinder = new OpacityFinder(getAcceptableSymbolizers(type
.getGeometryDescriptor()));
for( Iterator iter = ruleList.iterator(); iter.hasNext(); ) {
Rule rule = (Rule) iter.next();
rule.accept(opacityFinder);
}
IndexInfo.Reader shpreader = null;
boolean useJTS=true;
try {
shpreader = new IndexInfo.Reader(info, ShapefileRendererUtil.getShpReader(datastore,
bbox, screenSize, mt, opacityFinder.hasOpacity, useJTS), bbox);
} catch (Exception e) {
fireErrorEvent(e);
return;
}
FIDReader fidReader = null;
try {
fidReader = ShapefileRendererUtil.getFidReader(datastore,shpreader);
} catch (Exception e) {
fireErrorEvent(e);
return;
}
SimpleFeatureBuilder fbuilder = new SimpleFeatureBuilder(type);
try {
while( true ) {
try {
if (renderingStopRequested) {
break;
}
if (!shpreader.hasNext()) {
break;
}
boolean doElse = true;
String nextFid = null;
if( fidReader.hasNext() ){
try {
nextFid = fidReader.next();
}
catch( NoSuchElementException invalidIndex){
fireErrorEvent(new IllegalStateException("Skipping invalid FID; Please regenerate your index.", invalidIndex));
// TODO: mark index as needing regeneration
}
}
else {
fireErrorEvent(new IllegalStateException("Skipping invalid FID; shape and index are out of sync please regenerate index."));
// TODO: mark index as needing regeneration
}
if(LOGGER.isLoggable(Level.FINER))
LOGGER.finer("trying to read geometry ...");
if (nextFid == null || modifiedFIDs.contains(nextFid)) {
// this one is modified we will get it when we processTransaction
shpreader.next();
if( dbfreader != null && !dbfreader.IsRandomAccessEnabled() ){
dbfreader.skip();
}
continue;
}
if( dbfreader != null && dbfreader.IsRandomAccessEnabled() ){
dbfreader.goTo(shpreader.getRecordNumber());
}
ShapefileReader.Record record = shpreader.next();
Object geom = record.shape();
if (geom == null) {
if(LOGGER.isLoggable(Level.FINEST))
LOGGER.finest("skipping geometry");
if( dbfreader != null && !dbfreader.IsRandomAccessEnabled() )
dbfreader.skip();
continue;
}
SimpleFeature feature = createFeature(fbuilder, record, dbfreader, nextFid);
if (!query.getFilter().evaluate(feature))
continue;
if (renderingStopRequested) {
break;
}
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("... done: " + geom.toString());
}
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.fine("... done: " + type.getTypeName());
}
// applicable rules
for( Iterator it = ruleList.iterator(); it.hasNext(); ) {
Rule r = (Rule) it.next();
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("applying rule: " + r.toString());
}
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("this rule applies ...");
}
Filter filter = r.getFilter();
if ((filter == null) || filter.evaluate(feature)) {
doElse = false;
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("processing Symobolizer ...");
}
Symbolizer[] symbolizers = r.getSymbolizers();
processSymbolizers(graphics, feature, geom, symbolizers, scaleRange, useJTS, layerId);
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("... done!");
}
}
}
if (doElse) {
// rules with an else filter
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("rules with an else filter");
}
for( Iterator it = elseRuleList.iterator(); it.hasNext(); ) {
Rule r = (Rule) it.next();
Symbolizer[] symbolizers = r.getSymbolizers();
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("processing Symobolizer ...");
}
processSymbolizers(graphics, feature, geom, symbolizers, scaleRange, useJTS, layerId);
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("... done!");
}
}
}
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("feature rendered event ...");
}
} catch (Exception e) {
fireErrorEvent(e);
}
}
} finally {
try {
if (dbfreader != null) {
dbfreader.close();
}
} finally {
try {
if (shpreader != null) {
shpreader.close();
}
} finally {
if (fidReader != null)
fidReader.close();
}
}
}
}
private Class[] getAcceptableSymbolizers( GeometryDescriptor defaultGeometry ) {
Class binding = defaultGeometry.getType().getBinding();
if (Polygon.class.isAssignableFrom(binding)
|| MultiPolygon.class.isAssignableFrom(binding)) {
return new Class[]{PointSymbolizer.class, LineSymbolizer.class, PolygonSymbolizer.class};
}
return new Class[]{PointSymbolizer.class, LineSymbolizer.class};
}
SimpleFeature createFeature(SimpleFeatureBuilder builder, Record record, DbaseFileReader dbfreader, String id )
throws Exception {
SimpleFeatureType type = builder.getFeatureType();
if (type.getAttributeCount() == 1) {
builder.add(getGeom(record.shape(), type.getGeometryDescriptor()));
return builder.buildFeature(id);
} else {
dbfreader.read();
for( int i = 0; i < (type.getAttributeCount() - 1); i++ ) {
builder.add(dbfreader.readField(attributeIndexing[i]));
}
builder.add(getGeom(record.shape(), type.getGeometryDescriptor()));
return builder.buildFeature(id);
}
}
/**
* Return provided geom; or use a default value if null.
*
* @param geom Provided Geometry as read from record.shape()
* @param defaultGeometry GeometryDescriptor used to determine default value
* @return provided geom or default value if null
*/
private Object getGeom( Object geom, GeometryDescriptor defaultGeometry ) {
if( geom instanceof Geometry){
return geom;
}
return getGeom( defaultGeometry );
}
/**
* This class keeps a couple of default geometries on hand to use
* when making a feature with default values.
*
* @param defaultGeometry
* @return placeholder to use as a default while waiting for a real geometry.
*/
private Object getGeom(GeometryDescriptor defaultGeometry) {
Class binding = defaultGeometry.getType().getBinding();
if (MultiPolygon.class.isAssignableFrom(binding)) {
return MULTI_POLYGON_GEOM;
}
else if (MultiLineString.class.isAssignableFrom(binding)) {
return MULTI_LINE_GEOM;
}
else if (Point.class.isAssignableFrom(binding)) {
return POINT_GEOM;
}
else if (MultiPoint.class.isAssignableFrom(binding)) {
return MULTI_POINT_GEOM;
}
return null; // we don't have a good default value - null will need to do
}
/**
* DOCUMENT ME!
*
* @param query
* @param style
* @param schema DOCUMENT ME!
* @return
* @throws FactoryConfigurationError
* @throws SchemaException
*/
SimpleFeatureType createFeatureType( Query query, Style style, ShapefileDataStore ds)
throws SchemaException, IOException {
SimpleFeatureType schema = ds.getSchema();
String[] attributes = findStyleAttributes((query == null) ? Query.ALL : query, style,
schema);
AttributeDescriptor[] types = new AttributeDescriptor[attributes.length];
attributeIndexing = new int[attributes.length];
if (attributes.length == 1
&& attributes[0].equals(schema.getGeometryDescriptor().getLocalName())) {
types[0] = schema.getDescriptor(attributes[0]);
// the symbolizer might be referring "THE_GEOM" for example
if (types[0] == null)
throw new IllegalArgumentException("Attribute " + attributes[0]
+ " does not exist. Maybe it has just been spelled wrongly?");
} else {
dbfheader = getDBFHeader(ds);
for( int i = 0; i < types.length; i++ ) {
types[i] = schema.getDescriptor(attributes[i]);
if (types[i] == null)
throw new IllegalArgumentException("Attribute " + attributes[i]
+ " does not exist. Maybe it has just been spelled wrongly?");
for( int j = 0; j < dbfheader.getNumFields(); j++ ) {
if (dbfheader.getFieldName(j).equals(attributes[i])) {
attributeIndexing[i] = j;
break;
}
}
}
}
SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder();
tb.setName( schema.getName() );
tb.addAll( types );
tb.setDefaultGeometry( schema.getGeometryDescriptor().getLocalName() );
return tb.buildFeatureType();
}
/**
* Inspects the <code>MapLayer</code>'s style and retrieves it's needed attribute names,
* returning at least the default geometry attribute name.
*
* @param query DOCUMENT ME!
* @param style the <code>Style</code> to determine the needed attributes from
* @param schema the FeatureSource<SimpleFeatureType, SimpleFeature> schema
* @return the minimun set of attribute names needed to render <code>layer</code>
*/
private String[] findStyleAttributes( final Query query, Style style, SimpleFeatureType schema ) {
StyleAttributeExtractor sae = new StyleAttributeExtractor();
sae.visit(style);
FilterAttributeExtractor qae = new FilterAttributeExtractor();
query.getFilter().accept(qae,null);
Set ftsAttributes = new LinkedHashSet(sae.getAttributeNameSet());
ftsAttributes.addAll(qae.getAttributeNameSet());
if (sae.getDefaultGeometryUsed()
&& (!ftsAttributes.contains(schema.getGeometryDescriptor().getLocalName()))) {
ftsAttributes.add(schema.getGeometryDescriptor().getLocalName());
} else {
// the code following assumes the geometry column is the last one
// make sure it's the last for good
ftsAttributes.remove(schema.getGeometryDescriptor().getLocalName());
ftsAttributes.add(schema.getGeometryDescriptor().getLocalName());
}
return (String[]) ftsAttributes.toArray(new String[0]);
}
/**
* DOCUMENT ME!
*
* @param graphics
* @param feature DOCUMENT ME!
* @param geom
* @param symbolizers
* @param scaleRange
* @param layerId
*/
private void processSymbolizers( Graphics2D graphics, SimpleFeature feature, Object geom,
Symbolizer[] symbolizers, NumberRange scaleRange, boolean isJTS, String layerId ) {
for( int m = 0; m < symbolizers.length; m++ ) {
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("applying symbolizer " + symbolizers[m]);
}
if (renderingStopRequested) {
break;
}
if (symbolizers[m] instanceof TextSymbolizer) {
try {
labelCache.put(layerId,(TextSymbolizer) symbolizers[m],
feature,
new LiteShape2((Geometry)feature.getDefaultGeometry(), null, null, false, false),
scaleRange);
} catch (Exception e) {
fireErrorEvent(e);
}
} else {
Shape shape;
try {
Style2D style = styleFactory.createStyle(feature, symbolizers[m], scaleRange);
if( isJTS ){
Geometry g;
if(symbolizers[m] instanceof PointSymbolizer) {
g = RendererUtilities.getCentroid((Geometry) geom);
} else {
g = (Geometry) geom;
}
shape = new LiteShape2(g, null, null, false, false);
painter.paint(graphics, shape, style, scaleDenominator);
}else{
if(symbolizers[m] instanceof PointSymbolizer) {
shape = new LiteShape2(RendererUtilities.getCentroid((Geometry) feature.getDefaultGeometry()), null, null, false, false);
} else {
shape = getShape((SimpleGeometry) geom);
}
painter.paint(graphics, shape, style, scaleDenominator);
}
} catch (Exception e) {
fireErrorEvent(e);
}
}
}
fireFeatureRenderedEvent(feature);
}
/**
* Applies each of a set of symbolizers in turn to a given feature.
* <p>
* This is an internal method and should only be called by processStylers.
* </p>
*
* @param graphics
* @param feature The feature to be rendered
* @param symbolizers An array of symbolizers which actually perform the rendering.
* @param scaleRange The scale range we are working on... provided in order to make the style
* factory happy
* @param transform DOCUMENT ME!
* @param layerId
* @throws TransformException
* @throws FactoryException
*/
private void processSymbolizers( final Graphics2D graphics, final SimpleFeature feature,
final Symbolizer[] symbolizers, NumberRange scaleRange, MathTransform transform, String layerId )
throws TransformException, FactoryException {
LiteShape2 shape;
for( int m = 0; m < symbolizers.length; m++ ) {
if (LOGGER.isLoggable(Level.FINER)) {
LOGGER.finer("applying symbolizer " + symbolizers[m]);
}
Geometry g = (Geometry) feature.getDefaultGeometry();
if(symbolizers[m] instanceof PointSymbolizer)
g = RendererUtilities.getCentroid(g);
shape = new LiteShape2(g, transform, getDecimator(transform), false);
if (symbolizers[m] instanceof TextSymbolizer) {
labelCache.put(layerId, (TextSymbolizer) symbolizers[m], feature, shape, scaleRange);
} else {
Style2D style = styleFactory.createStyle(feature, symbolizers[m], scaleRange);
painter.paint(graphics, shape, style, scaleDenominator);
}
}
fireFeatureRenderedEvent(feature);
}
/**
* DOCUMENT ME!
*
* @param mathTransform DOCUMENT ME!
* @return
* @throws org.opengis.referencing.operation.NoninvertibleTransformException
*/
private Decimator getDecimator( MathTransform mathTransform )
throws org.opengis.referencing.operation.NoninvertibleTransformException {
Decimator decimator=null;
if( mathTransform!=null )
decimator = (Decimator) decimators.get(mathTransform);
if (decimator == null) {
decimator = new Decimator(mathTransform.inverse());
decimators.put(mathTransform, decimator);
}
return decimator;
}
//
// /**
// * Creates a JTS shape that is an approximation of the SImpleGeometry. This is ONLY use for
// * labelling and is only created if a text symbolizer is part of the current style.
// *
// * @param geom the geometry to wrap
// * @return
// * @throws TransformException
// * @throws FactoryException
// * @throws RuntimeException DOCUMENT ME!
// */
// LiteShape2 getLiteShape2( SimpleGeometry geom ) throws TransformException, FactoryException {
// Geometry jtsGeom;
// if ((geom.type == ShapeType.POLYGON) || (geom.type == ShapeType.POLYGONM)
// || (geom.type == ShapeType.POLYGONZ)) {
// double[] points = getPointSample(geom, true);
// CoordinateSequence seq = new LiteCoordinateSequence(points);
// Polygon poly;
//
// try {
// poly = geomFactory.createPolygon(geomFactory.createLinearRing(seq),
// new LinearRing[]{});
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
//
// jtsGeom = geomFactory.createMultiPolygon(new Polygon[]{poly});
// } else if ((geom.type == ShapeType.ARC) || (geom.type == ShapeType.ARCM)
// || (geom.type == ShapeType.ARCZ)) {
// double[] points = getPointSample(geom, false);
// CoordinateSequence seq = new LiteCoordinateSequence(points);
// jtsGeom = geomFactory.createMultiLineString(new LineString[]{geomFactory
// .createLineString(seq)});
// } else if ((geom.type == ShapeType.MULTIPOINT) || (geom.type == ShapeType.MULTIPOINTM)
// || (geom.type == ShapeType.MULTIPOINTZ)) {
// double[] points = getPointSample(geom, false);
// CoordinateSequence seq = new LiteCoordinateSequence(points);
// jtsGeom = geomFactory.createMultiPoint(seq);
// } else {
// jtsGeom = geomFactory.createPoint(new Coordinate(geom.coords[0][0], geom.coords[0][1]));
// }
//
// LiteShape2 shape = new LiteShape2(jtsGeom, null, null, false);
//
// return shape;
// }
// /**
// * takes a random sampling from the geometry. Only uses the larges part of the geometry.
// *
// * @param geom
// * @param isPolygon DOCUMENT ME!
// * @return
// */
// private double[] getPointSample( SimpleGeometry geom, boolean isPolygon ) {
// int largestPart = 0;
//
// for( int i = 0; i < geom.coords.length; i++ ) {
// if (geom.coords[i].length > geom.coords[largestPart].length) {
// largestPart = i;
// }
// }
//
// return geom.coords[largestPart];
// }
/**
* DOCUMENT ME!
*
* @param geom
* @return
*/
private Shape getShape( SimpleGeometry geom ) {
if ((geom.type == ShapeType.ARC) || (geom.type == ShapeType.ARCM)
|| (geom.type == ShapeType.ARCZ)) {
return new MultiLineShape(geom);
}
if ((geom.type == ShapeType.POLYGON) || (geom.type == ShapeType.POLYGONM)
|| (geom.type == ShapeType.POLYGONZ)) {
return new PolygonShape(geom);
}
if ((geom.type == ShapeType.POINT) || (geom.type == ShapeType.POINTM)
|| (geom.type == ShapeType.POINTZ) || (geom.type == ShapeType.MULTIPOINT)
|| (geom.type == ShapeType.MULTIPOINTM) || (geom.type == ShapeType.MULTIPOINTZ)) {
return new MultiPointShape(geom);
}
return null;
}
/**
* Checks if a rule can be triggered at the current scale level
*
* @param r The rule
* @return true if the scale is compatible with the rule settings
*/
private boolean isWithInScale( Rule r ) {
return ((r.getMinScaleDenominator() - TOLERANCE) <= scaleDenominator)
&& ((r.getMaxScaleDenominator() + TOLERANCE) > scaleDenominator);
}
/**
* adds a listener that responds to error events of feature rendered events.
*
* @param listener the listener to add.
* @see RenderListener
*/
public void addRenderListener( RenderListener listener ) {
renderListeners.add(listener);
}
/**
* Removes a render listener.
*
* @param listener the listener to remove.
* @see RenderListener
*/
public void removeRenderListener( RenderListener listener ) {
renderListeners.remove(listener);
}
private void fireFeatureRenderedEvent( SimpleFeature feature ) {
if (renderListeners.size() > 0) {
RenderListener listener;
for (int i = 0; i < renderListeners.size(); i++) {
listener = renderListeners.get(i);
listener.featureRenderer((SimpleFeature) feature);
}
}
}
private void fireErrorEvent(Exception e) {
if (renderListeners.size() > 0) {
RenderListener listener;
for (int i = 0; i < renderListeners.size(); i++) {
try {
listener = renderListeners.get(i);
listener.errorOccurred(e);
} catch (RuntimeException ignore) {
LOGGER.fine("Provided RenderListener could not handle error message:" + ignore);
LOGGER.throwing(getClass().getName(), "fireErrorEvent", ignore);
}
}
}
}
/**
* Setter for property scaleDenominator.
*
* @param scaleDenominator New value of property scaleDenominator.
*/
protected void setScaleDenominator( double scaleDenominator ) {
this.scaleDenominator = scaleDenominator;
}
/**
* If you call this method from another thread than the one that called <code>paint</code> or
* <code>render</code> the rendering will be forcefully stopped before termination
*/
public void stopRendering() {
try {
if(delegate != null)
delegate.stopRendering();
} catch(NullPointerException e) {
// Since stopRendering is called by another thread the null check may
// pass, and the method call can NPE nevertheless. It's ok, in that
// case rendering is done anyways
}
renderingStopRequested = true;
labelCache.stop();
}
/**
* True if we are caching styles.
*
* @return <code>ture </code>if caching
*/
public boolean isCaching() {
return caching;
}
/**
* Set to true to cache styles.
*
* @param caching The caching to set.
*/
public void setCaching( boolean caching ) {
this.caching = caching;
}
public MapContext getContext() {
return context;
}
public boolean isConcatTransforms() {
return concatTransforms;
}
public void setConcatTransforms( boolean concatTransforms ) {
this.concatTransforms = concatTransforms;
}
public IndexInfo useIndex( ShapefileDataStore ds ) throws IOException, StoreException {
IndexInfo info;
ShpFiles shpFiles = ShapefileRendererUtil.getShpFiles(ds);
if (ds.isLocal()) {
if (!shpFiles.exists(SHX)) {
info = new IndexInfo(IndexType.NONE, shpFiles);
LOGGER.fine("No indexing");
} else if (shpFiles.exists(QIX)) {
info = new IndexInfo(IndexType.QIX, shpFiles);
LOGGER.fine("Using quad tree");
} else {
info = new IndexInfo(IndexType.NONE, shpFiles);
LOGGER.fine("No indexing");
}
} else {
info = new IndexInfo(IndexType.NONE, shpFiles);
LOGGER.fine("No indexing");
}
return info;
}
/**
* By default ignores all feature renderered events and logs all exceptions as severe.
*/
private static class DefaultRenderListener implements RenderListener {
/**
* @see org.geotools.renderer.lite.RenderListener#featureRenderer(org.geotools.feature.Feature)
*/
public void featureRenderer( SimpleFeature feature ) {
// do nothing.
}
/**
* @see org.geotools.renderer.lite.RenderListener#errorOccurred(java.lang.Exception)
*/
public void errorOccurred( Exception e ) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
}
}
public void setJava2DHints( RenderingHints hints ) {
this.hints = hints;
}
public RenderingHints getJava2DHints() {
return hints;
}
public void setRendererHints(Map hints) {
if( hints!=null && hints.containsKey(LABEL_CACHE_KEY) ){
LabelCache cache=(LabelCache) hints.get(LABEL_CACHE_KEY);
if( cache==null )
throw new NullPointerException("Label_Cache_Hint has a null value for the labelcache");
this.labelCache=cache;
this.painter=new StyledShapePainter(cache);
}
if(hints != null && hints.containsKey(StreamingRenderer.LINE_WIDTH_OPTIMIZATION_KEY)) {
styleFactory.setLineOptimizationEnabled(Boolean.TRUE.equals(hints.get(StreamingRenderer.LINE_WIDTH_OPTIMIZATION_KEY)));
}
if(hints != null && hints.containsKey(StreamingRenderer.VECTOR_RENDERING_KEY)) {
styleFactory.setVectorRenderingEnabled(Boolean.TRUE.equals(hints.get(StreamingRenderer.VECTOR_RENDERING_KEY)));
}
rendererHints = hints;
}
public Map getRendererHints() {
return rendererHints;
}
public void setContext( MapContext context ) {
if (context == null) {
context = new DefaultMapContext(DefaultGeographicCRS.WGS84);
}
this.context = context;
MapLayer[] layers = context.getLayers();
layerIndexInfo = new IndexInfo[layers.length];
int i=0;
for(MapLayer layer:layers ) {
DataStore ds = (DataStore) layer.getFeatureSource().getDataStore();
if( ds instanceof ShapefileDataStore ){
ShapefileDataStore sds = (ShapefileDataStore) ds;
try {
layerIndexInfo[i] = useIndex(sds);
} catch (Exception e) {
layerIndexInfo[i] = new IndexInfo(IndexType.NONE, ShapefileRendererUtil.getShpFiles(sds));
if(LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Exception while trying to use index" + e.getLocalizedMessage());
}
}else{
layerIndexInfo[i]=STREAMING_RENDERER_INFO;
}
i++;
}
}
public void paint( Graphics2D graphics, Rectangle paintArea, AffineTransform worldToScreen ) {
if (worldToScreen == null || paintArea == null) {
LOGGER.info("renderer passed null arguments");
return;
} // Other arguments get checked later
// First, create the bbox in real world coordinates
ReferencedEnvelope mapArea;
try {
mapArea = RendererUtilities.createMapEnvelope(paintArea, worldToScreen, getContext().getCoordinateReferenceSystem());
paint(graphics, paintArea, mapArea, worldToScreen);
} catch (NoninvertibleTransformException e) {
fireErrorEvent(new Exception("Can't create pixel to world transform", e));
}
}
public void paint( Graphics2D graphics, Rectangle paintArea, ReferencedEnvelope envelope,
AffineTransform transform ) {
if (hints != null) {
graphics.setRenderingHints(hints);
}
if ((graphics == null) || (paintArea == null)) {
LOGGER.info("renderer passed null arguments");
return;
}
// reset the abort flag
renderingStopRequested = false;
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Affine Transform is " + transform);
}
/*
* If we are rendering to a component which has already set up some form of transformation
* then we can concatenate our transformation to it. An example of this is the ZoomPane
* component of the swinggui module.
*/
if (concatTransforms) {
AffineTransform atg = graphics.getTransform();
// graphics.setTransform(new AffineTransform());
atg.concatenate(transform);
transform = atg;
}
try {
setScaleDenominator(
computeScale(
envelope,
context.getCoordinateReferenceSystem(),
paintArea,
transform,
this.rendererHints));
} catch (Exception e) // probably either (1) no CRS (2) error xforming
{
LOGGER.throwing("RendererUtilities", "calculateScale(envelope, coordinateReferenceSystem, imageWidth, imageHeight, hints)", e);
setScaleDenominator(1 / transform.getScaleX()); // DJB old method - the best we can do
}
MapLayer[] layers = context.getLayers();
// get detstination CRS
CoordinateReferenceSystem destinationCrs = context.getCoordinateReferenceSystem();
labelCache.start();
labelCache.clear();
if(labelCache instanceof LabelCacheImpl) {
boolean outlineEnabled = TEXT_RENDERING_OUTLINE.equals(getTextRenderingMethod());
((LabelCacheImpl) labelCache).setOutlineRenderingEnabled(outlineEnabled);
}
for( int i = 0; i < layers.length; i++ ) {
MapLayer currLayer = layers[i];
if (!currLayer.isVisible()) {
// Only render layer when layer is visible
continue;
}
if (renderingStopRequested) {
return;
}
if( layerIndexInfo[i]==STREAMING_RENDERER_INFO ){
renderWithStreamingRenderer(currLayer, graphics, paintArea, envelope, transform);
continue;
}
labelCache.startLayer(""+i);
ReferencedEnvelope bbox = envelope;
try {
GeometryDescriptor geom = currLayer.getFeatureSource().getSchema().getGeometryDescriptor();
CoordinateReferenceSystem dataCRS;
if( getForceCRSHint()==null )
dataCRS = geom.getCoordinateReferenceSystem();
else
dataCRS=getForceCRSHint();
MathTransform mt;
CoordinateOperation op;
try {
op = CRS.getCoordinateOperationFactory(true).createOperation(dataCRS, destinationCrs);
mt = op.getMathTransform();
bbox = bbox.transform(dataCRS, true, 10);
} catch (Exception e) {
fireErrorEvent(e);
LOGGER.log(Level.WARNING, "Could not reproject the bounding boxes, proceeding in non reprojecting mode", e);
op = null;
mt = null;
}
MathTransform at = ReferencingFactoryFinder.getMathTransformFactory(null)
.createAffineTransform(new GeneralMatrix(transform));
if (mt == null) {
mt = at;
} else {
mt = ReferencingFactoryFinder.getMathTransformFactory(null).createConcatenatedTransform(
mt, at);
}
// dbfheader must be set so that the attributes required for theming can be read in.
ShapefileDataStore ds = (ShapefileDataStore) currLayer.getFeatureSource().getDataStore();
// graphics.setTransform(transform);
// extract the feature type stylers from the style object
// and process them
Transaction transaction = Transaction.AUTO_COMMIT;
if (currLayer.getFeatureSource() instanceof FeatureStore) {
transaction = ((FeatureStore<SimpleFeatureType, SimpleFeature>) currLayer.getFeatureSource()).getTransaction();
}
DefaultQuery query = new DefaultQuery(currLayer.getQuery());
if( query.getFilter() !=null ){
// now reproject the geometries in filter because geoms are retrieved projected to screen space
FilterTransformer transformer= new FilterTransformer(mt);
Filter transformedFilter = (Filter) query.getFilter().accept(transformer, null);
query.setFilter(transformedFilter);
}
// by processing the filter we can further restrict the maximum bounds that are
// required. For example if a filter
//BoundsExtractor extractor=new BoundsExtractor(bbox);
//if( query.getFilter()!=null )
// query.getFilter().accept(extractor);
//
//processStylers(graphics, ds, query, extractor.getIntersection(), paintArea,
// mt, currLayer.getStyle(), layerIndexInfo[i], transaction);
processStylers(graphics, ds, query, bbox, paintArea,
mt, currLayer.getStyle(), layerIndexInfo[i], transaction, ""+i);
} catch (Exception exception) {
fireErrorEvent(new Exception("Exception rendering layer " + currLayer, exception));
}
labelCache.endLayer(""+i, graphics, paintArea);
}
labelCache.end(graphics, paintArea);
if(LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Style cache hit ratio: " + styleFactory.getHitRatio() + " , hits "
+ styleFactory.getHits() + ", requests " + styleFactory.getRequests());
}
/**
* Returns the text rendering method
*/
private String getTextRenderingMethod() {
if (rendererHints == null)
return textRenderingModeDEFAULT;
String result = (String) rendererHints.get(TEXT_RENDERING_KEY);
if (result == null)
return textRenderingModeDEFAULT;
return result;
}
/**
* <p>
* Returns scale computation algorithm to be used.
* </p>
*/
private String getScaleComputationMethod() {
if (rendererHints == null)
return scaleComputationMethodDEFAULT;
String result = (String) rendererHints.get(SCALE_COMPUTATION_METHOD_KEY);
if (result == null)
return scaleComputationMethodDEFAULT;
return result;
}
private double computeScale(ReferencedEnvelope envelope, CoordinateReferenceSystem crs, Rectangle paintArea,
AffineTransform worldToScreen, Map hints) {
if(getScaleComputationMethod().equals(SCALE_ACCURATE)) {
try {
return RendererUtilities.calculateScale(envelope, paintArea.width, paintArea.height, hints);
} catch (Exception e) // probably either (1) no CRS (2) error xforming
{
LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e);
}
}
if (XAffineTransform.getRotation(worldToScreen) != 0.0) {
return RendererUtilities.calculateOGCScaleAffine(envelope.getCoordinateReferenceSystem(),
worldToScreen, hints);
}
return RendererUtilities.calculateOGCScale(envelope, paintArea.width, hints);
}
private void renderWithStreamingRenderer(MapLayer layer, Graphics2D graphics, Rectangle paintArea, ReferencedEnvelope envelope, AffineTransform transform) {
MapContext context = null;
RenderListener listener = null;;
try {
context = new DefaultMapContext(new MapLayer[]{layer}, envelope.getCoordinateReferenceSystem());
delegate = new StreamingRenderer();
delegate.setContext(context);
delegate.setJava2DHints(getJava2DHints());
Map rendererHints2 = new HashMap(getRendererHints() != null ? getRendererHints() : Collections.EMPTY_MAP);
rendererHints2.put(LABEL_CACHE_KEY, new IntegratingLabelCache(labelCache));
delegate.setRendererHints(rendererHints2);
// cascade events, provided there is anyone listening
listener = new RenderListener() {
public void featureRenderer(SimpleFeature feature) {
fireFeatureRenderedEvent(feature);
}
public void errorOccurred(Exception e) {
fireErrorEvent(e);
}
};
delegate.addRenderListener(listener);
delegate.paint(graphics, paintArea, envelope, transform);
} finally {
// cleanups to avoid circular references
if(context != null)
context.clearLayerList();
if(listener != null && delegate != null)
delegate.removeRenderListener(listener);
delegate = null;
}
}
/**
* If the forceCRS hint is set then return the value.
* @return the value of the forceCRS hint or null
*/
private CoordinateReferenceSystem getForceCRSHint() {
if ( rendererHints==null )
return null;
Object crs=this.rendererHints.get("forceCRS");
if( crs instanceof CoordinateReferenceSystem )
return (CoordinateReferenceSystem) crs;
return null;
}
/**
* @deprecated
*/
public void paint(Graphics2D graphics, Rectangle paintArea, Envelope mapArea) {
paint(graphics, paintArea, new ReferencedEnvelope(mapArea, context.getCoordinateReferenceSystem()));
}
/**
* @deprecated
*/
public void paint(Graphics2D graphics, Rectangle paintArea, Envelope mapArea, AffineTransform worldToScreen) {
paint(graphics, paintArea, new ReferencedEnvelope(mapArea, context.getCoordinateReferenceSystem()), worldToScreen);
}
}