/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wms.animate;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.ImageInputStream;
import javax.xml.namespace.QName;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.LayerGroupInfo;
import org.geoserver.data.test.MockData;
import org.geoserver.wms.GetMapRequest;
import org.geoserver.wms.WMS;
import org.geoserver.wms.WMSTestSupport;
import org.geoserver.wms.WebMapService;
import org.geoserver.wms.map.RenderedImageMap;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.NodeList;
import org.springframework.mock.web.MockHttpServletResponse;
/**
* Some functional tests for animator
*
* @author Alessio Fabiani, GeoSolutions S.A.S., alessio.fabiani@geo-solutions.it
* @author Andrea Aime, GeoSolutions S.A.S., andrea.aime@geo-solutions.it
*/
public class AnimatorTest extends WMSTestSupport {
/** default 'format' value */
public static final String GIF_ANIMATED_FORMAT = "image/gif;subtype=animated";
/**
* Testing FrameCatalog constructor from a generic WMS request.
*
*/
@org.junit.Test
public void testFrameCatalog() throws Exception {
final WebMapService wms = (WebMapService) applicationContext.getBean("wmsService2");
final String layerName = MockData.BASIC_POLYGONS.getPrefix() + ":" +
MockData.BASIC_POLYGONS.getLocalPart();
GetMapRequest getMapRequest = createGetMapRequest(new QName(layerName));
FrameCatalog catalog = null;
try {
catalog = new FrameCatalog(getMapRequest, wms, getWMS());
} catch (RuntimeException e) {
assertEquals("Missing \"animator\" mandatory params \"aparam\" and \"avalues\".", e.getLocalizedMessage());
}
getMapRequest.getRawKvp().put("aparam", "fake_param");
getMapRequest.getRawKvp().put("avalues", "val0,val\\,1,val2\\,\\,,val3");
catalog = new FrameCatalog(getMapRequest, wms, getWMS());
assertNotNull(catalog);
assertEquals("fake_param", catalog.getParameter());
assertEquals(4, catalog.getValues().length);
assertEquals("val0", catalog.getValues()[0]);
assertEquals("val\\,1", catalog.getValues()[1]);
assertEquals("val2\\,\\,", catalog.getValues()[2]);
assertEquals("val3", catalog.getValues()[3]);
}
/**
* Testing FrameVisitor animation frames setup and production.
*
*/
@org.junit.Test
public void testFrameVisitor() throws Exception {
final WebMapService wms = (WebMapService) applicationContext.getBean("wmsService2");
final String layerName = MockData.BASIC_POLYGONS.getPrefix() + ":" +
MockData.BASIC_POLYGONS.getLocalPart();
GetMapRequest getMapRequest = createGetMapRequest(new QName(layerName));
FrameCatalog catalog = null;
getMapRequest.getRawKvp().put("aparam", "fake_param");
getMapRequest.getRawKvp().put("avalues", "val0,val\\,1,val2\\,\\,,val3");
getMapRequest.getRawKvp().put("format", GIF_ANIMATED_FORMAT);
getMapRequest.getRawKvp().put("LAYERS", layerName);
catalog = new FrameCatalog(getMapRequest, wms, getWMS());
assertNotNull(catalog);
FrameCatalogVisitor visitor = new FrameCatalogVisitor();
catalog.getFrames(visitor);
assertEquals(4, visitor.framesNumber);
List<RenderedImageMap> frames = visitor.produce(getWMS());
assertNotNull(frames);
assertEquals(4, frames.size());
}
/**
* Produce animated gif through the WMS request.
*/
@org.junit.Test
public void testAnimator() throws Exception {
final String layerName = MockData.BASIC_POLYGONS.getPrefix() + ":" +
MockData.BASIC_POLYGONS.getLocalPart();
String requestURL = "wms/animate?layers=" + layerName
+ "&aparam=fake_param&avalues=val0,val\\,1,val2\\,\\,,val3";
checkAnimatedGif(requestURL, false, WMS.DISPOSAL_METHOD_DEFAULT);
checkAnimatedGif(requestURL + "&format_options=gif_loop_continuously:true", true, WMS.DISPOSAL_METHOD_DEFAULT);
checkAnimatedGif(requestURL + "&format_options=gif_loop_continuosly:true", true, WMS.DISPOSAL_METHOD_DEFAULT);
// check all valid disposal methods
for (String disposal : WMS.DISPOSAL_METHODS) {
checkAnimatedGif(requestURL + "&format_options=gif_disposal:" + disposal, false, disposal);
}
}
private void checkAnimatedGif(String requestURL, boolean loopContinously, String disposal)
throws Exception, IOException {
MockHttpServletResponse resp = getAsServletResponse(requestURL);
assertEquals("image/gif", resp.getContentType());
try (ImageInputStream is = ImageIO.createImageInputStream(getBinaryInputStream(resp))) {
ImageReader reader = ImageIO.getImageReadersByFormatName("gif").next();
reader.setInput(is);
// check we have the right number of images
assertEquals(4, reader.getNumImages(true));
IIOMetadata imageMetadata = reader.getImageMetadata(0);
LOGGER.info(Arrays.toString(imageMetadata.getMetadataFormatNames()));
IIOMetadataNode node = (IIOMetadataNode) imageMetadata
.getAsTree("javax_imageio_gif_image_1.0");
// print("", node);
// check the applied disposal method
IIOMetadataNode nodeGCE = (IIOMetadataNode)
node.getElementsByTagName("GraphicControlExtension").item(0);
// assign the proper specified value for the disposal method option
if(disposal == "backgroundColor") {
disposal = "restoreToBackgroundColor";
} else if(disposal == "previous") {
disposal = "restoreToPrevious";
}
assertEquals(disposal, nodeGCE.getAttribute("disposalMethod").toString());
NodeList nodes = node.getElementsByTagName("ApplicationExtensions");
node = (IIOMetadataNode) nodes.item(0);
nodes = node.getElementsByTagName("ApplicationExtension");
boolean found = false;
for (int i = 0; i < nodes.getLength(); i++) {
node = (IIOMetadataNode) nodes.item(i);
if ("NETSCAPE".equals(node.getAttribute("applicationID"))
&& "2.0".equals(node.getAttribute("authenticationCode"))) {
found = true;
byte[] flags = (byte[]) node.getUserObject();
if (loopContinously) {
assertArrayEquals(new byte[] { 0x1, 0x0, 0x0 }, flags);
} else {
assertArrayEquals(new byte[] { 0x1, 0x1, 0x0 }, flags);
}
}
}
if(!found) {
fail("Could not find custom metadata node containing the loop control extension");
}
}
}
/**
* Utility method to print a metadata node
*
* @param prefix
* @param node
*/
private void print(String prefix, IIOMetadataNode node) {
Object user = node.getUserObject();
System.out.println(prefix + node.getNodeName() + ": " + node.getNodeValue() + ", "
+ (user instanceof byte[] ? Arrays.toString((byte[]) user) : user));
NamedNodeMap attributes = node.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
print(prefix + "Attribute ", (IIOMetadataNode) attributes.item(i));
}
NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
print(" " + prefix, (IIOMetadataNode) children.item(i));
}
}
/**
* Animate layers
*/
@org.junit.Test
public void testAnimatorLayers() throws Exception {
final String layerName = MockData.BASIC_POLYGONS.getPrefix() + ":" +
MockData.BASIC_POLYGONS.getLocalPart();
String requestURL = "cite/wms/animate?&aparam=layers&avalues=MapNeatline,Buildings,Lakes";
// check we got a gif
MockHttpServletResponse resp = getAsServletResponse(requestURL);
assertEquals("image/gif", resp.getContentType());
// check it has three frames
ByteArrayInputStream bis = getBinaryInputStream(resp);
ImageInputStream iis = ImageIO.createImageInputStream(bis);
ImageReader reader = ImageIO.getImageReadersBySuffix("gif").next();
reader.setInput(iis);
assertEquals(3, reader.getNumImages(true));
}
/**
* Animate layer groups
*/
@org.junit.Test
public void testAnimatorLayerGroups() throws Exception {
Catalog catalog = getCatalog();
LayerGroupInfo singleGroup = createLakesPlacesLayerGroup(catalog, "singleGroup", LayerGroupInfo.Mode.SINGLE, null);
try {
LayerGroupInfo namedGroup = createLakesPlacesLayerGroup(catalog, "namedGroup", LayerGroupInfo.Mode.NAMED, null);
try {
LayerGroupInfo eoGroup = createLakesPlacesLayerGroup(catalog, "eoGroup", LayerGroupInfo.Mode.EO, catalog.getLayerByName(getLayerId(MockData.LAKES)));
try {
String requestURL = "wms/animate?BBOX=0.0000,-0.0020,0.0035,0.0010&width=512&aparam=layers&avalues="
+
singleGroup.getName() + "," +
namedGroup.getName() + "," +
eoGroup.getName();
// check we got a gif
MockHttpServletResponse resp = getAsServletResponse(requestURL);
assertEquals("image/gif", resp.getContentType());
// check it has three frames
ByteArrayInputStream bis = getBinaryInputStream(resp);
ImageInputStream iis = ImageIO.createImageInputStream(bis);
ImageReader reader = ImageIO.getImageReadersBySuffix("gif").next();
reader.setInput(iis);
// BufferedImage gif = getAsImage(requestURL, "image/gif");
// ImageIO.write(gif, "gif", new File("anim.gif"));
assertEquals(3, reader.getNumImages(true));
// single group:
BufferedImage image = reader.read(0);
assertPixel(image, 300, 270, Color.WHITE);
// places
assertPixel(image, 380, 30, COLOR_PLACES_GRAY);
// lakes
assertPixel(image, 180, 350, COLOR_LAKES_BLUE);
// named group:
image = reader.read(1);
assertPixel(image, 300, 270, Color.WHITE);
// places
assertPixel(image, 380, 30, COLOR_PLACES_GRAY);
// lakes
assertPixel(image, 180, 350, COLOR_LAKES_BLUE);
// EO group:
image = reader.read(2);
assertPixel(image, 300, 270, Color.WHITE);
// no places
assertPixel(image, 380, 30, Color.WHITE);
// lakes
assertPixel(image, 180, 350, COLOR_LAKES_BLUE);
} finally {
catalog.remove(eoGroup);
}
} finally {
catalog.remove(namedGroup);
}
} finally {
catalog.remove(singleGroup);
}
}
}