/* (c) 2014 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.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; import java.io.IOException; import org.junit.Test; import org.geoserver.catalog.FeatureTypeInfo; import org.geotools.data.FeatureSource; import org.geotools.data.complex.AppSchemaDataAccess; import org.geotools.data.complex.FeatureTypeMapping; import org.geotools.data.complex.config.AppSchemaDataAccessConfigurator; import org.geotools.data.complex.filter.ComplexFilterSplitter; import org.geotools.data.jdbc.FilterToSQL; import org.geotools.filter.FilterFactoryImplNamespaceAware; import org.geotools.geometry.jts.JTS; import org.geotools.jdbc.BasicSQLDialect; import org.geotools.jdbc.JDBCDataStore; import org.geotools.jdbc.NestedFilterToSQL; import org.geotools.jdbc.PreparedStatementSQLDialect; import org.geotools.jdbc.SQLDialect; import org.geotools.referencing.CRS; import org.geotools.util.NullProgressListener; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.filter.Filter; import org.opengis.filter.PropertyIsEqualTo; import org.opengis.filter.spatial.BBOX; import org.opengis.geometry.MismatchedDimensionException; import org.opengis.referencing.FactoryException; import org.opengis.referencing.NoSuchAuthorityCodeException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; import org.w3c.dom.Document; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; /** * This is to test encoding of SRS information and reprojection values in app-schema features. This * is separated from SRSWfsTest as both test uses the same top level mapping and this test doesn't * make changes to the Axis Order. * * @author Victor Tey, CSIRO Exploration and Mining */ public class SRSReprojectionTest extends AbstractAppSchemaTestSupport { final String EPSG_4326 = "urn:x-ogc:def:crs:EPSG:4326"; final String EPSG_4283 = "EPSG:4283"; @Override protected SRSReprojectionMockData createTestData() { return new SRSReprojectionMockData(); } /** * Tests re-projection of NonFeatureTypeProxy. * */ @Test public void testNonFeatureTypeProxy() { Document doc = null; doc = getAsDOM("wfs?request=GetFeature&version=1.1.0&typename=gsml:MappedFeature&srsName=EPSG:4326"); LOGGER.info("WFS GetFeature&typename=gsml:MappedFeature response:\n" + prettyString(doc)); assertXpathEvaluatesTo( "value01", "//gsml:MappedFeature[@gml:id='gsml.mappedfeature.mf1']/gsml:observationMethod[1]/gsml:CGI_TermValue/gsml:value[@codeSpace='codespace01']", doc); assertXpathEvaluatesTo( "value02", "//gsml:MappedFeature[@gml:id='gsml.mappedfeature.mf1']/gsml:observationMethod[2]/gsml:CGI_TermValue/gsml:value[@codeSpace='codespace02']", doc); assertXpathEvaluatesTo( "value03", "//gsml:MappedFeature[@gml:id='gsml.mappedfeature.mf2']/gsml:observationMethod[1]/gsml:CGI_TermValue/gsml:value[@codeSpace='codespace03']", doc); assertXpathCount( 0, "//gsml:MappedFeature[@gml:id='gsml.mappedfeature.mf3']/gsml:observationMethod[1]/gsml:CGI_TermValue/gsml:value", doc); assertXpathCount( 0, "//gsml:MappedFeature[@gml:id='gsml.mappedfeature.mf4']/gsml:observationMethod[2]/gsml:CGI_TermValue/gsml:value", doc); assertXpathEvaluatesTo( "http://www.opengis.net/gml/srs/epsg.xml#4326", "//gsml:MappedFeature[@gml:id='gsml.mappedfeature.mf1']/gsml:shape/gml:Point/@srsName", doc); assertXpathEvaluatesTo( "2", "//gsml:MappedFeature[@gml:id='gsml.mappedfeature.mf1']/gsml:shape/gml:Point/@srsDimension", doc); // test that result returns in long,lat order. assertXpathEvaluatesTo( "133.8855 -23.6701", "//gsml:MappedFeature[@gml:id='gsml.mappedfeature.mf1']/gsml:shape/gml:Point/gml:pos", doc); doc = getAsDOM("wfs?request=GetFeature&version=1.1.0&typename=gsml:MappedFeature&srsName=urn:x-ogc:def:crs:EPSG:4326"); LOGGER.info("WFS GetFeature&typename=gsml:MappedFeature response:\n" + prettyString(doc)); // test result returns in lat,long order assertXpathEvaluatesTo( "-23.6701 133.8855", "//gsml:MappedFeature[@gml:id='gsml.mappedfeature.mf1']/gsml:shape/gml:Point/gml:pos", doc); } /** * Tests re-projection in a normal feature chaining mapping where there is no geometry on the * parent feature and only on the nested feature level * */ @Test public void testChainingReprojection() throws NoSuchAuthorityCodeException, FactoryException, MismatchedDimensionException, TransformException { Document doc = getAsDOM("wfs?request=GetFeature&version=1.1.0&typename=ex:geomContainer"); LOGGER.info("WFS GetFeature&typename=ex:geomContainer response:\n" + prettyString(doc)); // Generate test geometries and its results after re-projection. CoordinateReferenceSystem sourceCRS = (CoordinateReferenceSystem) CRS.decode(EPSG_4283); CoordinateReferenceSystem targetCRS = (CoordinateReferenceSystem) CRS.decode(EPSG_4326); MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS); GeometryFactory factory = new GeometryFactory(); Polygon srcPolygon = factory.createPolygon(factory.createLinearRing(factory .getCoordinateSequenceFactory().create( new Coordinate[] { new Coordinate(-1.2, 52.5), new Coordinate(-1.2, 52.6), new Coordinate(-1.1, 52.6), new Coordinate(-1.1, 52.5), new Coordinate(-1.2, 52.5) })), null); Polygon targetPolygon = (Polygon) JTS.transform(srcPolygon, transform); StringBuffer polygonBuffer = new StringBuffer(); for (Coordinate coord : targetPolygon.getCoordinates()) { polygonBuffer.append(coord.x).append(" "); polygonBuffer.append(coord.y).append(" "); } String targetPolygonCoords = polygonBuffer.toString().trim(); Point targetPoint = (Point) JTS.transform( factory.createPoint(new Coordinate(42.58, 31.29)), transform); String targetPointCoord = targetPoint.getCoordinate().x + " " + targetPoint.getCoordinate().y; assertXpathEvaluatesTo( "52.5 -1.2 52.6 -1.2 52.6 -1.1 52.5 -1.1 52.5 -1.2", "//ex:geomContainer[@gml:id='1']/ex:nestedFeature[2]/ex:nestedGeom[@gml:id='secondNested.2']/ex:nestedFeature/ex:nestedGeom[@gml:id='thirdNested.1']/ex:geom/gml:Polygon/gml:exterior/gml:LinearRing/gml:posList", doc); assertXpathEvaluatesTo( "urn:x-ogc:def:crs:EPSG:4283", "//ex:geomContainer[@gml:id='2']/ex:nestedFeature[2]/ex:nestedGeom[@gml:id='secondNested.1']/ex:nestedFeature/ex:nestedGeom[@gml:id='thirdNested.2']/ex:geom/gml:Point/@srsName", doc); assertXpathEvaluatesTo( "31.29 42.58", "//ex:geomContainer[@gml:id='2']/ex:nestedFeature[2]/ex:nestedGeom[@gml:id='secondNested.1']/ex:nestedFeature/ex:nestedGeom[@gml:id='thirdNested.2']/ex:geom/gml:Point/gml:pos", doc); assertXpathEvaluatesTo( "NAME", "//ex:geomContainer[@gml:id='2']/ex:nestedFeature[2]/ex:nestedGeom[@gml:id='secondNested.1']/ex:name", doc); // test that having empty geometry in a re-projection doesn't throw an exception. assertXpathCount( 0, "//ex:geomContainer[@gml:id='2']/ex:nestedFeature[2]/ex:nestedGeom[@gml:id='secondNested.1']/ex:geom", doc); doc = getAsDOM("wfs?request=GetFeature&version=1.1.0&typename=ex:geomContainer&srsName=urn:x-ogc:def:crs:EPSG:4326"); LOGGER.info("WFS GetFeature&typename=ex:geomContainer response:\n" + prettyString(doc)); // test that the polygon is correctly re-projected. assertXpathEvaluatesTo( targetPolygonCoords, "//ex:geomContainer[@gml:id='1']/ex:nestedFeature[2]/ex:nestedGeom[@gml:id='secondNested.2']/ex:nestedFeature/ex:nestedGeom[@gml:id='thirdNested.1']/ex:geom/gml:Polygon/gml:exterior/gml:LinearRing/gml:posList", doc); assertXpathEvaluatesTo( "urn:x-ogc:def:crs:EPSG:4326", "//ex:geomContainer[@gml:id='2']/ex:nestedFeature[2]/ex:nestedGeom[@gml:id='secondNested.1']/ex:nestedFeature/ex:nestedGeom[@gml:id='thirdNested.2']/ex:geom/gml:Point/@srsName", doc); // Test that the point coordinate are correctly projected. assertXpathEvaluatesTo( targetPointCoord, "//ex:geomContainer[@gml:id='2']/ex:nestedFeature[2]/ex:nestedGeom[@gml:id='secondNested.1']/ex:nestedFeature/ex:nestedGeom[@gml:id='thirdNested.2']/ex:geom/gml:Point/gml:pos", doc); } /** * Tests that Xlink href works fine in nested feature chaining with features that contains * geometry * */ @Test public void testChainingXlink() { Document doc = getAsDOM("wfs?request=GetFeature&version=1.1.0&typename=ex:geomContainer"); LOGGER.info("WFS GetFeature&typename=ex:geomContainer response:\n" + prettyString(doc)); assertXpathEvaluatesTo("http://example.com/UrnResolver/?uri=1", "//ex:geomContainer[@gml:id='1']/ex:nestedFeature[3]/@xlink:href", doc); assertXpathEvaluatesTo("http://example.com/UrnResolver/?uri=2", "//ex:geomContainer[@gml:id='2']/ex:nestedFeature[3]/@xlink:href", doc); } @Test public void testNestedSpatialFilterEncoding() throws IOException { FeatureTypeInfo ftInfo = getCatalog().getFeatureTypeByName("ex", "geomContainer"); FeatureSource fs = ftInfo.getFeatureSource(new NullProgressListener(), null); AppSchemaDataAccess da = (AppSchemaDataAccess) fs.getDataStore(); FeatureTypeMapping rootMapping = da.getMappingByNameOrElement(ftInfo.getQualifiedName()); // make sure nested filters encoding is enabled, otherwise skip test assumeTrue(shouldTestNestedFiltersEncoding(rootMapping)); JDBCDataStore store = (JDBCDataStore) rootMapping.getSource().getDataStore(); FilterFactoryImplNamespaceAware ff = new FilterFactoryImplNamespaceAware(); ff.setNamepaceContext(rootMapping.getNamespaces()); /* * test spatial filter on nested geometry attribute */ GeometryFactory factory = new GeometryFactory(); Polygon srcPolygon = factory.createPolygon(factory.createLinearRing(factory .getCoordinateSequenceFactory().create( new Coordinate[] { new Coordinate(-1.2, 52.5), new Coordinate(-1.2, 52.6), new Coordinate(-1.1, 52.6), new Coordinate(-1.1, 52.5), new Coordinate(-1.2, 52.5) })), null); Envelope bounds = srcPolygon.getEnvelopeInternal(); BBOX intersects = ff .bbox("ex:nestedFeature[2]/ex:nestedGeom/ex:nestedFeature/ex:nestedGeom/ex:geom", bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY(), "EPSG:4283"); // Filter involving nested geometry attribute --> CANNOT be encoded ComplexFilterSplitter splitter = new ComplexFilterSplitter(store.getFilterCapabilities(), rootMapping); splitter.visit(intersects, null); Filter preFilter = splitter.getFilterPre(); Filter postFilter = splitter.getFilterPost(); assertEquals(Filter.INCLUDE, preFilter); assertEquals(intersects, postFilter); // filter must be "unrolled" (i.e. reverse mapped) first Filter unrolled = AppSchemaDataAccess.unrollFilter(intersects, rootMapping); // Filter is nested assertTrue(NestedFilterToSQL.isNestedFilter(unrolled)); } }