/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-2016, 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.anyObject; import static org.easymock.EasyMock.expect; import static org.easymock.classextension.EasyMock.createNiceMock; import static org.easymock.classextension.EasyMock.replay; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.atomic.AtomicInteger; import javax.media.jai.Interpolation; import javax.media.jai.JAI; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.GridCoverageFactory; import org.geotools.coverage.grid.io.GridCoverage2DReader; import org.geotools.data.Query; import org.geotools.data.collection.CollectionFeatureSource; import org.geotools.data.property.PropertyDataStore; import org.geotools.data.property.PropertyFeatureSource; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.data.simple.SimpleFeatureIterator; import org.geotools.data.simple.SimpleFeatureSource; import org.geotools.data.store.ContentEntry; import org.geotools.data.store.ContentFeatureSource; import org.geotools.feature.DefaultFeatureCollection; import org.geotools.feature.FeatureCollection; import org.geotools.feature.simple.SimpleFeatureBuilder; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.geotools.geometry.GeneralEnvelope; import org.geotools.geometry.jts.LiteCoordinateSequence; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.map.DefaultMapContext; import org.geotools.map.FeatureLayer; import org.geotools.map.GridCoverageLayer; import org.geotools.map.Layer; import org.geotools.map.MapContent; import org.geotools.map.MapContext; import org.geotools.referencing.CRS; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.geotools.renderer.RenderListener; import org.geotools.renderer.lite.StreamingRenderer.RenderingRequest; import org.geotools.resources.coverage.FeatureUtilities; import org.geotools.styling.DescriptionImpl; import org.geotools.styling.Graphic; import org.geotools.styling.Rule; import org.geotools.styling.Style; import org.geotools.styling.StyleImpl; import org.geotools.styling.StyleBuilder; import org.geotools.styling.StyleFactoryImpl; import org.geotools.styling.Symbolizer; import org.geotools.test.TestData; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.filter.Filter; import org.opengis.filter.spatial.BBOX; import org.opengis.geometry.BoundingBox; import org.opengis.parameter.GeneralParameterValue; 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 { private SimpleFeatureType testLineFeatureType; private SimpleFeatureType testPointFeatureType; private GeometryFactory gf = new GeometryFactory(); protected int errors; protected int features; @Before public void setUp() throws Exception { SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder(); builder.setName("Lines"); builder.add("geom", LineString.class, DefaultGeographicCRS.WGS84); testLineFeatureType = builder.buildFeatureType(); builder = new SimpleFeatureTypeBuilder(); builder.setName("Points"); builder.add("geom", Point.class, DefaultGeographicCRS.WGS84); testPointFeatureType = builder.buildFeatureType(); } public SimpleFeatureCollection createLineCollection() throws Exception { DefaultFeatureCollection fc = new DefaultFeatureCollection(); 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(testLineFeatureType, 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 createRasterStyle() { StyleBuilder sb = new StyleBuilder(); return sb.createStyle(sb.createRasterSymbolizer()); } private Style createPointStyle() { StyleBuilder sb = new StyleBuilder(); return sb.createStyle(sb.createPointSymbolizer()); } @Test public void testInterpolationByLayer() throws Exception { StreamingRenderer sr = new StreamingRenderer(); Layer layer = new FeatureLayer(createLineCollection(), createLineStyle()); // default is nearest neighbor assertEquals(sr.getRenderingInterpolation(layer), Interpolation.getInstance(Interpolation.INTERP_NEAREST)); // test all possible values layer.getUserData().put(StreamingRenderer.BYLAYER_INTERPOLATION, Interpolation.getInstance(Interpolation.INTERP_BICUBIC)); assertEquals(sr.getRenderingInterpolation(layer), Interpolation.getInstance(Interpolation.INTERP_BICUBIC)); layer.getUserData().put(StreamingRenderer.BYLAYER_INTERPOLATION, Interpolation.getInstance(Interpolation.INTERP_BILINEAR)); assertEquals(sr.getRenderingInterpolation(layer), Interpolation.getInstance(Interpolation.INTERP_BILINEAR)); layer.getUserData().put(StreamingRenderer.BYLAYER_INTERPOLATION, Interpolation.getInstance(Interpolation.INTERP_NEAREST)); assertEquals(sr.getRenderingInterpolation(layer), Interpolation.getInstance(Interpolation.INTERP_NEAREST)); } @Test public void testDrawIntepolation() throws Exception { MapContent mc = new MapContent(); ReferencedEnvelope reWgs = new ReferencedEnvelope(new Envelope(-180, 180, -90, 90), DefaultGeographicCRS.WGS84); BufferedImage testImage = new BufferedImage(200, 200, BufferedImage.TYPE_4BYTE_ABGR); GridCoverage2D testCoverage = new GridCoverageFactory().create("test", testImage, reWgs); GridCoverage2D coverage = new GridCoverage2D("test", testCoverage); // mocking a GridCoverageReader to wrap the testing coverage GridCoverage2DReader gridCoverageReader = Mockito.mock(GridCoverage2DReader.class); Mockito.when(gridCoverageReader.getOriginalEnvelope()).thenReturn(new GeneralEnvelope(reWgs)); Mockito.when(gridCoverageReader.getCoordinateReferenceSystem()).thenReturn(DefaultGeographicCRS.WGS84); Mockito.when(gridCoverageReader.read(Mockito.any(GeneralParameterValue[].class))).thenReturn(coverage); Layer layer = new FeatureLayer(FeatureUtilities.wrapGridCoverageReader(gridCoverageReader, new GeneralParameterValue[] {}), createRasterStyle()); layer.getUserData().put(StreamingRenderer.BYLAYER_INTERPOLATION, Interpolation.getInstance(Interpolation.INTERP_BICUBIC)); mc.addLayer(layer); BufferedImage image = new BufferedImage(200, 200,BufferedImage.TYPE_4BYTE_ABGR); StreamingRenderer sr = new StreamingRenderer(); sr.setMapContent(mc); Graphics2D graphics = (Graphics2D) image.getGraphics(); sr.paint(graphics, new Rectangle(200, 200),reWgs); // test right interpolation hint is set on Graphics2D assertEquals(graphics.getRenderingHint(JAI.KEY_INTERPOLATION), Interpolation.getInstance(Interpolation.INTERP_BICUBIC)); layer.getUserData().put(StreamingRenderer.BYLAYER_INTERPOLATION, Interpolation.getInstance(Interpolation.INTERP_NEAREST)); sr.paint(graphics, new Rectangle(200, 200),reWgs); // test right interpolation hint is set on Graphics2D assertEquals(graphics.getRenderingHint(JAI.KEY_INTERPOLATION), Interpolation.getInstance(Interpolation.INTERP_NEAREST)); } @Test public void testEventAfterDrawing() throws Exception { // build map context MapContent mc = new MapContent(); mc.addLayer(new FeatureLayer(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"); ReferencedEnvelope reUtm = reWgs.transform(utm1N, true); BufferedImage image = new BufferedImage(200, 200,BufferedImage.TYPE_4BYTE_ABGR); // setup the renderer and listen for errors final AtomicInteger commandsCount = new AtomicInteger(0); final BlockingQueue<RenderingRequest> queue = new ArrayBlockingQueue<RenderingRequest>(10) { @Override public void put(RenderingRequest e) throws InterruptedException { commandsCount.incrementAndGet(); super.put(e); } }; StreamingRenderer sr = new StreamingRenderer() { @Override protected BlockingQueue<RenderingRequest> getRequestsQueue() { return queue; } }; sr.setMapContent(mc); sr.addRenderListener(new RenderListener() { public void featureRenderer(SimpleFeature feature) { assertTrue(commandsCount.get() > 0); features++; } public void errorOccurred(Exception e) { errors++; } }); errors = 0; features = 0; sr.paint((Graphics2D) image.getGraphics(), new Rectangle(200, 200),reUtm); // we should get errors since there are two features that cannot be // projected but the renderer itself should not throw exceptions assertTrue(errors > 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() SimpleFeatureIterator it2 = createNiceMock(SimpleFeatureIterator.class); expect(it2.hasNext()).andThrow(sentinel).anyTimes(); replay(it2); SimpleFeatureCollection fc = createNiceMock(SimpleFeatureCollection.class); expect(fc.features()).andReturn(it2); expect(fc.size()).andReturn(200); expect(fc.getSchema()).andReturn(testLineFeatureType).anyTimes(); replay(fc); SimpleFeatureSource fs = createNiceMock(SimpleFeatureSource.class); expect(fs.getFeatures((Query) anyObject())).andReturn(fc); expect(fs.getSchema()).andReturn(testLineFeatureType).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 public void testDeadlockOnException() throws Exception { ReferencedEnvelope reWgs = new ReferencedEnvelope(new Envelope(-180, 180, -90, 90), DefaultGeographicCRS.WGS84); // create the grid coverage that throws a OOM BufferedImage testImage = new BufferedImage(200, 200, BufferedImage.TYPE_4BYTE_ABGR); GridCoverage2D testCoverage = new GridCoverageFactory().create("test", testImage, reWgs); GridCoverage2D oomCoverage = new GridCoverage2D("test", testCoverage) { @Override public RenderedImage getRenderedImage() { throw new OutOfMemoryError("Boom!"); } }; // also have a collections of features to create the deadlock once the painter // thread is dead SimpleFeatureCollection lines = createLineCollection(); Style rasterStyle = createRasterStyle(); Style lineStyle = createLineStyle(); MapContent mapContent = new MapContent(); mapContent.addLayer(new GridCoverageLayer(oomCoverage, rasterStyle)); mapContent.addLayer(new FeatureLayer(lines, lineStyle)); final StreamingRenderer sr = new StreamingRenderer() { // makes it easy to reproduce the deadlock, just two features are sufficient protected BlockingQueue<RenderingRequest> getRequestsQueue() { return new RenderingBlockingQueue(1); } }; sr.setMapContent(mapContent); final List<Exception> exceptions = new ArrayList<Exception>(); sr.addRenderListener(new RenderListener() { public void featureRenderer(SimpleFeature feature) { features++; } public void errorOccurred(Exception e) { errors++; exceptions.add(e); } }); errors = 0; features = 0; BufferedImage image = new BufferedImage(200, 200,BufferedImage.TYPE_4BYTE_ABGR); sr.paint((Graphics2D) image.getGraphics(), new Rectangle(200, 200),reWgs); // all the lines should have been painted, the coverage reports as painted too // since the reporting happens in the main thread that does not error, but with // the new queue draining on some systems it might not assertTrue(features == 4 || features == 3); assertEquals(1, errors); assertTrue(exceptions.get(0).getCause() instanceof OutOfMemoryError); } /** * 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); DefaultFeatureCollection fc = new DefaultFeatureCollection(); fc.add(createPoint(0, 0)); fc.add(createPoint(world.getMaxX(), world.getMinY())); MapContext mapContext = new DefaultMapContext(DefaultGeographicCRS.WGS84); mapContext.addLayer((FeatureCollection)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); } @Test public void testRepeatedEnvelopeExpansion() throws Exception { final List<Filter> filters = new ArrayList<Filter>(); SimpleFeatureSource testSource = new CollectionFeatureSource(createLineCollection()) { @Override public SimpleFeatureCollection getFeatures(Query query) { filters.add(query.getFilter()); return super.getFeatures(query); } }; StyleBuilder sb = new StyleBuilder(); Style style20 = sb.createStyle(sb.createLineSymbolizer(20)); Style style10 = sb.createStyle(sb.createLineSymbolizer(10)); MapContent mc = new MapContent(); mc.addLayer(new FeatureLayer(testSource, style20)); mc.addLayer(new FeatureLayer(testSource, style10)); StreamingRenderer sr = new StreamingRenderer(); sr.setMapContent(mc); BufferedImage bi = new BufferedImage(100, 100, BufferedImage.TYPE_3BYTE_BGR); Graphics2D graphics = bi.createGraphics(); sr.paint(graphics, new Rectangle(0, 0, 100, 100), new ReferencedEnvelope(0, 100, 0, 100, DefaultGeographicCRS.WGS84)); graphics.dispose(); System.out.println(filters); assertEquals(2, filters.size()); Filter f1 = filters.get(0); assertTrue(f1 instanceof BBOX); BoundingBox bbox1 = ((BBOX) f1).getBounds(); ReferencedEnvelope expected = new ReferencedEnvelope(-11, 111, -11, 111, DefaultGeographicCRS.WGS84); assertEquals(expected, bbox1); Filter f2 = filters.get(1); assertTrue(f2 instanceof BBOX); BoundingBox bbox2 = ((BBOX) f2).getBounds(); assertEquals(new ReferencedEnvelope(-6, 106, -6, 106, DefaultGeographicCRS.WGS84), bbox2); } @Test public void testScreenMapMemory() { // build a feature source with two zig-zag line occupying the same position LiteCoordinateSequence cs = new LiteCoordinateSequence(new double[] {0, 0, 1, 1, 2, 0, 3, 1, 4, 0}); SimpleFeature zigzag1 = SimpleFeatureBuilder.build(testLineFeatureType, new Object[] { gf.createLineString(cs) }, "zz1"); SimpleFeature zigzag2 = SimpleFeatureBuilder.build(testLineFeatureType, new Object[] { gf.createLineString(cs) }, "zz2"); DefaultFeatureCollection fc = new DefaultFeatureCollection(); fc.add(zigzag1); fc.add(zigzag2); SimpleFeatureSource zzSource = new CollectionFeatureSource(fc); // prepare the map MapContent mc = new MapContent(); StyleBuilder sb = new StyleBuilder(); mc.addLayer(new FeatureLayer(zzSource, sb.createStyle(sb.createLineSymbolizer()))); StreamingRenderer sr = new StreamingRenderer(); sr.setMapContent(mc); // collect rendered features final List<SimpleFeature> features = new ArrayList<SimpleFeature>(); RenderListener renderedFeaturesCollector = new RenderListener() { @Override public void featureRenderer(SimpleFeature feature) { features.add(feature); } @Override public void errorOccurred(Exception e) { // nothing to do } }; sr.addRenderListener(renderedFeaturesCollector); BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR); Graphics2D graphics = bi.createGraphics(); // have the lines be smaller than a 1/3 of a pixel sr.paint(graphics, new Rectangle(0, 0, 1, 1), new ReferencedEnvelope(0, 8, 0, 8, DefaultGeographicCRS.WGS84)); // check we only rendered one feature assertEquals(1, features.size()); assertEquals("zz1", features.get(0).getID()); // now have the lines be big enough to be painted instead features.clear(); sr.paint(graphics, new Rectangle(0, 0, 1, 1), new ReferencedEnvelope(0, 1, 0, 1, DefaultGeographicCRS.WGS84)); assertEquals(2, features.size()); assertEquals("zz1", features.get(0).getID()); assertEquals("zz2", features.get(1).getID()); graphics.dispose(); } /** * Test that we don't have the geometry added twice by StreamingRenderer#findStyleAttributes when geofence is * filtering a layer. * @throws Exception */ @Test public void testFindLineStyleAttributeWithAddedFilter() throws Exception { final List<Filter> filters = new ArrayList<Filter>(); SimpleFeatureSource testSource = new CollectionFeatureSource(createLineCollection()) { @Override public SimpleFeatureCollection getFeatures(Query query) { filters.add(query.getFilter()); return super.getFeatures(query); } }; Style style = createPointStyle(); MapContent mc = new MapContent(); FeatureLayer layer = new FeatureLayer(testSource, style); mc.addLayer(layer); StreamingRenderer sr = new StreamingRenderer(); sr.setMapContent(mc); ReferencedEnvelope envelope = new ReferencedEnvelope(0, 100, 0, 100, DefaultGeographicCRS.WGS84); //simulate geofence adding a bbox BBOX bbox = StreamingRenderer.filterFactory.bbox("", 30, 60, 30, 60, "WGS84"); StyleFactoryImpl sf = new StyleFactoryImpl(); Rule bboxRule = sf.createRule(new Symbolizer[0], new DescriptionImpl(), new Graphic[0], "bbox", bbox, false, 1e12, 0); style.featureTypeStyles().get(0).rules().add(bboxRule); BufferedImage bi = new BufferedImage(100, 100, BufferedImage.TYPE_3BYTE_BGR); Graphics2D graphics = bi.createGraphics(); try { sr.paint(graphics, new Rectangle(5, 5, 7, 7), envelope); } finally { graphics.dispose(); } //must have only one bbox, not two assertEquals(1, filters.size()); assertEquals(FastBBOX.class, filters.get(0).getClass()); } /* * https://osgeo-org.atlassian.net/browse/GEOT-5287 */ @Test public void testEmptyGeometryRendering() throws Exception { MapContent mc = new MapContent(); /* * We simulate reading empty geometries with this properties and mocking the capability to * filter, so that no filter layer is installed over our data and the empty geometry reaches * rendering code. These geometries are in EPSG:32717 because the 0,0 coordinate is in the * pole. */ File dir = new File(TestData.getResource(this, "empty-geom-rendering.properties").toURI()); PropertyDataStore dataStore = new PropertyDataStore(dir.getParentFile()) { @Override protected ContentFeatureSource createFeatureSource(ContentEntry entry) throws IOException { return new PropertyFeatureSource(entry, Query.ALL) { @Override protected boolean canFilter() { return true; } }; } }; /* * Set up the rendering of previous empty geometry */ StyleBuilder sb = new StyleBuilder(); Style style = sb.createStyle(sb.createPolygonSymbolizer()); Layer layer = new FeatureLayer(dataStore.getFeatureSource("empty-geom-rendering"), style); mc.addLayer(layer); StreamingRenderer sr = new StreamingRenderer(); sr.setMapContent(mc); BufferedImage img = new BufferedImage(40, 40, BufferedImage.TYPE_INT_ARGB); Graphics2D graphics = img.createGraphics(); Rectangle paintArea = new Rectangle(40, 40); // An EPSG:8357 extent on the EPSG:32717 area of application. double minx = -8929252.1; double maxx = -8708634.6; double miny = -491855.7; double maxy = -271204.3; ReferencedEnvelope referencedEnvelope = new ReferencedEnvelope( new Rectangle2D.Double(minx, miny, maxx - minx, maxy - miny), CRS.decode("EPSG:3857")); sr.addRenderListener(new RenderListener() { public void featureRenderer(SimpleFeature feature) { } public void errorOccurred(Exception e) { errors++; } }); errors = 0; sr.paint(graphics, paintArea, referencedEnvelope); assertTrue(errors == 0); } @Test public void testStyleThatUsesGeometryDefaultAttribute() throws Exception { // preparing the layer to be rendered, the provided style as a filter that will use // the default geometry attribute "", this will allow us to test that using geometry // default attribute "" is correctly handled StyleImpl style = (StyleImpl) RendererBaseTest.loadStyle(this, "genericLines.sld"); File vectorDataFile = new File(TestData.getResource(this, "genericLines.properties").toURI()); PropertyDataStore dataStore = new PropertyDataStore(vectorDataFile.getParentFile()); Layer layer = new FeatureLayer(dataStore.getFeatureSource("genericLines"), style); // prepare map content and instantiate a streaming reader MapContent mapContent = new MapContent(); mapContent.addLayer(layer); StreamingRenderer gRender = new StreamingRenderer(); gRender.setMapContent(mapContent); gRender.addRenderListener(new RenderListener() { @Override public void featureRenderer(SimpleFeature feature) { features++; } @Override public void errorOccurred(Exception e) { errors++; } }); features = 0; errors = 0; // defining the paint area and performing the rendering BufferedImage image = new BufferedImage(40, 40, BufferedImage.TYPE_INT_ARGB); Graphics2D graphics = image.createGraphics(); Rectangle paintArea = new Rectangle(40, 40); double minx = -2; double maxx = 2; double miny = -2; double maxy = 2; ReferencedEnvelope referencedEnvelope = new ReferencedEnvelope( new Rectangle2D.Double(minx, miny, maxx - minx, maxy - miny), CRS.decode("EPSG:4326")); gRender.paint(graphics, paintArea, referencedEnvelope); // checking that four features were rendered, if the default geometry attribute was not // correctly handled no geometries were selected and so no features were rendered Assert.assertEquals(features, 4); Assert.assertEquals(errors, 0); } }