/*
* 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.lite;
import static org.easymock.EasyMock.*;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.HashSet;
import java.util.Iterator;
import junit.framework.TestCase;
import org.geotools.data.FeatureSource;
import org.geotools.data.Query;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureCollections;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.DefaultMapContext;
import org.geotools.map.MapContext;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.renderer.RenderListener;
import org.geotools.styling.Style;
import org.geotools.styling.StyleBuilder;
import org.junit.Test;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.Point;
/**
* Test the inner workings of StreamingRenderer.
* <p>
* Rendering is a pretty high level concept
* @author Jody
* @author PHustad
*
* @source $URL$
*/
public class StreamingRendererTest extends TestCase {
private SimpleFeatureType testFeatureType;
private SimpleFeatureType testPointFeatureType;
private GeometryFactory gf = new GeometryFactory();
protected int errors;
protected int features;
protected void setUp() throws Exception {
super.setUp();
SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
builder.setName("Lines");
builder.add("geom", LineString.class, DefaultGeographicCRS.WGS84);
testFeatureType = builder.buildFeatureType();
builder = new SimpleFeatureTypeBuilder();
builder.setName("Points");
builder.add("geom", Point.class, DefaultGeographicCRS.WGS84);
testPointFeatureType = builder.buildFeatureType();
}
public FeatureCollection<SimpleFeatureType, SimpleFeature> createLineCollection() throws Exception {
FeatureCollection<SimpleFeatureType, SimpleFeature> fc = FeatureCollections.newCollection();
fc.add(createLine(-177, 0, -177, 10));
fc.add(createLine(-177, 0, -200, 0));
fc.add(createLine(-177, 0, -177, 100));
return fc;
}
private SimpleFeature createLine(double x1, double y1, double x2, double y2) {
Coordinate[] coords = new Coordinate[] { new Coordinate(x1, y1), new Coordinate(x2, y2) };
return SimpleFeatureBuilder.build(testFeatureType, new Object[] { gf.createLineString(coords) }, null);
}
private SimpleFeature createPoint(double x, double y) {
Coordinate coord = new Coordinate(x, y);
return SimpleFeatureBuilder.build(testPointFeatureType, new Object[] { gf.createPoint(coord) }, null);
}
private Style createLineStyle() {
StyleBuilder sb = new StyleBuilder();
return sb.createStyle(sb.createLineSymbolizer());
}
private Style createPointStyle() {
StyleBuilder sb = new StyleBuilder();
return sb.createStyle(sb.createPointSymbolizer());
}
@Test
public void testRenderStuff() throws Exception {
// build map context
MapContext mapContext = new DefaultMapContext(DefaultGeographicCRS.WGS84);
mapContext.addLayer(createLineCollection(), createLineStyle());
// build projected envelope to work with (small one around the area of
// validity of utm zone 1, which being a Gauss projection is a vertical
// slice parallel to the central meridian, -177°)
ReferencedEnvelope reWgs = new ReferencedEnvelope(new Envelope(-180,
-170, 20, 40), DefaultGeographicCRS.WGS84);
CoordinateReferenceSystem utm1N = CRS.decode("EPSG:32601");
System.out.println(CRS.getGeographicBoundingBox(utm1N));
ReferencedEnvelope reUtm = reWgs.transform(utm1N, true);
BufferedImage image = new BufferedImage(200, 200,BufferedImage.TYPE_4BYTE_ABGR);
// setup the renderer and listen for errors
StreamingRenderer sr = new StreamingRenderer();
sr.setContext(mapContext);
sr.addRenderListener(new RenderListener() {
public void featureRenderer(SimpleFeature feature) {
features++;
}
public void errorOccurred(Exception e) {
errors++;
}
});
errors = 0;
features = 0;
sr.paint((Graphics2D) image.getGraphics(), new Rectangle(200, 200),reUtm);
// we should get two errors since there are two features that cannot be
// projected but the renderer itself should not throw exceptions
assertTrue( features > 0 );
}
@Test
public void testInfiniteLoopAvoidance() throws Exception {
final Exception sentinel = new RuntimeException("This is the one that should be thrown in hasNext()");
// setup the mock necessary to have the renderer hit into the exception in hasNext()
Iterator it2 = createNiceMock(Iterator.class);
expect(it2.hasNext()).andThrow(sentinel).anyTimes();
replay(it2);
FeatureCollection fc = createNiceMock(FeatureCollection.class);
expect(fc.iterator()).andReturn(it2);
expect(fc.size()).andReturn(200);
expect(fc.getSchema()).andReturn(testFeatureType).anyTimes();
replay(fc);
FeatureSource fs = createNiceMock(FeatureSource.class);
expect(fs.getFeatures((Query) anyObject())).andReturn(fc);
expect(fs.getSchema()).andReturn(testFeatureType).anyTimes();
expect(fs.getSupportedHints()).andReturn(new HashSet()).anyTimes();
replay(fs);
// build map context
MapContext mapContext = new DefaultMapContext(DefaultGeographicCRS.WGS84);
mapContext.addLayer(fs, createLineStyle());
// setup the renderer and listen for errors
final StreamingRenderer sr = new StreamingRenderer();
sr.setContext(mapContext);
sr.addRenderListener(new RenderListener() {
public void featureRenderer(SimpleFeature feature) {
features++;
}
public void errorOccurred(Exception e) {
errors++;
if(errors > 2) {
// we dont' want to block the loop in case of regression on this bug
sr.stopRendering();
}
// but we want to make sure we're getting
Throwable t = e;
while(t != sentinel && t.getCause() != null)
t = t.getCause();
assertSame(sentinel, t);
}
});
errors = 0;
features = 0;
BufferedImage image = new BufferedImage(200, 200,BufferedImage.TYPE_4BYTE_ABGR);
ReferencedEnvelope reWgs = new ReferencedEnvelope(new Envelope(-180,
-170, 20, 40), DefaultGeographicCRS.WGS84);
sr.paint((Graphics2D) image.getGraphics(), new Rectangle(200, 200),reWgs);
// we should get two errors since there are two features that cannot be
// projected but the renderer itself should not throw exceptions
assertEquals(0, features);
assertEquals(1, errors);
}
/**
* Test that point features are rendered at the expected
* image coordinates when the map is rotated.
* StreamingRenderer
* @throws Exception
*/
@Test
public void testRotatedTransform() throws Exception {
// If we rotate the world rectangle + 90 degrees around (0,0), we get the screen rectangle
final Rectangle screen = new Rectangle(0, 0, 100, 50);
final Envelope world = new Envelope(0, 50, 0, -100);
final AffineTransform worldToScreen = AffineTransform.getRotateInstance(Math.toRadians(90), 0, 0);
FeatureCollection<SimpleFeatureType, SimpleFeature> fc = FeatureCollections.newCollection();
fc.add(createPoint(0, 0));
fc.add(createPoint(world.getMaxX(), world.getMinY()));
MapContext mapContext = new DefaultMapContext(DefaultGeographicCRS.WGS84);
mapContext.addLayer(fc, createPointStyle());
BufferedImage image = new BufferedImage(screen.width, screen.height,
BufferedImage.TYPE_4BYTE_ABGR);
final StreamingRenderer sr = new StreamingRenderer();
sr.setContext(mapContext);
sr.paint(image.createGraphics(), screen, worldToScreen);
assertTrue("Pixel should be drawn at 0,0 ", image.getRGB(0, 0) != 0);
assertTrue("Pixel should not be drawn in image centre ", image.getRGB(screen.width / 2,
screen.height / 2) == 0);
assertTrue("Pixel should be drawn at image max corner ", image.getRGB(screen.width - 1,
screen.height - 1) != 0);
}
}