/* * 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 java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.logging.Level; import junit.framework.TestCase; import org.geotools.TestData; import org.geotools.data.DataUtilities; import org.geotools.data.DefaultTransaction; import org.geotools.data.Query; import org.geotools.data.Transaction; import org.geotools.data.shapefile.ShapefileDataStore; import org.geotools.data.shapefile.ShapefileRendererUtil; import org.geotools.data.shapefile.dbf.IndexedDbaseFileReader; import org.geotools.data.shapefile.shp.ShapefileReader; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.data.simple.SimpleFeatureIterator; import org.geotools.data.simple.SimpleFeatureSource; import org.geotools.data.simple.SimpleFeatureStore; import org.geotools.factory.CommonFactoryFinder; import org.geotools.feature.simple.SimpleFeatureBuilder; import org.geotools.filter.Filter; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.map.DefaultMapContext; import org.geotools.map.DefaultMapLayer; import org.geotools.map.MapContext; import org.geotools.referencing.operation.transform.IdentityTransform; import org.geotools.renderer.GTRenderer; import org.geotools.renderer.RenderListener; import org.geotools.styling.Rule; import org.geotools.styling.Style; import org.geotools.styling.StyleBuilder; import org.geotools.styling.TextSymbolizer; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.filter.Id; import org.opengis.filter.identity.FeatureId; import org.opengis.referencing.operation.MathTransform; 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.MultiLineString; /** * Tests ShapeRenderer class * * @author jeichar * @since 2.1.x * * * @source $URL$ */ public class ShapeRendererTest extends TestCase { private static final boolean INTERACTIVE = false; private static final MathTransform IDENTITY = IdentityTransform.create(2); private File shp2; private File shx2; private File prj2; private File dbf2; private String typename; private File directory; private SimpleFeature sf; protected void setUp() throws Exception { org.geotools.util.logging.Logging.getLogger("org.geotools").setLevel(Level.FINE); File shp = DataUtilities.urlToFile(TestData.url(Rendering2DTest.class, "theme1.shp")); File shx = DataUtilities.urlToFile(TestData.url(Rendering2DTest.class, "theme1.shx")); File prj = DataUtilities.urlToFile(TestData.url(Rendering2DTest.class, "theme1.prj")); File dbf = DataUtilities.urlToFile(TestData.url(Rendering2DTest.class, "theme1.dbf")); directory = TestData.file(Rendering2DTest.class, "."); shp2 = File.createTempFile("theme2", ".shp", directory); typename = shp2.getName().substring(0, shp2.getName().lastIndexOf(".")); shx2 = new File(directory, typename + ".shx"); prj2 = new File(directory, typename + ".prj"); dbf2 = new File(directory, typename + ".dbf"); copy(shp, shp2); copy(shx, shx2); copy(prj, prj2); copy(dbf, dbf2); // setup a sample feature ShapefileDataStore ds = TestUtilites.getDataStore(shp2.getName()); SimpleFeatureType type = ds.getSchema(); GeometryFactory gf = new GeometryFactory(); LineString ls = gf.createLineString(new Coordinate[] {new Coordinate(0,0), new Coordinate(10,10)}); MultiLineString mls = gf.createMultiLineString(new LineString[] {ls}); sf = SimpleFeatureBuilder.build( type, new Object[] {mls, new Integer(0), "Hi"}, "newFeature"); } protected void tearDown() throws Exception { dbf2.deleteOnExit(); shx2.deleteOnExit(); shp2.deleteOnExit(); prj2.deleteOnExit(); File fix=new File( directory, typename+".fix"); File qix=new File( directory, typename+".qix"); if( shp2.exists() && !shp2.delete() ) System.out.println("failed to delete: "+shp2.getAbsolutePath()); if( shx2.exists() && !shx2.delete() ) System.out.println("failed to delete: "+shx2.getAbsolutePath()); if( prj2.exists() && !prj2.delete()) System.out.println("failed to delete: "+prj2.getAbsolutePath()); if( dbf2.exists() && !dbf2.delete() ) System.out.println("failed to delete: "+dbf2.getAbsolutePath()); if( fix.exists() && !fix.delete() ){ fix.deleteOnExit(); System.out.println("failed to delete: "+fix.getAbsolutePath()); } if( qix.exists() && !qix.delete() ){ qix.deleteOnExit(); System.out.println("failed to delete: "+qix.getAbsolutePath()); } } void copy(File src, File dst) throws IOException { InputStream in = null; OutputStream out = null; try { in = new FileInputStream(src); out = new FileOutputStream(dst, false); // Transfer bytes from in to out byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } } finally { if (in != null) { in.close(); } if (out != null) { out.close(); } } } /** * What an ugly bug! It only happens if the DefaultMapLayer has been created with a * FeatureSource (not a FeatureCollection) in combination with ShapefileRenderer. With * StramingRenderer it works. Also works with ShapefileRenderer, if the MapLayer is created with * a FeatureCollection. * * How does this test work: <br/> * 1. Using countries.shp which is not standard in this test package. But it's hard to show the * bug with lakes. <br/> * 2. Putting every third feature into a FidFilter 3. Creating a Style with two rules: First * rule paints red borders for all countries. Second rule is filtered by FidS and paints the * polygons with a red fill.<br/> * 3. Moving the BBOX 20 degree east, so the center is above a feature.<br/> * 4. Rendering the image once (this is the correct image!), and remembering the color of the * center point.<br/> * 5. Zooming in iteratively.. We expect the center point color not to change.<br/> * <br/> * But it changes with FeatureSource + ShapefileRenderer. Run the test interactively to see how * messy it is! <br/> * <br/> * It happens with lines, points and polygons. For questions contact alfonx on #geotools */ public void testFidFilterWithFeatureSource() throws Exception { TestUtilites.INTERACTIVE = INTERACTIVE; // Sorry! Not part of this testing resources, but too hard to show it with lakes. ShapefileDataStore dataStore = TestUtilites.getDataStore("countries.shp"); final SimpleFeatureSource featureSource = dataStore .getFeatureSource(dataStore.getTypeNames()[0]); SimpleFeatureCollection features = featureSource.getFeatures(); // Preparing the Filter Set<FeatureId> selectedFids = new HashSet<FeatureId>(); { Iterator<SimpleFeature> fIt = features.iterator(); int count = 0; while (fIt.hasNext()) { SimpleFeature sf = fIt.next(); // Add every third to the filter if (count++ % 3 != 0) continue; selectedFids.add(sf.getIdentifier()); } features.close(fIt); } assertEquals(84, selectedFids.size()); Id filter = CommonFactoryFinder.getFilterFactory2(null).id(selectedFids); // Preparing the Style final StyleBuilder SB = new StyleBuilder(); org.geotools.styling.Rule rule1 = SB.createRule(SB.createLineSymbolizer(Color.red)); org.geotools.styling.Rule rule2 = SB.createRule(SB.createPolygonSymbolizer(Color.red)); rule2.setFilter(filter); Style style = SB.createStyle(); assertEquals(0, style.featureTypeStyles().size()); style.featureTypeStyles().add( SB.createFeatureTypeStyle("Feature", new Rule[] { rule1, rule2 })); // One featuretypes, two rules assertEquals(1, style.featureTypeStyles().size()); assertEquals(2, style.featureTypeStyles().get(0).rules().size()); // second rule has a FID filter assertTrue(style.featureTypeStyles().get(0).rules().get(1).getFilter() instanceof Id); // WORKS: DefaultMapLayer layer = new DefaultMapLayer(featureSource.getFeatures(), style); DefaultMapLayer layer = new DefaultMapLayer(featureSource, style); MapContext mapContext = new DefaultMapContext(); mapContext.addLayer(layer); // GTRenderer renderer = new StreamingRenderer(); GTRenderer renderer = new ShapefileRenderer(); renderer.setContext(mapContext); // Moving the bounds "over afrika". ReferencedEnvelope fullBounds = features.getBounds(); fullBounds.translate(20, 0); fullBounds = zoomIn(fullBounds); BufferedImage correctImage = TestUtilites.showRender("full", renderer, 4000, fullBounds); int correctRgb = correctImage.getRGB(150, 150); assertEquals(-1, correctRgb); { ReferencedEnvelope zoomIn = fullBounds; for (int i = 1; i < 5; i++) { zoomIn = zoomIn(zoomIn); BufferedImage testImage = TestUtilites.showRender("zomming in step " + i, renderer, 4000, zoomIn); int testRgb = testImage.getRGB(150, 150); // Because we are zooming "into" the center, the color shouldn't change assertEquals( "Just zooming into the mapContext, should not change the color of the center point in this test:", correctRgb, testRgb); } } } /** * Zooms into the center. Helper-method for #testFidFilterWithFeatureSource * * @param bounds * @return */ private ReferencedEnvelope zoomIn(ReferencedEnvelope bounds) { ReferencedEnvelope b2 = new ReferencedEnvelope(bounds); double c = 1. / 8.; b2.expandBy(-b2.getSpan(0) * c, -b2.getSpan(1) * c); return b2; } public void testCreateFeature() throws Exception { ShapefileRenderer renderer = new ShapefileRenderer(null); Style style = LabelingTest.loadStyle("LineStyle.sld"); ShapefileDataStore ds = TestUtilites.getDataStore(shp2.getName()); IndexedDbaseFileReader reader = ShapefileRendererUtil .getDBFReader(ds); SimpleFeatureType type = renderer.createFeatureType(null, style, ds); assertEquals(2, type.getAttributeCount()); assertEquals("NAME", type.getDescriptor(0).getLocalName()); Envelope bounds = ds.getFeatureSource().getBounds(); ShapefileReader shpReader = ShapefileRendererUtil .getShpReader(ds, bounds, new Rectangle(0,0,(int)bounds.getWidth(), (int)bounds.getHeight()), IDENTITY, false, false); SimpleFeatureBuilder builder = new SimpleFeatureBuilder(type); SimpleFeature feature = renderer.createFeature(builder, shpReader.nextRecord(), reader, "id"); shpReader.close(); reader.close(); assertEquals("id", feature.getID()); assertEquals("dave street", feature.getAttribute(0)); } public void testRemoveTransaction() throws Exception { ShapefileDataStore ds = TestUtilites.getDataStore(shp2.getName()); System.out.println("Count: " + ds.getFeatureSource().getCount(Query.ALL)); Style st = TestUtilites.createTestStyle(null, typename); final SimpleFeatureStore store; store = (SimpleFeatureStore) ds.getFeatureSource(); Transaction t = new DefaultTransaction(); store.setTransaction(t); SimpleFeatureCollection collection = store.getFeatures(); SimpleFeatureIterator iter = collection.features(); FeatureId id = TestUtilites.filterFactory.featureId(iter.next().getID()); Id createFidFilter = TestUtilites.filterFactory.id(Collections.singleton(id)); collection.close(iter); store.removeFeatures(createFidFilter); MapContext context = new DefaultMapContext(); context.addLayer(store, st); ShapefileRenderer renderer = new ShapefileRenderer(context); TestUtilites.CountingRenderListener listener = new TestUtilites.CountingRenderListener(getName()); renderer.addRenderListener(listener); Envelope env = context.getLayerBounds(); int boundary = 7; TestUtilites.INTERACTIVE = INTERACTIVE; env = new Envelope(env.getMinX() - boundary, env.getMaxX() + boundary, env.getMinY() - boundary, env.getMaxY() + boundary); TestUtilites.showRender("testTransaction", renderer, 2000, env); assertEquals(2, listener.count); t.commit(); collection = store.getFeatures(); iter = collection.features(); final SimpleFeature feature = iter.next(); collection.close(iter); // now add a new feature new fid should be theme2.4 remove it and assure // that it is not rendered SimpleFeatureType type = store.getSchema(); store.addFeatures(DataUtilities.collection(new SimpleFeature[] { sf } )); //$NON-NLS-1$ t.commit(); System.out.println("Count: " + ds.getFeatureSource().getCount(Query.ALL)); listener.count = 0; TestUtilites.showRender("testTransaction", renderer, 2000, env); assertEquals(3, listener.count); iter = store.getFeatures().features(); SimpleFeature last = null; while (iter.hasNext()) { last = iter.next(); } iter.close(); id = TestUtilites.filterFactory.featureId(last.getID()); store.removeFeatures(TestUtilites.filterFactory.id(Collections.singleton(id))); listener.count = 0; TestUtilites.showRender("testTransaction", renderer, 2000, env); assertEquals(2, listener.count); } public void testAddTransaction() throws Exception { final ShapefileDataStore ds = TestUtilites.getDataStore(shp2.getName()); Style st = TestUtilites.createTestStyle(null, typename); SimpleFeatureStore store = (SimpleFeatureStore) ds.getFeatureSource(); Transaction t = new DefaultTransaction(); store.setTransaction(t); SimpleFeatureCollection collection = store.getFeatures(); SimpleFeatureIterator iter = collection.features(); final SimpleFeature feature = iter.next(); collection.close(iter); SimpleFeatureType type = ds.getSchema(); store.addFeatures(DataUtilities.collection(sf)); MapContext context = new DefaultMapContext(); context.addLayer(store, st); ShapefileRenderer renderer = new ShapefileRenderer(context); TestUtilites.CountingRenderListener listener = new TestUtilites.CountingRenderListener(getName()); renderer.addRenderListener(listener); Envelope env = context.getLayerBounds(); int boundary = 7; TestUtilites.INTERACTIVE = INTERACTIVE; env = new Envelope(env.getMinX() - boundary, env.getMaxX() + boundary, env.getMinY() - boundary, env.getMaxY() + boundary); TestUtilites.showRender("testTransaction", renderer, 2000, env); assertEquals(4, listener.count); } public void testModifyTransaction() throws Exception { ShapefileDataStore ds = TestUtilites.getDataStore(shp2.getName()); Style st = TestUtilites.createTestStyle(null, typename); SimpleFeatureStore store = (SimpleFeatureStore) ds.getFeatureSource(); Transaction t = new DefaultTransaction(); store.setTransaction(t); store.modifyFeatures(ds.getSchema().getDescriptor("NAME"), "bleep", Filter.NONE); MapContext context = new DefaultMapContext(); context.addLayer(store, st); ShapefileRenderer renderer = new ShapefileRenderer(context); TestUtilites.CountingRenderListener listener = new TestUtilites.CountingRenderListener(getName()); renderer.addRenderListener(listener); renderer.addRenderListener(new RenderListener() { public void featureRenderer(SimpleFeature feature) { assertEquals("bleep", feature.getAttribute("NAME")); } public void errorOccurred(Exception e) { e.printStackTrace(); assertFalse(true); } }); Envelope env = context.getLayerBounds(); int boundary = 7; TestUtilites.INTERACTIVE = INTERACTIVE; env = new Envelope(env.getMinX() - boundary, env.getMaxX() + boundary, env.getMinY() - boundary, env.getMaxY() + boundary); TestUtilites.showRender("testTransaction", renderer, 2000, env); assertEquals(3, listener.count); } /** * The {@link ShapefileRenderer} is case-sensitive about its propertynames. This test checks for * the correct Exception when the property is wrongly spelled. * * @throws Exception */ public void testExceptionWhenPropertyDoesntExist() throws Exception { ShapefileDataStore store = TestUtilites.getDataStore("lakes.shp"); // Create a Style with a wrongly spelled property name StyleBuilder sb = new StyleBuilder(); TextSymbolizer ts = sb.createTextSymbolizer(Color.red, sb.createFont("serif", 15.), "ELEVaTION"); // Here is the mistake. The ShapefileRenderer is case-sensitive for // the attributes. Style styleWithWronglySpelledPropertyName = sb.createStyle(ts); /** * ComplexStyle and ShapefileRenderer works NOT ! */ { MapContext context = new DefaultMapContext(); context.addLayer(store.getFeatureSource(), styleWithWronglySpelledPropertyName); int w = 300; int h = 300; final BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); Graphics g = image.getGraphics(); g.setColor(Color.white); g.fillRect(0, 0, w, h); ShapefileRenderer renderer = new ShapefileRenderer(); TestUtilites.ExceptionCollectorRenderListener listenerForEx = new TestUtilites.ExceptionCollectorRenderListener(getName()); renderer.addRenderListener(listenerForEx); renderer.setContext(context); renderer.paint((Graphics2D) g, new Rectangle(w, h), context.getLayerBounds()); assertEquals("Exactly one exception should have been thrown", 1, listenerForEx.exceptions.size()); assertTrue("The Exception catched is not of expected type IllegalArgumentException", listenerForEx.exceptions.get(0) instanceof IllegalArgumentException); assertEquals("The IllegalArgumentException catched doesn't have the expected message.", "Attribute ELEVaTION does not exist. Maybe it has just been spelled wrongly?", listenerForEx.exceptions.get(0).getMessage()); } } }