/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2015, 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 java.awt.RenderingHints.KEY_ANTIALIASING; import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Frame; import java.awt.Graphics; import java.awt.HeadlessException; import java.awt.Panel; import java.awt.RenderingHints; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; import javax.imageio.ImageIO; import org.geotools.data.property.PropertyDataStore; import org.geotools.data.simple.SimpleFeatureSource; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.map.FeatureLayer; import org.geotools.map.MapContent; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.geotools.renderer.label.LabelCacheImpl; import org.geotools.renderer.style.FontCache; import org.geotools.styling.Style; import org.geotools.test.TestData; import org.geotools.util.logging.Logging; import junit.framework.TestCase; /** * Confirm functionality of letter level conflict detection. */ public class LetterConflictTest extends TestCase { static final Logger LOGGER = Logging.getLogger(LetterConflictTest.class); /** * Makes the test interactive, showing a Swing dialog with image. * <p> * Build with mvn -P image.interactive */ static final boolean IMAGE_INTERACTIVE = Boolean.getBoolean("org.geotools.image.test.interactive"); /** * Forces the image comparison / output tests to be skipped. * * Build with org.geotools.image.test.skip=true */ static private boolean IMAGE_SKIP = Boolean.getBoolean("org.geotools.image.test.skip"); /** * Write images to temp directory for debugging. * * Test with -D org.geotools.image.test.output=true */ static private boolean OUTPUT_IMAGE = Boolean.getBoolean("org.geotools.image.test.output"); private static final long TIME = 5000; SimpleFeatureSource fs_line1; SimpleFeatureSource fs_line2; SimpleFeatureSource fs_line3; SimpleFeatureSource fs_line4; ReferencedEnvelope bounds1; ReferencedEnvelope bounds2; @Override protected void setUp() throws Exception { File property_line = new File(TestData.getResource(this, "letterConflict1.properties").toURI()); PropertyDataStore ds_line = new PropertyDataStore(property_line.getParentFile()); fs_line1 = ds_line.getFeatureSource("letterConflict1"); fs_line2 = ds_line.getFeatureSource("letterConflict2"); fs_line3 = ds_line.getFeatureSource("letterConflict3"); bounds1 = new ReferencedEnvelope(-10, 10, -10, 10, DefaultGeographicCRS.WGS84); fs_line4 = ds_line.getFeatureSource("letterConflict4"); bounds2 = new ReferencedEnvelope(20, 80, 23, 86, DefaultGeographicCRS.WGS84); FontCache.getDefaultInstance().registerFont(Font.createFont(Font.TRUETYPE_FONT, TestData.getResource(this, "Vera.ttf").openStream())); } private StreamingRenderer getNewRenderer(MapContent context) { StreamingRenderer renderer = new StreamingRenderer(); Map<String,Object> rendererParams = new HashMap<String,Object>(); LabelCacheImpl labelCache = new LabelCacheImpl(); rendererParams.put(StreamingRenderer.LABEL_CACHE_KEY, labelCache); renderer.setRendererHints(rendererParams); renderer.setJava2DHints(new RenderingHints(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON)); renderer.setMapContent(context); return renderer; } public void testLetterConflictEnabled() throws Exception { LabelCacheImpl.DISABLE_LETTER_LEVEL_CONFLICT = true; Style style = RendererBaseTest.loadStyle(this, "letterConflict20.sld"); MapContent mc = new MapContent(); mc.getViewport().setCoordinateReferenceSystem(DefaultGeographicCRS.WGS84); mc.addLayer( new FeatureLayer( fs_line1, style)); StreamingRenderer renderer = getNewRenderer(mc); final BufferedImage image1 = RendererBaseTest.renderImage(renderer, bounds1, null); mc.dispose(); LabelCacheImpl.DISABLE_LETTER_LEVEL_CONFLICT = false; style = RendererBaseTest.loadStyle(this, "letterConflict20.sld"); mc = new MapContent(); mc.getViewport().setCoordinateReferenceSystem(DefaultGeographicCRS.WGS84); mc.addLayer( new FeatureLayer( fs_line1, style)); renderer = getNewRenderer(mc); final BufferedImage image2 = RendererBaseTest.renderImage(renderer, bounds1, null); mc.dispose(); assertTrue("More labels in image2 than image1", countPixels(image2, Color.BLACK) >= countPixels(image1, Color.BLACK)); writeImage("letterConflictEnabledFalse", image1); writeImage("letterConflictEnabledTrue", image2); showImage("letterConflictEnabled false", TIME, image1); showImage("letterConflictEnabled true", TIME, image2); } public void testLetterConflictEnabled2Lines() throws Exception { LabelCacheImpl.DISABLE_LETTER_LEVEL_CONFLICT = true; Style style = RendererBaseTest.loadStyle(this, "letterConflict20.sld"); MapContent mc = new MapContent(); mc.getViewport().setCoordinateReferenceSystem(DefaultGeographicCRS.WGS84); mc.addLayer(new FeatureLayer(fs_line2, style)); StreamingRenderer renderer = getNewRenderer(mc); final BufferedImage image1 = RendererBaseTest.renderImage(renderer, bounds1, null); mc.dispose(); LabelCacheImpl.DISABLE_LETTER_LEVEL_CONFLICT = false; style = RendererBaseTest.loadStyle(this, "letterConflict20.sld"); mc = new MapContent(); mc.getViewport().setCoordinateReferenceSystem(DefaultGeographicCRS.WGS84); mc.addLayer(new FeatureLayer(fs_line2, style)); renderer = getNewRenderer(mc); final BufferedImage image2 = RendererBaseTest.renderImage(renderer, bounds1, null); assertTrue("More labels in image2 than image1", countPixels(image2, Color.BLACK) > countPixels(image1, Color.BLACK)); writeImage("letterConflictEnabled2False",image1); writeImage("letterConflictEnabled2True",image2); showImage("letterConflictEnabled2 false", TIME, image1); showImage("letterConflictEnabled2 true", TIME, image2); mc.dispose(); } public void testLetterConflictEnabledCurvedLine() throws Exception { LabelCacheImpl.DISABLE_LETTER_LEVEL_CONFLICT = true; Style style = RendererBaseTest.loadStyle(this, "letterConflict20.sld"); MapContent mc = new MapContent(); mc.getViewport().setCoordinateReferenceSystem(DefaultGeographicCRS.WGS84); mc.addLayer( new FeatureLayer( fs_line3, style)); StreamingRenderer renderer = getNewRenderer(mc); final BufferedImage image1 = RendererBaseTest.renderImage(renderer, bounds1, null); mc.dispose(); LabelCacheImpl.DISABLE_LETTER_LEVEL_CONFLICT = false; style = RendererBaseTest.loadStyle(this, "letterConflict20.sld"); mc = new MapContent(); mc.getViewport().setCoordinateReferenceSystem(DefaultGeographicCRS.WGS84); mc.addLayer( new FeatureLayer( fs_line3, style)); renderer = getNewRenderer(mc); final BufferedImage image2 = RendererBaseTest.renderImage(renderer, bounds1, null); mc.dispose(); assertTrue("More labels in image2 than image1", countPixels(image2, Color.BLACK) > countPixels(image1, Color.BLACK)); writeImage("letterConflictEnabledCurvedLineFalse", image1); writeImage("letterConflictEnabledCurvedLineTrue", image2); showImage("letterConflictEnabledCurvedLine false", TIME, image1); showImage("letterConflictEnabledCurvedLine true", TIME, image2); } public void testLetterConflictEnabledPerf() throws Exception { synchronized (LabelCacheImpl.class) { LabelCacheImpl.DISABLE_LETTER_LEVEL_CONFLICT = true; Style style = RendererBaseTest.loadStyle(this, "letterConflict6.sld"); MapContent mc = new MapContent(); mc.getViewport().setCoordinateReferenceSystem(DefaultGeographicCRS.WGS84); mc.addLayer( new FeatureLayer( fs_line4, style)); StreamingRenderer renderer = getNewRenderer(mc); BufferedImage image1 = RendererBaseTest.renderImage(renderer, bounds2, null); long t0, t1, t2, t3; long ta = 0; for (int i = 0; i < 10; i++) { renderer = getNewRenderer(mc); t0 = System.nanoTime(); image1 = RendererBaseTest.renderImage(renderer, bounds2, null); t1 = System.nanoTime(); ta += (t1 - t0); } LOGGER.fine("time false " + ta / 10000000); mc.dispose(); LabelCacheImpl.DISABLE_LETTER_LEVEL_CONFLICT = false; mc = new MapContent(); mc.getViewport().setCoordinateReferenceSystem(DefaultGeographicCRS.WGS84); mc.addLayer( new FeatureLayer( fs_line4, style)); renderer = getNewRenderer(mc); BufferedImage image2 = RendererBaseTest.renderImage(renderer, bounds2, null); ta = 0; for (int i = 0; i < 10; i++) { renderer = getNewRenderer(mc); t2 = System.nanoTime(); image2 = RendererBaseTest.renderImage(renderer, bounds2, null); t3 = System.nanoTime(); //System.out.println("time true " + (t3 - t2) + " ms"); ta += (t3 - t2); } LOGGER.fine("time true " + ta / 10000000); mc.dispose(); assertTrue("More labels in image2 than image1", countDarkPixels(image2) >= countDarkPixels(image1)); writeImage("letterConflictEnabledPerfFalse",image1); writeImage("letterConflictEnabledPerfTrue",image2); showImage("letterConflictEnabledPref false", TIME, image1); showImage("letterConflictEnabledPerf true", TIME, image2); } } public int countPixels(BufferedImage bi, Color color) { int count = 0; for (int i = 0 ; i < bi.getWidth() ; i++) { for (int j = 0 ; j < bi.getHeight() ; j++) { if (bi.getRGB(i,j) == color.getRGB()) count++; } } return count; } public int countDarkPixels(BufferedImage bi) { int count = 0; for (int i = 0 ; i < bi.getWidth() ; i++) { for (int j = 0 ; j < bi.getHeight() ; j++) { Color col = new Color(bi.getRGB(i,j)); if (col.getBlue() < 127 && col.getGreen() < 127 && col.getRed() < 127) count++; } } return count; } /** * Internal utility method used to write out image for debugging purposes. * * @param testName * @param image * @throws IOException */ static void writeImage(String testName, BufferedImage image) throws IOException { if( IMAGE_SKIP) return; if (OUTPUT_IMAGE) { File tmpFile = File.createTempFile("geotools-" + testName, ".png"); ImageIO.write(image, "png", tmpFile); } } /** * Internal utility method used to display an image for interactive tests. * * @param testName test name used as window name * @param timeOut * @param image * @throws InterruptedException */ static void showImage(String testName, long timeOut, final BufferedImage image) throws InterruptedException { boolean HEADLESS = Boolean.getBoolean("java.awt.headless"); if( HEADLESS || IMAGE_SKIP) { return; // obvious reasons to skip showing the image } if (IMAGE_INTERACTIVE && TestData.isInteractiveTest()) { try { Frame frame = new Frame(testName); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { e.getWindow().dispose(); } }); Panel p = new Panel() { /** <code>serialVersionUID</code> field */ private static final long serialVersionUID = 1L; { setPreferredSize(new Dimension(image.getWidth(), image.getHeight())); } public void paint(Graphics g) { g.drawImage(image, 0, 0, this); } }; frame.add(p); frame.pack(); frame.setVisible(true); Thread.sleep(timeOut); frame.dispose(); } catch (HeadlessException exception) { // The test is running on a machine without X11 display. Ignore. } } } }