/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2017, 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.sprite;
import org.geotools.TestData;
import org.geotools.data.property.PropertyDataStore;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.factory.CommonFactoryFinder;
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.MapboxTestUtils;
import org.geotools.referencing.CRS;
import org.geotools.renderer.lite.StreamingRenderer;
import org.geotools.renderer.style.DynamicSymbolFactoryFinder;
import org.geotools.renderer.style.ExternalGraphicFactory;
import org.geotools.styling.*;
import org.json.simple.parser.JSONParser;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.opengis.filter.FilterFactory;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.Iterator;
import static java.awt.RenderingHints.KEY_ANTIALIASING;
import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON;
import static org.junit.Assert.*;
/**
* Tests for {@link SpriteGraphicFactory}.
*
* Some of the tests are perceptual. In order to display these tests as they run, uncomment the following line below:
*
* // System.setProperty("org.geotools.test.interactive", "true");
*
* HOW TO ADD A NEW PERCEPTUAL 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 MapboxSpriteTest {
protected SpriteGraphicFactory mgf = new SpriteGraphicFactory();
JSONParser jsonParser = new JSONParser();
private static final long DISPLAY_TIME = 5000;
static StyleFactory styleFactory = CommonFactoryFinder.getStyleFactory();
static FilterFactory filterFactory = CommonFactoryFinder.getFilterFactory();
String spriteBaseUrl;
SimpleFeatureSource pointFS;
SimpleFeatureSource bgFS;
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");
bgFS = ds.getFeatureSource("background");
bounds = new ReferencedEnvelope(0, 10, 0, 10, CRS.decode("EPSG:4326"));
// The sprite base url should not have an extension.
URL url = this.getClass().getResource("test-data/sprite.png");
String urlStr = url.toExternalForm();
spriteBaseUrl = urlStr.substring(0, urlStr.lastIndexOf(".png"));
// UNCOMMENT THE BELOW LINE TO SHOW VISUAL TESTS
// System.setProperty("org.geotools.test.interactive", "true");
}
/**
* Test that the {@link SpriteGraphicFactory} is registered with the {@link DynamicSymbolFactoryFinder}.
*/
@Test
public void testServiceRegistered() {
Iterator<ExternalGraphicFactory> it = DynamicSymbolFactoryFinder
.getExternalGraphicFactories();
boolean foundIt = false;
while (it.hasNext()) {
ExternalGraphicFactory egf = it.next();
if (egf instanceof SpriteGraphicFactory) {
foundIt = true;
}
}
assertTrue(foundIt);
}
/**
*
* Perform a perceptual test verifying that icons can be correctly retrieved by name from a sprite sheet.
*/
@Test
public void testRenderSpriteIcons() throws Exception {
// Use the feature's "icon" property.
String iconUrl = constructSpriteUrl(spriteBaseUrl, "${icon}");
MapContent mc = new MapContent();
ExternalGraphic eg = styleFactory.createExternalGraphic(iconUrl, "mbsprite");
// Also add a simple background layer.
mc.addLayer(
new FeatureLayer(bgFS,
SLD.wrapSymbolizers(styleFactory.createPolygonSymbolizer(null,
styleFactory.createFill(
filterFactory.literal("#6ba3ff")),
null))));
mc.addLayer(new FeatureLayer(pointFS, pointStyleWithExternalGraphic(eg, null)));
// Render the image and do a perceptual assert.
StreamingRenderer renderer = new StreamingRenderer();
renderer.setMapContent(mc);
renderer.setJava2DHints(new RenderingHints(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON));
BufferedImage image = MapboxTestUtils.showRender("Mapbox Sprite Icon Test", renderer,
DISPLAY_TIME, new ReferencedEnvelope[] { bounds }, null);
ImageAssert.assertEquals(file("rendered-icons"), image, 50);
}
/**
* Test that {@link SpriteGraphicFactory} correctly parses both the sprite base URL and the icon name from External Graphic URLs.
*
* @throws MalformedURLException
*/
@Test
public void testSpriteUrlFunctions() throws MalformedURLException {
URL url = new URL("file:/GeoServerDataDirs/release/styles/testSpritesheet#someIconName");
assertEquals("file:/GeoServerDataDirs/release/styles/testSpritesheet", SpriteGraphicFactory.parseBaseUrl(url).toExternalForm());
assertEquals("someIconName", SpriteGraphicFactory.parseIconName(url));
URL url2x = new URL("file:/GeoServerDataDirs/release/styles/testSpritesheet@2x#someIconName");
assertEquals("file:/GeoServerDataDirs/release/styles/testSpritesheet@2x", SpriteGraphicFactory.parseBaseUrl(url2x).toExternalForm());
assertEquals("someIconName", SpriteGraphicFactory.parseIconName(url2x));
}
/**
* Test that {@link SpriteGraphicFactory} correctly fetches and parses a sprite index file.
*
* @throws IOException
* @throws MalformedURLException
*/
@Test
public void testParseSpriteIndexFile() throws MalformedURLException, IOException {
SpriteIndex spriteIndex = mgf.getSpriteIndex(new URL(spriteBaseUrl));
assertNotNull(spriteIndex);
assertEquals(6, spriteIndex.getIcons().keySet().size());
assertTrue(spriteIndex.getIcons().keySet().containsAll(Arrays.asList("bomb", "face", "goldfish", "owl", "owlhead", "pattern")));
assertEquals(32, spriteIndex.getIcon("goldfish").getHeight());
assertEquals(32, spriteIndex.getIcon("goldfish").getWidth());
assertEquals(1, spriteIndex.getIcon("goldfish").getPixelRatio());
assertEquals(64, spriteIndex.getIcon("goldfish").getX());
assertEquals(64, spriteIndex.getIcon("goldfish").getY());
}
/**
* Append a base url with #{iconName}. This parameter is used by the {@link SpriteGraphicFactory} to pull the correct icon from the
* spritesheet.
*/
private String constructSpriteUrl(String baseUrl, String iconName) {
return baseUrl + "#" + iconName;
}
/**
* Create a simple style with a {@link PointSymbolizer} using the provided graphic and size.
*
* @param eg The external graphic to use
* @param size The size in pixels for the point symbolizer's graphic. If null, defaults to the external graphic's default size.
*/
public Style pointStyleWithExternalGraphic(ExternalGraphic eg, String size) {
size = size == null ? "-1" : size;
Graphic gr = styleFactory.graphic(Arrays.asList(eg), filterFactory.literal(1),
filterFactory.literal(size), null, null, null);
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;
}
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/sprite/test-data/rendered/"
+ name + ".png");
}
}