/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 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.mbstyle.transform; import org.geotools.TestData; import org.geotools.coverage.grid.io.GridCoverage2DReader; import org.geotools.data.property.PropertyDataStore; import org.geotools.data.simple.SimpleFeatureSource; import org.geotools.factory.CommonFactoryFinder; import org.geotools.gce.geotiff.GeoTiffReader; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.image.test.ImageAssert; import org.geotools.map.FeatureLayer; import org.geotools.map.MapContent; import org.geotools.mbstyle.MBStyle; import org.geotools.mbstyle.MapboxTestUtils; import org.geotools.referencing.CRS; import org.geotools.renderer.lite.RendererUtilities; import org.geotools.renderer.lite.StreamingRenderer; import org.geotools.renderer.lite.gridcoverage2d.GridCoverageRenderer; import org.geotools.styling.*; import org.geotools.styling.Stroke; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.opengis.filter.FilterFactory; import org.opengis.referencing.crs.CoordinateReferenceSystem; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; import java.io.File; import java.io.IOException; import java.util.Arrays; import javax.media.jai.Interpolation; import static java.awt.RenderingHints.KEY_ANTIALIASING; import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON; import static org.junit.Assert.*; /** * Perceptual tests for {@link MBStyleTransformer}. * * In order to display the visual tests as they run, uncomment the following line below: * * // System.setProperty("org.geotools.test.interactive", "true"); * * HOW TO ADD A NEW TEST: * * The first time you run a new test, the reference image must be generated. To do so, run the test with * <code>-Dorg.geotools.image.test.interactive=true</code>. * */ public class VisualTransformerTest { JSONParser jsonParser = new JSONParser(); private static final long DISPLAY_TIME = 1000; static StyleFactory styleFactory = CommonFactoryFinder.getStyleFactory(); static FilterFactory filterFactory = CommonFactoryFinder.getFilterFactory(); SimpleFeatureSource pointFS; SimpleFeatureSource gridFS; SimpleFeatureSource lineFS; SimpleFeatureSource lineZigFS; SimpleFeatureSource bgFS; SimpleFeatureSource polygonFS; SimpleFeatureSource polygonsBigFS; ReferencedEnvelope bounds; @BeforeClass public static void setupClass() { System.clearProperty("org.geotools.referencing.forceXY"); CRS.reset("all"); } @Before public void setUp() throws Exception { File property = new File(TestData.getResource(this, "testpoints.properties").toURI()); PropertyDataStore ds = new PropertyDataStore(property.getParentFile()); pointFS = ds.getFeatureSource("testpoints"); gridFS = ds.getFeatureSource("testgrid"); polygonFS = ds.getFeatureSource("testpolygons"); polygonsBigFS = ds.getFeatureSource("testpolygonsbig"); lineFS = ds.getFeatureSource("testlines"); lineZigFS = ds.getFeatureSource("testlinezigs"); bounds = new ReferencedEnvelope(0, 10, 0, 10, CRS.decode("EPSG:4326")); // UNCOMMENT THE BELOW LINE TO DISPLAY VISUAL TESTS // System.setProperty("org.geotools.test.interactive", "true"); } /** * Test generation of a GeoTools style from an MBFillLayer */ @Test public void mbFillLayerVisualTest() throws Exception { // Read file to JSONObject JSONObject jsonObject = MapboxTestUtils.parseTestStyle("fillStyleTest.json"); // Get the style MBStyle mbStyle = new MBStyle(jsonObject); StyledLayerDescriptor sld = mbStyle.transform(); UserLayer l = (UserLayer) sld.layers().get(0); Style style = l.getUserStyles()[0]; MapContent mc = new MapContent(); mc.addLayer(new FeatureLayer(polygonFS, style)); StreamingRenderer renderer = new StreamingRenderer(); renderer.setMapContent(mc); renderer.setJava2DHints(new RenderingHints(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON)); BufferedImage image = MapboxTestUtils.showRender("Fill Test", renderer, DISPLAY_TIME, new ReferencedEnvelope[] { bounds }, null); ImageAssert.assertEquals(file("fill"), image, 50); mc.dispose(); } /** * Test generation of a GeoTools style from an MBFillLayer */ @Test public void mbFillLayerAllPropertiesVisualTest() throws Exception { // Read file to JSONObject JSONObject jsonObject = MapboxTestUtils.parseTestStyle("fillStyleTestAllProperties.json"); // Get the style MBStyle mbStyle = new MBStyle(jsonObject); StyledLayerDescriptor sld = mbStyle.transform(); UserLayer l = (UserLayer) sld.layers().get(0); Style style = l.getUserStyles()[0]; MapContent mc = new MapContent(); mc.addLayer(new FeatureLayer(polygonFS, style)); StreamingRenderer renderer = new StreamingRenderer(); renderer.setMapContent(mc); renderer.setJava2DHints(new RenderingHints(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON)); BufferedImage image = MapboxTestUtils.showRender("Fill Test", renderer, DISPLAY_TIME, new ReferencedEnvelope[] { bounds }, null); ImageAssert.assertEquals(file("fill-test-all"), image, 50); mc.dispose(); } /** * Test generation of a GeoTools style from an MBFillLayer (using a {tokenized} sprite fill pattern) */ @Test public void mbFillLayerSpritesTokenizedVisualTest() throws Exception { // Read file to JSONObject JSONObject jsonObject = MapboxTestUtils.parseTestStyle("fillStyleTokenizedSpriteTest.json"); // Get the style MBStyle mbStyle = new MBStyle(jsonObject); StyledLayerDescriptor sld = mbStyle.transform(); UserLayer l = (UserLayer) sld.layers().get(0); Style style = l.getUserStyles()[0]; MapContent mc = new MapContent(); mc.addLayer(new FeatureLayer(polygonsBigFS, style)); StreamingRenderer renderer = new StreamingRenderer(); renderer.setMapContent(mc); renderer.setJava2DHints(new RenderingHints(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON)); BufferedImage image = MapboxTestUtils.showRender("Fill Test", renderer, DISPLAY_TIME, new ReferencedEnvelope[] { bounds }, null); ImageAssert.assertEquals(file("fill-sprite-tokenized"), image, 50); mc.dispose(); } /** * Test generation of a GeoTools style from an MBFillLayer (using a constant sprite fill pattern) */ @Test public void mbFillLayerSpritesVisualTest() throws Exception { // Read file to JSONObject JSONObject jsonObject = MapboxTestUtils.parseTestStyle("fillStyleSpriteTest.json"); // Get the style MBStyle mbStyle = new MBStyle(jsonObject); StyledLayerDescriptor sld = mbStyle.transform(); UserLayer l = (UserLayer) sld.layers().get(0); Style style = l.getUserStyles()[0]; MapContent mc = new MapContent(); mc.addLayer(new FeatureLayer(polygonsBigFS, style)); StreamingRenderer renderer = new StreamingRenderer(); renderer.setMapContent(mc); renderer.setJava2DHints(new RenderingHints(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON)); BufferedImage image = MapboxTestUtils.showRender("Fill Test", renderer, DISPLAY_TIME, new ReferencedEnvelope[] { bounds }, null); ImageAssert.assertEquals(file("fill-sprite"), image, 50); mc.dispose(); } /** * Test generation of a GeoTools style from an MBFillLayer (using a function-defined sprite fill pattern) */ @Test public void mbFillLayerSpritesFunctionTest() throws Exception { // Read file to JSONObject JSONObject jsonObject = MapboxTestUtils.parseTestStyle("fillStyleFunctionSpriteTest.json"); // Get the style MBStyle mbStyle = new MBStyle(jsonObject); StyledLayerDescriptor sld = mbStyle.transform(); UserLayer l = (UserLayer) sld.layers().get(0); Style style = l.getUserStyles()[0]; MapContent mc = new MapContent(); mc.addLayer(new FeatureLayer(polygonsBigFS, style)); StreamingRenderer renderer = new StreamingRenderer(); renderer.setMapContent(mc); renderer.setJava2DHints(new RenderingHints(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON)); BufferedImage image = MapboxTestUtils.showRender("Fill Test", renderer, DISPLAY_TIME, new ReferencedEnvelope[] { bounds }, null); ImageAssert.assertEquals(file("fill-sprite-function"), image, 50); mc.dispose(); } /** * Test visualization of a GeoTools style from an MB Symbol Layer */ @Test public void mbSymbolLayerSpritesVisualTest() throws Exception { // Read file to JSONObject JSONObject jsonObject = MapboxTestUtils.parseTestStyle("symbolStyleSimpleIconTest.json"); // Get the style MBStyle mbStyle = new MBStyle(jsonObject); StyledLayerDescriptor sld = mbStyle.transform(); UserLayer l = (UserLayer) sld.layers().get(0); Style style = l.getUserStyles()[0]; MapContent mc = new MapContent(); mc.addLayer(new FeatureLayer(pointFS, style)); StreamingRenderer renderer = new StreamingRenderer(); renderer.setMapContent(mc); renderer.setJava2DHints(new RenderingHints(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON)); BufferedImage image = MapboxTestUtils.showRender("Symbol Sprite Test", renderer, DISPLAY_TIME, new ReferencedEnvelope[] { bounds }, null); ImageAssert.assertEquals(file("symbol-sprite"), image, 50); mc.dispose(); } /** * Test visualization of a GeoTools style from an MB Symbol Layer */ @Test public void mbSymbolLayerTextVisualTest() throws Exception { JSONObject jsonObject = MapboxTestUtils.parseTestStyle("symbolTextTest.json"); testVisualizeStyleWithPointFeatures(jsonObject, "Symbol Text Test", "symbol-text", true, 300, 300); } /** * Test visualization of a GeoTools style from an MB Symbol Layer */ @Test public void mbSymbolLayerTextAndIconVisualTest() throws Exception { JSONObject jsonObject = MapboxTestUtils.parseTestStyle("symbolTextAndIconTest.json"); testVisualizeStyleWithPointFeatures(jsonObject, "Symbol Text and Icon Test", "symbol-text-icon", true, 450, 450); } /** * Test visualization of a GeoTools style from an MB Symbol Layer */ @Test public void mbSymbolLayerHaloTextVisualTest() throws Exception { JSONObject jsonObject = MapboxTestUtils.parseTestStyle("symbolTextHaloTest.json"); testVisualizeStyleWithPointFeatures(jsonObject, "Symbol Text Halo Test", "symbol-halo-text", true, 300, 300); } /** * Test visualization of a GeoTools style from an MB Symbol Layer */ @Test public void mbSymbolLayerTextLinePlacementVisualTest() throws Exception { // Read file to JSONObject JSONObject jsonObject = MapboxTestUtils.parseTestStyle("symbolTextLinePlacementTest.json"); testVisualizeStyleWithLineFeatures(jsonObject, "Symbol Text Line Placement", "symbol-text-line-placement", true); } /** * Test visualization of a GeoTools style from an MB Symbol Layer */ @Test public void mbSymbolLayerIconLinePlacementVisualTest() throws Exception { // Read file to JSONObject JSONObject jsonObject = MapboxTestUtils.parseTestStyle("symbolStyleSimpleIconLinePlacementTest.json"); testVisualizeStyleWithLineFeatures(jsonObject, "Symbol Icon Line Placement", "symbol-icon-line-placement", true); } /** * Test visualization of a GeoTools style from an MB Symbol Layer */ @Test public void mbSymbolLayerIconAndTextLinePlacementVisualTest() throws Exception { // Read file to JSONObject JSONObject jsonObject = MapboxTestUtils.parseTestStyle("symbolStyleSimpleIconAndTextLinePlacementTest.json"); testVisualizeStyleWithLineFeatures(jsonObject, "Symbol Text+Icon Line Placement", "symbol-text-icon-line-placement", true); } /** * Test visualization of a GeoTools style from an MB Circle Layer */ @Test public void mbCircleLayerVisualTest() throws Exception { // Read file to JSONObject JSONObject jsonObject = MapboxTestUtils.parseTestStyle("circleStyleTest.json"); testVisualizeStyleWithPointFeatures(jsonObject, "Circle Style Test", "circle-style-test", true, 300, 300); } /** * * Test visualization of a GeoTools style from an MB Circle Layer using defaults */ @Test public void mbCircleLayerDefaultsVisualTest() throws Exception { // Read file to JSONObject JSONObject jsonObject = MapboxTestUtils.parseTestStyle("circleStyleTestDefaults.json"); testVisualizeStyleWithPointFeatures(jsonObject, "Circle Style Test Defaults", "circle-style-test-defaults", true, 300, 300); } /** * Test visualization of a GeoTools style from an MB Circle Layer with opacity and overlaps */ @Test public void mbCircleLayerOverlapVisualTest() throws Exception { // Read file to JSONObject JSONObject jsonObject = MapboxTestUtils.parseTestStyle("circleStyleTestOverlap.json"); testVisualizeStyleWithPointFeatures(jsonObject, "Circle Style Test Overlap", "circle-style-test-overlap", true, 300, 300); } @Test public void mbLineLayerTest() throws Exception { JSONObject jsonObject = MapboxTestUtils.parseTestStyle("lineStyleTest.json"); testVisualizeStyleWithLineFeatures(jsonObject, "Line Style", "line-style", true); } @Test public void mbLineLayerAllPropertiesTest() throws Exception { JSONObject jsonObject = MapboxTestUtils.parseTestStyle("lineStyleTestAllProperties.json"); // Get the style MBStyle mbStyle = new MBStyle(jsonObject); StyledLayerDescriptor sld = mbStyle.transform(); UserLayer l = (UserLayer) sld.layers().get(0); Style style = l.getUserStyles()[0]; MapContent mc = new MapContent(); // mc.addLayer(new FeatureLayer(lineFS, defaultLineStyle())); mc.addLayer(new FeatureLayer(lineZigFS, style)); StreamingRenderer renderer = new StreamingRenderer(); renderer.setMapContent(mc); renderer.setJava2DHints(new RenderingHints(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON)); BufferedImage image = MapboxTestUtils.showRender("Line Style", renderer, DISPLAY_TIME, new ReferencedEnvelope[] { bounds }, null); ImageAssert.assertEquals(file("line-style-all-props"), image, 5000); mc.dispose(); } @Test public void mbLineLayerSpriteTest() throws Exception { JSONObject jsonObject = MapboxTestUtils.parseTestStyle("lineStyleSpriteTest.json"); testVisualizeStyleWithLineFeatures(jsonObject, "Line Style w Sprite", "line-style-sprite", true); } /** * Test specifying displacement as an array function. */ @Test public void testDisplacementAsInterpolatedFunction() throws Exception { // Read file to JSONObject JSONObject jsonObject = MapboxTestUtils.parseTestStyle("testDisplacementAsFunction.json"); testVisualizeStyleWithPointFeatures(jsonObject, "Circle Style Test Displacement Function", "circle-style-displacement-fn-test", true, 300, 300); } @Test public void testRasterLayer() throws Exception { File coverageFile = TestData.copy(this, "geotiff/worldPalette.tiff"); assertTrue(coverageFile.exists()); GridCoverage2DReader worldPaletteReader = new GeoTiffReader(coverageFile); CoordinateReferenceSystem googleMercator = CRS.decode("EPSG:3857"); ReferencedEnvelope mapExtent = new ReferencedEnvelope(-20037508.34, 20037508.34, -20037508.34, 20037508.34, googleMercator); Rectangle screenSize = new Rectangle(200, (int) (mapExtent.getHeight() / mapExtent.getWidth() * 200)); AffineTransform w2s = RendererUtilities.worldToScreenTransform(mapExtent, screenSize); GridCoverageRenderer renderer = new GridCoverageRenderer(googleMercator, mapExtent, screenSize, w2s); JSONObject jsonObject = MapboxTestUtils.parseTestStyle("rasterStyleTestAllProperties.json"); MBStyle mbStyle = new MBStyle(jsonObject); StyledLayerDescriptor sld = mbStyle.transform(); UserLayer l = (UserLayer) sld.layers().get(0); Style style = l.getUserStyles()[0]; RasterSymbolizer s = (RasterSymbolizer)style.featureTypeStyles().get(0).getRules()[0].getSymbolizers()[0]; RenderedImage image = renderer.renderImage(worldPaletteReader, null, s, Interpolation.getInstance(Interpolation.INTERP_BICUBIC), null, 256, 256); ImageAssert.assertEquals(file("raster"), image, 50); } public void testVisualizeStyleWithPointFeatures(JSONObject jsonStyle, String renderTitle, String renderComparisonFileName, boolean includeGrid, int width, int height) throws Exception { // Read file to JSONObject // Get the style MBStyle mbStyle = new MBStyle(jsonStyle); StyledLayerDescriptor sld = mbStyle.transform(); UserLayer l = (UserLayer) sld.layers().get(0); Style style = l.getUserStyles()[0]; MapContent mc = new MapContent(); if (includeGrid) { mc.addLayer(new FeatureLayer(gridFS, defaultLineStyle())); } mc.addLayer(new FeatureLayer(pointFS, style)); StreamingRenderer renderer = new StreamingRenderer(); renderer.setMapContent(mc); renderer.setJava2DHints(new RenderingHints(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON)); BufferedImage image = MapboxTestUtils.showRender(renderTitle, renderer, DISPLAY_TIME, new ReferencedEnvelope[] { bounds }, null, width, height); ImageAssert.assertEquals(file(renderComparisonFileName), image, 50); mc.dispose(); } public void testVisualizeStyleWithLineFeatures(JSONObject jsonStyle, String renderTitle, String renderComparisonFileName, boolean showLines) throws Exception { // Read file to JSONObject // Get the style MBStyle mbStyle = new MBStyle(jsonStyle); StyledLayerDescriptor sld = mbStyle.transform(); UserLayer l = (UserLayer) sld.layers().get(0); Style style = l.getUserStyles()[0]; MapContent mc = new MapContent(); if (showLines) { mc.addLayer(new FeatureLayer(lineFS, defaultLineStyle())); } mc.addLayer(new FeatureLayer(lineFS, style)); StreamingRenderer renderer = new StreamingRenderer(); renderer.setMapContent(mc); renderer.setJava2DHints(new RenderingHints(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON)); BufferedImage image = MapboxTestUtils.showRender(renderTitle, renderer, DISPLAY_TIME, new ReferencedEnvelope[] { bounds }, null); ImageAssert.assertEquals(file(renderComparisonFileName), image, 50); mc.dispose(); } public Style defaultLineStyle() { Rule rule = styleFactory.createRule(); Stroke stroke = styleFactory.createStroke(filterFactory.literal(Color.BLACK), filterFactory.literal(1)); rule.symbolizers().add(styleFactory.createLineSymbolizer(stroke, null)); FeatureTypeStyle fts = styleFactory.createFeatureTypeStyle(new Rule[] { rule }); Style lineStyle = styleFactory.createStyle(); lineStyle.featureTypeStyles().addAll(Arrays.asList(fts)); return lineStyle; } public Style defaultPointStyle() { Graphic gr = styleFactory.createDefaultGraphic(); Mark mark = styleFactory.getCircleMark(); mark.setStroke(styleFactory.createStroke(filterFactory.literal(Color.BLACK), filterFactory.literal(1))); mark.setFill(styleFactory.createFill(filterFactory.literal(Color.BLACK))); gr.graphicalSymbols().clear(); gr.graphicalSymbols().add(mark); gr.setSize(filterFactory.literal(1)); Rule rule = styleFactory.createRule(); PointSymbolizer p = styleFactory.createPointSymbolizer(gr, null); rule.symbolizers().add(p); FeatureTypeStyle fts = styleFactory.createFeatureTypeStyle(new Rule[] { rule }); Style pointStyle = styleFactory.createStyle(); pointStyle.featureTypeStyles().addAll(Arrays.asList(fts)); return pointStyle; } public Style defaultPolyStyle() { // create a partially opaque outline stroke Stroke stroke = styleFactory.createStroke(filterFactory.literal(Color.BLACK), filterFactory.literal(1), filterFactory.literal(1)); // create a partial opaque fill Fill fill = styleFactory.createFill(filterFactory.literal(Color.BLACK), filterFactory.literal(1)); /* * Setting the geometryPropertyName arg to null signals that we want to draw the default geometry of features */ PolygonSymbolizer sym = styleFactory.createPolygonSymbolizer(stroke, fill, null); Rule rule = styleFactory.createRule(); rule.symbolizers().add(sym); FeatureTypeStyle fts = styleFactory.createFeatureTypeStyle(new Rule[] { rule }); Style style = styleFactory.createStyle(); style.featureTypeStyles().add(fts); return style; } File file(String name) throws IOException { // The first time you run a new test, the reference image must be generated. To do so, run the test with // -Dorg.geotools.image.test.interactive=true</code> return new File("src/test/resources/org/geotools/mbstyle/transform/test-data/rendered/" + name + ".png"); } }