/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved * (c) 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.wms.featureinfo; import static org.junit.Assert.assertEquals; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import javax.xml.namespace.QName; import net.sf.json.JSONObject; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.mutable.MutableDouble; import org.geoserver.config.GeoServer; import org.geoserver.data.test.MockData; import org.geoserver.data.test.SystemTestData; import org.geoserver.platform.ServiceException; import org.geoserver.wms.FeatureInfoRequestParameters; import org.geoserver.wms.GetFeatureInfoRequest; import org.geoserver.wms.GetMapOutputFormat; import org.geoserver.wms.GetMapRequest; import org.geoserver.wms.GetMapTest; import org.geoserver.wms.MapLayerInfo; import org.geoserver.wms.MapProducerCapabilities; import org.geoserver.wms.WMS; import org.geoserver.wms.WMSInfo; import org.geoserver.wms.WMSMapContent; import org.geoserver.wms.WMSTestSupport; import org.geoserver.wms.WebMap; import org.geoserver.wms.map.AbstractMapOutputFormat; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.geotools.renderer.lite.RendererUtilities; import org.junit.After; import org.junit.Test; import org.opengis.referencing.FactoryException; import org.opengis.referencing.operation.TransformException; import com.vividsolutions.jts.geom.Envelope; public class RenderingBasedFeatureInfoTest extends WMSTestSupport { public static QName GRID = new QName(MockData.CITE_URI, "grid", MockData.CITE_PREFIX); public static QName REPEATED = new QName(MockData.CITE_URI, "repeated", MockData.CITE_PREFIX); public static QName GIANT_POLYGON = new QName(MockData.CITE_URI, "giantPolygon", MockData.CITE_PREFIX); // @Override // protected String getLogConfiguration() { // return "/DEFAULT_LOGGING.properties"; // } @Override protected void onSetUp(SystemTestData testData) throws Exception { super.onSetUp(testData); testData.addStyle("box-offset", "box-offset.sld",this.getClass(), getCatalog()); testData.addStyle("transparent-fill", "transparent-fill.sld",this.getClass(), getCatalog()); File styles = new File(testData.getDataDirectoryRoot(), "styles"); File symbol = new File("./src/test/resources/org/geoserver/wms/featureinfo/box-offset.png"); FileUtils.copyFileToDirectory(symbol, styles); testData.addVectorLayer(GRID, Collections.EMPTY_MAP, "grid.properties", RenderingBasedFeatureInfoTest.class, getCatalog()); testData.addVectorLayer(REPEATED, Collections.EMPTY_MAP, "repeated_lines.properties", RenderingBasedFeatureInfoTest.class, getCatalog()); testData.addVectorLayer(GIANT_POLYGON, Collections.EMPTY_MAP, "giantPolygon.properties", GetMapTest.class, getCatalog()); testData.addStyle("ranged", "ranged.sld",this.getClass(), getCatalog()); testData.addStyle("dynamic", "dynamic.sld",this.getClass(), getCatalog()); testData.addStyle("symbol-uom", "symbol-uom.sld", this.getClass(), getCatalog()); testData.addStyle("two-rules", "two-rules.sld", this.getClass(), getCatalog()); testData.addStyle("two-fts", "two-fts.sld", this.getClass(), getCatalog()); testData.addStyle("dashed", "dashed.sld",this.getClass(), getCatalog()); testData.addStyle("dashed-exp", "dashed-exp.sld",this.getClass(), getCatalog()); testData.addStyle("polydash", "polydash.sld", this.getClass(), getCatalog()); testData.addStyle("doublepoly", "doublepoly.sld", this.getClass(), getCatalog()); testData.addStyle("pureLabel", "purelabel.sld", this.getClass(), getCatalog()); testData.addStyle("transform", "transform.sld", this.getClass(), getCatalog()); } @After public void cleanup() { VectorRenderingLayerIdentifier.RENDERING_FEATUREINFO_ENABLED = true; } /** * Test hitArea does not overflow out of painted area. */ @Test public void testHitAreaSize() throws Exception { int mapWidth = 100; int mapHeight = 100; Envelope mapbbox = new Envelope(0.0001955, 0.0002035, 0.000696, 0.000704); VectorRenderingLayerIdentifier vrli = new VectorRenderingLayerIdentifier(getWMS(), null) { @Override protected int getBuffer(int userBuffer) { return 3; } }; GetFeatureInfoRequest request = new GetFeatureInfoRequest(); GetMapRequest getMapRequest = new GetMapRequest(); List<MapLayerInfo> layers = new ArrayList<MapLayerInfo>(); layers.add(new MapLayerInfo(getCatalog().getLayerByName( MockData.BRIDGES.getLocalPart()))); getMapRequest.setLayers(layers); getMapRequest.setSRS("EPSG:4326"); getMapRequest.setBbox(mapbbox); getMapRequest.setWidth(mapWidth); getMapRequest.setFormat("image/png"); getMapRequest.setHeight(mapHeight); request.setGetMapRequest(getMapRequest); request.setQueryLayers(layers); request.setXPixel(50); request.setYPixel(50); FeatureInfoRequestParameters params = new FeatureInfoRequestParameters(request); assertEquals(0, vrli.identify(params, 10).size()); } @Test public void testBoxOffset() throws Exception { // try the old way clicking in the area of the symbol that is transparent VectorRenderingLayerIdentifier.RENDERING_FEATUREINFO_ENABLED = false; String url = "wms?REQUEST=GetFeatureInfo&BBOX=1.9E-4,6.9E-4,2.1E-4,7.1E-4&SERVICE=WMS&INFO_FORMAT=application/json" + "&QUERY_LAYERS=cite%3ABridges&Layers=cite%3ABridges&WIDTH=100&HEIGHT=100" + "&format=image%2Fpng&styles=box-offset&srs=EPSG%3A4326&version=1.1.1&x=50&y=63&feature_count=50"; JSONObject result1 = (JSONObject) getAsJSON(url); // print(result1); assertEquals(1, result1.getJSONArray("features").size()); // the new aware is aware that we're clicking into "nothing" VectorRenderingLayerIdentifier.RENDERING_FEATUREINFO_ENABLED = true; JSONObject result2 = (JSONObject) getAsJSON(url); // print(result2); assertEquals(0, result2.getJSONArray("features").size()); } @Test public void testRangedSize() throws Exception { // use a style that has a rule with a large symbolizer, but the point is // actually painted with a much smaller one String url = "wms?REQUEST=GetFeatureInfo&BBOX=0.000196%2C0.000696%2C0.000204%2C0.000704" + "&SERVICE=WMS&INFO_FORMAT=application/json&QUERY_LAYERS=cite%3ABridges&FEATURE_COUNT=50&Layers=cite%3ABridges" + "&WIDTH=100&HEIGHT=100&format=image%2Fpng&styles=ranged&srs=EPSG%3A4326&version=1.1.1&x=49&y=65&feature_count=50"; VectorRenderingLayerIdentifier.RENDERING_FEATUREINFO_ENABLED = false; JSONObject result1 = (JSONObject) getAsJSON(url); // print(result1); assertEquals(1, result1.getJSONArray("features").size()); // the new aware is aware that we're clicking into "nothing" VectorRenderingLayerIdentifier.RENDERING_FEATUREINFO_ENABLED = true; JSONObject result2 = (JSONObject) getAsJSON(url); // print(result2); assertEquals(0, result2.getJSONArray("features").size()); } @Test public void testDynamicSize() throws Exception { // use a style that has a rule with a attribute dependent size, the old code // will fallback on the default size since the actual one is not known String url = "wms?REQUEST=GetFeatureInfo" + "&BBOX=0.000196%2C0.000696%2C0.000204%2C0.000704&SERVICE=WMS" + "&INFO_FORMAT=application/json&QUERY_LAYERS=cite%3ABridges&FEATURE_COUNT=50" + "&Layers=cite%3ABridges&WIDTH=100&HEIGHT=100&format=image%2Fpng" + "&styles=dynamic&srs=EPSG%3A4326&version=1.1.1&x=49&y=60&feature_count=50"; // the default buffer is not large enough to realize we clicked on the mark VectorRenderingLayerIdentifier.RENDERING_FEATUREINFO_ENABLED = false; JSONObject result1 = (JSONObject) getAsJSON(url); // print(result1); assertEquals(0, result1.getJSONArray("features").size()); // the new is aware that we're clicking onto the feature instead VectorRenderingLayerIdentifier.RENDERING_FEATUREINFO_ENABLED = true; JSONObject result2 = (JSONObject) getAsJSON(url); // print(result2); assertEquals(1, result2.getJSONArray("features").size()); } @Test public void testUom() throws Exception { // this results in a very large symbol (the map 8m wide and 100 pixels), but if you // don't handle uom, you don't get to know that String url = "wms?REQUEST=GetFeatureInfo" + "&BBOX=0.000196%2C0.000696%2C0.000204%2C0.000704&SERVICE=WMS" + "&INFO_FORMAT=application/json&QUERY_LAYERS=cite%3ABridges&FEATURE_COUNT=50" + "&Layers=cite%3ABridges&WIDTH=100&HEIGHT=100&format=image%2Fpng" + "&styles=symbol-uom&srs=EPSG%3A4326&version=1.1.1&x=49&y=60&feature_count=50"; VectorRenderingLayerIdentifier.RENDERING_FEATUREINFO_ENABLED = true; JSONObject result = (JSONObject) getAsJSON(url); // print(result2); assertEquals(1, result.getJSONArray("features").size()); } @Test public void testTwoRules() throws Exception { String layer = getLayerId(MockData.FORESTS); String request = "wms?version=1.1.1&bbox=-0.002,-0.002,0.002,0.002&format=jpeg" + "&request=GetFeatureInfo&layers=" + layer + "&query_layers=" + layer + "&styles=two-rules" + "&width=20&height=20&x=10&y=10" + "&info_format=application/json&feature_count=50"; JSONObject result = (JSONObject) getAsJSON(request); // we used to get two results when two rules matched the same feature // print(result); assertEquals(1, result.getJSONArray("features").size()); } @Test public void testTwoFeatureTypeStyles() throws Exception { String layer = getLayerId(MockData.FORESTS); String request = "wms?version=1.1.1&bbox=-0.002,-0.002,0.002,0.002&format=jpeg" + "&request=GetFeatureInfo&layers=" + layer + "&query_layers=" + layer + "&styles=two-fts" + "&width=20&height=20&x=10&y=10&info_format=application/json"; JSONObject result = (JSONObject) getAsJSON(request); // we used to get two results when two rules matched the same feature // print(result); assertEquals(1, result.getJSONArray("features").size()); } @Test public void testFillStrokeDashArray() throws Exception { String layer = getLayerId(MockData.FORESTS); String request = "wms?version=1.1.1&bbox=-0.002,-0.002,0.002,0.002&format=jpeg" + "&request=GetFeatureInfo&layers=" + layer + "&query_layers=" + layer + "&styles=polydash" + "&width=20&height=20&x=10&y=10&info_format=application/json"; // System.out.println("The response iTESTs: " + getAsString(request)); JSONObject result = (JSONObject) getAsJSON(request); // we used to get two results when two rules matched the same feature // print(result); assertEquals(1, result.getJSONArray("features").size()); } @Test public void testTransparentFill() throws Exception { String layer = getLayerId(MockData.FORESTS); String request = "wms?version=1.1.1&bbox=-0.002,-0.002,0.002,0.002&format=jpeg" + "&request=GetFeatureInfo&layers=" + layer + "&query_layers=" + layer + "&styles=transparent-fill" + "&width=20&height=20&x=10&y=10&info_format=application/json"; // System.out.println("The response iTESTs: " + getAsString(request)); JSONObject result = (JSONObject) getAsJSON(request); // we used to get two results when two rules matched the same feature // print(result); assertEquals(1, result.getJSONArray("features").size()); } @Test public void testGenericGeometry() throws Exception { String layer = getLayerId(MockData.GENERICENTITY); String request = "wms?REQUEST=GetFeatureInfo&BBOX=-2.73291%2C55.220703%2C8.510254%2C69.720703&SERVICE=WMS" + "&INFO_FORMAT=application/json&QUERY_LAYERS=" + layer + "&Layers=" + layer + "&WIDTH=397&HEIGHT=512&format=image%2Fpng&styles=line&srs=EPSG%3A4326&version=1.1.1&x=284&y=269"; JSONObject result = (JSONObject) getAsJSON(request); // we used to get no results assertEquals(1, result.getJSONArray("features").size()); } @Test public void testDashed() throws Exception { String layer = getLayerId(MockData.GENERICENTITY); String request = "wms?REQUEST=GetFeatureInfo&&BBOX=0.778809%2C45.421875%2C12.021973%2C59.921875&SERVICE=WMS" + "&INFO_FORMAT=application/json&QUERY_LAYERS=" + layer + "&Layers=" + layer + "&WIDTH=397&HEIGHT=512&format=image%2Fpng&styles=dashed&srs=EPSG%3A4326&version=1.1.1&x=182&y=241"; JSONObject result = (JSONObject) getAsJSON(request); // we used to get no results assertEquals(1, result.getJSONArray("features").size()); } @Test public void testDashedWithExpressions() throws Exception { String layer = getLayerId(MockData.GENERICENTITY); String request = "wms?REQUEST=GetFeatureInfo&&BBOX=0.778809%2C45.421875%2C12.021973%2C59.921875&SERVICE=WMS" + "&INFO_FORMAT=application/json&QUERY_LAYERS=" + layer + "&Layers=" + layer + "&WIDTH=397&HEIGHT=512&format=image%2Fpng&styles=dashed-exp&srs=EPSG%3A4326&version=1.1.1&x=182&y=241"; JSONObject result = (JSONObject) getAsJSON(request); assertEquals(1, result.getJSONArray("features").size()); } @Test public void testDoublePoly() throws Exception { String layer = getLayerId(GRID); String request = "wms?REQUEST=GetFeatureInfo&&BBOX=0,0,3,3&SERVICE=WMS" + "&INFO_FORMAT=application/json&FEATURE_COUNT=50&QUERY_LAYERS=" + layer + "&Layers=" + layer + "&WIDTH=90&HEIGHT=90&format=image%2Fpng&styles=doublepoly&srs=EPSG%3A4326&version=1.1.1&x=34&y=34"; JSONObject result = (JSONObject) getAsJSON(request); // we used to get two results assertEquals(1, result.getJSONArray("features").size()); } @Test public void testRepeatedLine() throws Exception { String layer = getLayerId(REPEATED); String request = "wms?REQUEST=GetFeatureInfo&&BBOX=499900,499900,500100,500100&SERVICE=WMS" + "&INFO_FORMAT=application/json&FEATURE_COUNT=50&QUERY_LAYERS=" + layer + "&Layers=" + layer + "&WIDTH=11&HEIGHT=11&format=image%2Fpng&styles=line&srs=EPSG%3A32615&version=1.1.1&x=5&y=5"; JSONObject result = (JSONObject) getAsJSON(request); //print(result); // we used to get two results assertEquals(2, result.getJSONArray("features").size()); } @Test public void testPureLabelGenericGeometry() throws Exception { String layer = getLayerId(MockData.GENERICENTITY); String request = "wms?REQUEST=GetFeatureInfo&&BBOX=0.778809%2C45.421875%2C12.021973%2C59.921875&SERVICE=WMS" + "&INFO_FORMAT=application/json&QUERY_LAYERS=" + layer + "&Layers=" + layer + "&WIDTH=397&HEIGHT=512&format=image%2Fpng&styles=pureLabel&srs=EPSG%3A4326&version=1.1.1&x=182&y=241"; JSONObject result = (JSONObject) getAsJSON(request); // we used to get no results assertEquals(1, result.getJSONArray("features").size()); } @Test public void testPureLabelPolygon() throws Exception { String layer = getLayerId(MockData.FORESTS); String request = "wms?version=1.1.1&bbox=-0.002,-0.002,0.002,0.002&format=jpeg" + "&request=GetFeatureInfo&layers=" + layer + "&query_layers=" + layer + "&styles=pureLabel" + "&width=20&height=20&x=10&y=10&info_format=application/json"; JSONObject result = (JSONObject) getAsJSON(request); // we used to get two results when two rules matched the same feature // print(result); assertEquals(1, result.getJSONArray("features").size()); } @Test public void testMapWrapping() throws Exception { GeoServer gs = getGeoServer(); WMSInfo wms = gs.getService(WMSInfo.class); Boolean original = wms.getMetadata().get(WMS.MAP_WRAPPING_KEY, Boolean.class); try { // wms.getMetadata().put(WMS.ADVANCED_PROJECTION_KEY, Boolean.TRUE); wms.getMetadata().put(WMS.MAP_WRAPPING_KEY, Boolean.TRUE); gs.save(wms); String layer = getLayerId(GIANT_POLYGON); String request = "wms?version=1.1.1&bbox=170,-10,190,10&format=image/png" + "&request=GetFeatureInfo&layers=" + layer + "&query_layers=" + layer + "&styles=polygon" + "&width=100&height=100&x=60&y=0&srs=EPSG:4326&info_format=application/json"; JSONObject result = (JSONObject) getAsJSON(request); // with wrapping enabled we should get the giant polygon on the other side too assertEquals(1, result.getJSONArray("features").size()); String disableRequest = request + "&format_options=mapWrapping:false"; result = (JSONObject) getAsJSON(disableRequest); // with wrapping disabled we should not get any hits assertEquals(0, result.getJSONArray("features").size()); String enableRequest = request + "&format_options=mapWrapping:true"; result = (JSONObject) getAsJSON(enableRequest); // with wrapping enabled we should get the giant polygon on the other side too assertEquals(1, result.getJSONArray("features").size()); wms.getMetadata().put(WMS.MAP_WRAPPING_KEY, Boolean.FALSE); gs.save(wms); result = (JSONObject) getAsJSON(request); // with wrapping disabled we should not get any hit assertEquals(0, result.getJSONArray("features").size()); result = (JSONObject) getAsJSON(disableRequest); // with wrapping disabled in the config, the request param should be ignored assertEquals(0, result.getJSONArray("features").size()); result = (JSONObject) getAsJSON(enableRequest); // with wrapping disabled in the config, the request param should be ignored assertEquals(0, result.getJSONArray("features").size()); } finally { wms.getMetadata().put(WMS.MAP_WRAPPING_KEY, original); gs.save(wms); } } /** * Tests GEOS-7020: imprecise scale calculation in StreamingRenderer * with VectorRenderingLayerIdentifier, due to 1 pixel missing * in map size. * */ @Test public void testCalculatedScale() throws Exception { int mapWidth = 1000; int mapHeight = 500; Envelope mapbbox = new Envelope(-2, 2, -1, 1); ReferencedEnvelope mapEnvelope = new ReferencedEnvelope(mapbbox, DefaultGeographicCRS.WGS84); final HashMap<String, String> hints = new HashMap<String, String>(); double originalScale = RendererUtilities.calculateScale(mapEnvelope, mapWidth, mapHeight, hints); double originalOGCScale = RendererUtilities.calculateOGCScale(mapEnvelope, mapWidth, hints); final MutableDouble calculatedScale = new MutableDouble(0.0); final MutableDouble calculatedOGCScale = new MutableDouble(0.0); VectorRenderingLayerIdentifier vrli = new VectorRenderingLayerIdentifier(getWMS(), null) { @Override protected GetMapOutputFormat createMapOutputFormat(BufferedImage image, FeatureInfoRenderListener featureInfoListener) { return new AbstractMapOutputFormat("image/png", new String[] { "png" }) { @Override public WebMap produceMap(WMSMapContent mapContent) throws ServiceException, IOException { // let's capture mapContent for identify purpose, so // that we can store the scale(s), to be verified later try { ReferencedEnvelope referencedEnvelope = new ReferencedEnvelope(mapContent.getViewport().getBounds(), DefaultGeographicCRS.WGS84); calculatedScale.setValue(RendererUtilities.calculateScale( referencedEnvelope, mapContent.getMapWidth(), mapContent.getMapHeight(), hints)); calculatedOGCScale.setValue(RendererUtilities.calculateOGCScale( referencedEnvelope, mapContent.getMapWidth(), hints)); } catch (TransformException e) { throw new ServiceException(e); } catch (FactoryException e) { throw new ServiceException(e); } return null; } @Override public MapProducerCapabilities getCapabilities(String format) { return null; } }; } }; GetFeatureInfoRequest request = new GetFeatureInfoRequest(); GetMapRequest getMapRequest = new GetMapRequest(); List<MapLayerInfo> layers = new ArrayList<MapLayerInfo>(); layers.add(new MapLayerInfo(getCatalog().getLayerByName( MockData.BASIC_POLYGONS.getLocalPart()))); getMapRequest.setLayers(layers); getMapRequest.setSRS("EPSG:4326"); getMapRequest.setBbox(mapbbox); getMapRequest.setWidth(mapWidth); getMapRequest.setHeight(mapHeight); request.setGetMapRequest(getMapRequest); request.setQueryLayers(layers); FeatureInfoRequestParameters params = new FeatureInfoRequestParameters(request); vrli.identify(params, 10); // 1% of error tolerance assertEquals(originalScale, calculatedScale.doubleValue(), originalScale * 0.01); assertEquals(originalOGCScale, calculatedOGCScale.doubleValue(), originalScale * 0.01); } @Test public void testRenderingTransform() throws Exception { String layer = getLayerId(MockData.FORESTS); String request = "wms?version=1.1.1&bbox=-0.002,-0.002,0.002,0.002&format=image/png" + "&request=GetFeatureInfo&layers=" + layer + "&query_layers=" + layer + "&styles=transform&transparent=true&srs=EPSG:4326" + "&width=20&height=20&x=10&y=10" + "&info_format=application/json&feature_count=50"; JSONObject result = (JSONObject) getAsJSON(request); assertEquals(1, result.getJSONArray("features").size()); } }