From: <svn...@os...> - 2012-05-26 18:46:49
|
Author: aaime Date: 2012-05-26 11:46:42 -0700 (Sat, 26 May 2012) New Revision: 38759 Modified: trunk/modules/library/render/src/main/java/org/geotools/renderer/lite/StreamingRenderer.java trunk/modules/library/render/src/test/java/org/geotools/renderer/lite/SpatialFilterTest.java trunk/modules/library/render/src/test/resources/org/geotools/renderer/lite/test-data/point.properties trunk/modules/plugin/epsg-hsql/src/test/java/org/geotools/referencing/CRSTest.java Log: [GEOT-4155] Renderer does not handle spatial filters in projections others than the native data one Modified: trunk/modules/library/render/src/main/java/org/geotools/renderer/lite/StreamingRenderer.java =================================================================== --- trunk/modules/library/render/src/main/java/org/geotools/renderer/lite/StreamingRenderer.java 2012-05-26 18:46:14 UTC (rev 38758) +++ trunk/modules/library/render/src/main/java/org/geotools/renderer/lite/StreamingRenderer.java 2012-05-26 18:46:42 UTC (rev 38759) @@ -73,8 +73,11 @@ import org.geotools.filter.IllegalFilterException; import org.geotools.filter.function.GeometryTransformationVisitor; import org.geotools.filter.function.RenderingTransformation; +import org.geotools.filter.spatial.DefaultCRSFilterVisitor; +import org.geotools.filter.spatial.ReprojectingFilterVisitor; import org.geotools.filter.visitor.ExtractBoundsFilterVisitor; import org.geotools.filter.visitor.SimplifyingFilterVisitor; +import org.geotools.filter.visitor.SpatialFilterVisitor; import org.geotools.geometry.GeneralEnvelope; import org.geotools.geometry.jts.Decimator; import org.geotools.geometry.jts.GeometryClipper; @@ -109,8 +112,10 @@ import org.geotools.styling.PointSymbolizer; import org.geotools.styling.RasterSymbolizer; import org.geotools.styling.Rule; +import org.geotools.styling.RuleImpl; import org.geotools.styling.Style; import org.geotools.styling.StyleAttributeExtractor; +import org.geotools.styling.StyleFactory; import org.geotools.styling.Symbolizer; import org.geotools.styling.TextSymbolizer; import org.geotools.styling.visitor.DuplicatingStyleVisitor; @@ -363,6 +368,7 @@ private static boolean VECTOR_RENDERING_ENABLED_DEFAULT = false; public static final String LABEL_CACHE_KEY = "labelCache"; + public static final String FORCE_EPSG_AXIS_ORDER_KEY = "ForceEPSGAxisOrder"; public static final String DPI_KEY = "dpi"; public static final String DECLARED_SCALE_DENOM_KEY = "declaredScaleDenominator"; public static final String SCALE_COMPUTATION_METHOD_KEY = "scaleComputationMethod"; @@ -374,7 +380,10 @@ * and the displayed area of the map. * "dpi" - Integer number of dots per inch of the display 90 DPI is the default (as declared by OGC) * "forceCRS" - CoordinateReferenceSystem declares to the renderer that all layers are of the CRS declared in this hint - * "labelCache" - Declares the label cache that will be used by the renderer. + * "labelCache" - Declares the label cache that will be used by the renderer. + * "forceEPSGAxisOrder" - When doing spatial filter reprojection (from the SLD towards the native CRS) assume the geometries + * are expressed with the axis order suggested by the official EPSG database, regardless of how the + * CRS system might be configured */ private Map rendererHints = null; @@ -1358,7 +1367,23 @@ return false; return Boolean.TRUE.equals(result); } + + /** + * Checks if the geometries in spatial filters in the SLD must be assumed to be expressed + * in the official EPSG axis order, regardless of how the referencing subsystem is configured + * (this is required to support filter reprojection in WMS 1.3+) + * @return + */ + private boolean isEPSGAxisOrderForced() { + if (rendererHints == null) + return false; + Object result = rendererHints.get(FORCE_EPSG_AXIS_ORDER_KEY); + if (result == null) + return false; + return Boolean.TRUE.equals(result); + } + /** * Checks if vector rendering is enabled or not. * See {@link SLDStyleFactory#isVectorRenderingEnabled()} for a full explanation. @@ -1929,6 +1954,10 @@ if(lfts.isEmpty()) return; + // make sure all spatial filters in the feature source native SRS + reprojectSpatialFilters(lfts, featureSource); + + // apply the uom and dpi rescale applyUnitRescale(lfts); // classify by transformation @@ -2300,6 +2329,76 @@ } /** + * Reprojects the spatial filters in each {@link LiteFeatureTypeStyle} so that they match + * the feature source native coordinate system + * @param lfts + * @param fs + * @throws FactoryException + */ + void reprojectSpatialFilters(final ArrayList<LiteFeatureTypeStyle> lfts, FeatureSource fs) throws FactoryException { + // compute the default SRS of the feature source + FeatureType schema = fs.getSchema(); + CoordinateReferenceSystem declaredCRS = schema.getCoordinateReferenceSystem(); + if(isEPSGAxisOrderForced()) { + Integer code = CRS.lookupEpsgCode(declaredCRS, false); + if(code != null) { + declaredCRS = CRS.decode("urn:ogc:def:crs:EPSG::" + code); + } + } + + // reproject spatial filters in each fts + for (LiteFeatureTypeStyle fts : lfts) { + reprojectSpatialFilters(fts, declaredCRS, schema); + } + } + + /** + * Reprojects spatial filters so that they match the feature source native CRS, and assuming all literal + * geometries are specified in the specified declaredCRS + */ + void reprojectSpatialFilters(LiteFeatureTypeStyle fts, CoordinateReferenceSystem declaredCRS, FeatureType schema) { + for (int i = 0; i < fts.ruleList.length; i++) { + fts.ruleList[i] = reprojectSpatialFilters(fts.ruleList[i], declaredCRS, schema); + } + if(fts.elseRules != null) { + for (int i = 0; i < fts.elseRules.length; i++) { + fts.elseRules[i] = reprojectSpatialFilters(fts.elseRules[i], declaredCRS, schema); + } + } + } + + /** + * Reprojects spatial filters so that they match the feature source native CRS, and assuming all literal + * geometries are specified in the specified declaredCRS + */ + private Rule reprojectSpatialFilters(Rule rule, CoordinateReferenceSystem declaredCRS, FeatureType schema) { + // NPE avoidance + Filter filter = rule.getFilter(); + if(filter == null) { + return rule; + } + + // do we have any spatial filter? + SpatialFilterVisitor sfv = new SpatialFilterVisitor(); + filter.accept(sfv, null); + if(!sfv.hasSpatialFilter()) { + return rule; + } + + // all right, we need to default the literals to the declaredCRS and then reproject to + // the native one + DefaultCRSFilterVisitor defaulter = new DefaultCRSFilterVisitor(filterFactory, declaredCRS); + Filter defaulted = (Filter) filter.accept(defaulter, null); + ReprojectingFilterVisitor reprojector = new ReprojectingFilterVisitor(filterFactory, schema); + Filter reprojected = (Filter) defaulted.accept(reprojector, null); + + // clone the rule (the style can be reused over and over, we cannot alter it) and set the new filter + Rule rr = new RuleImpl(rule); + rr.setFilter(reprojected); + return rr; + } + + /** * Utility method to apply the two rescale visitors without duplicating code * @param fts * @param visitor Modified: trunk/modules/library/render/src/test/java/org/geotools/renderer/lite/SpatialFilterTest.java =================================================================== --- trunk/modules/library/render/src/test/java/org/geotools/renderer/lite/SpatialFilterTest.java 2012-05-26 18:46:14 UTC (rev 38758) +++ trunk/modules/library/render/src/test/java/org/geotools/renderer/lite/SpatialFilterTest.java 2012-05-26 18:46:42 UTC (rev 38759) @@ -3,26 +3,40 @@ import static org.junit.Assert.*; import java.io.File; +import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.geotools.data.property.PropertyDataStore; import org.geotools.data.simple.SimpleFeatureSource; import org.geotools.factory.CommonFactoryFinder; +import org.geotools.factory.Hints; +import org.geotools.geometry.jts.JTS; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.map.FeatureLayer; import org.geotools.map.MapContent; +import org.geotools.referencing.CRS; +import org.geotools.referencing.CRS.AxisOrder; +import org.geotools.referencing.ReferencingFactoryFinder; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.geotools.renderer.RenderListener; import org.geotools.styling.PolygonSymbolizer; import org.geotools.styling.Rule; +import org.geotools.styling.SLDTransformer; import org.geotools.styling.Style; import org.geotools.styling.StyleBuilder; +import org.geotools.styling.Symbolizer; import org.geotools.test.TestData; +import org.junit.After; import org.junit.Before; +import org.junit.Test; import org.opengis.feature.simple.SimpleFeature; import org.opengis.filter.FilterFactory2; +import org.opengis.referencing.crs.CRSAuthorityFactory; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import com.vividsolutions.jts.geom.Polygon; + public class SpatialFilterTest { private static final long TIME = 2000; @@ -43,12 +57,28 @@ RenderListener listener; + SimpleFeatureSource pointFS; + @Before public void setUp() throws Exception { + CRS.reset("all"); + Hints.putSystemDefault(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE); + + // the following is only to make the test work in Eclipse, where the test + // classpath is tainted by the test classpath of dependent modules (whilst in Maven it's not) + Set<CRSAuthorityFactory> factories = ReferencingFactoryFinder.getCRSAuthorityFactories(null); + for (CRSAuthorityFactory factory : factories) { + if(factory.getClass().getSimpleName().equals("EPSGCRSAuthorityFactory")) { + ReferencingFactoryFinder.removeAuthorityFactory(factory); + } + } + assertEquals(AxisOrder.NORTH_EAST, CRS.getAxisOrder(CRS.decode("urn:ogc:def:crs:EPSG::4326"))); + // setup data File property = new File(TestData.getResource(this, "square.properties").toURI()); PropertyDataStore ds = new PropertyDataStore(property.getParentFile()); squareFS = ds.getFeatureSource("square"); + pointFS = ds.getFeatureSource("point"); bounds = new ReferencedEnvelope(0, 10, 0, 10, DefaultGeographicCRS.WGS84); // prepare the renderer @@ -56,6 +86,7 @@ content = new MapContent(); content.getViewport().setCoordinateReferenceSystem(DefaultGeographicCRS.WGS84); + renderer.setMapContent(content); renderer.addRenderListener(new RenderListener() { public void featureRenderer(SimpleFeature feature) { @@ -66,19 +97,131 @@ errorCount++; } }); + + // System.setProperty("org.geotools.test.interactive", "true"); } + + @After + public void tearDown() { + Hints.putSystemDefault(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.FALSE); + } + @Test public void testSpatialNoReprojection() throws Exception { // a spatial filter in the same SRS as the geometry StyleBuilder sb = new StyleBuilder(); PolygonSymbolizer ps = sb.createPolygonSymbolizer(); Style style = sb.createStyle(ps); Rule rule = style.featureTypeStyles().get(0).rules().get(0); - rule.setFilter(ff.bbox("geom", 1, 1, 4, 4, "EPSG:4326)")); + rule.setFilter(ff.bbox("geom", 1, 1, 4, 4, "EPSG:4326")); content.addLayer(new FeatureLayer(squareFS, style)); - RendererBaseTest.showRender("OneSquare", renderer, TIME, bounds); + RendererBaseTest.showRender("Spatial with default CRS", renderer, TIME, bounds); assertEquals(2, renderedIds.size()); } + + @Test + public void testSpatialDefaulter() throws Exception { + // a spatial filter in the same SRS as the geometry + StyleBuilder sb = new StyleBuilder(); + PolygonSymbolizer ps = sb.createPolygonSymbolizer(); + Style style = sb.createStyle(ps); + Rule rule = style.featureTypeStyles().get(0).rules().get(0); + rule.setFilter(ff.bbox("geom", 1, 1, 4, 4, null)); + + content.addLayer(new FeatureLayer(squareFS, style)); + + RendererBaseTest.showRender("Spatial without CRS", renderer, TIME, bounds); + assertEquals(2, renderedIds.size()); + } + + @Test + public void testSpatialDefaulterForceEPSG() throws Exception { + // a spatial filter in the same SRS as the geometry... but with a different axis order + // interpretation, if we assume lat/lon we should pick point.4 + StyleBuilder sb = new StyleBuilder(); + Symbolizer ps = sb.createPointSymbolizer(); + Style style = sb.createStyle(ps); + Rule rule = style.featureTypeStyles().get(0).rules().get(0); + rule.setFilter(ff.bbox("geom", 5, 1, 7, 3, null)); + + // force EPSG axis order interpretation + renderer.setRendererHints(Collections.singletonMap(StreamingRenderer.FORCE_EPSG_AXIS_ORDER_KEY, true)); + + content.addLayer(new FeatureLayer(pointFS, style)); + + RendererBaseTest.showRender("Spatial in EPSG order", renderer, TIME, bounds); + assertEquals(1, renderedIds.size()); + assertEquals("point.4", renderedIds.iterator().next()); + } + + @Test + public void testReprojectedBBOX() throws Exception { + // a spatial filter in a different SRS + CoordinateReferenceSystem utm31n = CRS.decode("EPSG:32631"); + CoordinateReferenceSystem wgs84 = CRS.decode("EPSG:4326"); + ReferencedEnvelope envWgs84 = new ReferencedEnvelope(1, 3, 5, 7, wgs84); + ReferencedEnvelope envUTM31N = envWgs84.transform(utm31n, true); + + StyleBuilder sb = new StyleBuilder(); + Symbolizer ps = sb.createPointSymbolizer(); + Style style = sb.createStyle(ps); + Rule rule = style.featureTypeStyles().get(0).rules().get(0); + rule.setFilter(ff.bbox("geom", envUTM31N.getMinX(), envUTM31N.getMinY(), envUTM31N.getMaxX(), envUTM31N.getMaxY(), "EPSG:32631")); + + // force EPSG axis order interpretation + renderer.setRendererHints(Collections.singletonMap(StreamingRenderer.FORCE_EPSG_AXIS_ORDER_KEY, true)); + + content.addLayer(new FeatureLayer(pointFS, style)); + + RendererBaseTest.showRender("Spatial in EPSG order", renderer, TIME, bounds); + assertEquals(1, renderedIds.size()); + assertEquals("point.4", renderedIds.iterator().next()); + } + + @Test + public void testReprojectedPolygon() throws Exception { + // a spatial filter in a different SRS + CoordinateReferenceSystem utm31n = CRS.decode("EPSG:32631"); + CoordinateReferenceSystem wgs84 = CRS.decode("EPSG:4326"); + ReferencedEnvelope envWgs84 = new ReferencedEnvelope(1, 3, 5, 7, wgs84); + ReferencedEnvelope envUTM31N = envWgs84.transform(utm31n, true); + + StyleBuilder sb = new StyleBuilder(); + Symbolizer ps = sb.createPointSymbolizer(); + Style style = sb.createStyle(ps); + Rule rule = style.featureTypeStyles().get(0).rules().get(0); + Polygon polygon = JTS.toGeometry(envUTM31N); + polygon.setUserData(utm31n); + rule.setFilter(ff.intersects(ff.property("geom"), ff.literal(polygon))); + + // force EPSG axis order interpretation + renderer.setRendererHints(Collections.singletonMap(StreamingRenderer.FORCE_EPSG_AXIS_ORDER_KEY, true)); + + content.addLayer(new FeatureLayer(pointFS, style)); + + RendererBaseTest.showRender("Spatial in EPSG order", renderer, TIME, bounds); + assertEquals(1, renderedIds.size()); + assertEquals("point.4", renderedIds.iterator().next()); + } + + @Test + public void testReprojectedPolygonFromSLD() throws Exception { + // same as above, but with the style in SLD form + Style style = RendererBaseTest.loadStyle(this, "spatialFilter.sld"); + + // force EPSG axis order interpretation + renderer.setRendererHints(Collections.singletonMap(StreamingRenderer.FORCE_EPSG_AXIS_ORDER_KEY, true)); + + content.addLayer(new FeatureLayer(pointFS, style)); + + RendererBaseTest.showRender("Spatial in EPSG order", renderer, TIME, bounds); + assertEquals(1, renderedIds.size()); + assertEquals("point.4", renderedIds.iterator().next()); + } + + + + } Modified: trunk/modules/library/render/src/test/resources/org/geotools/renderer/lite/test-data/point.properties =================================================================== --- trunk/modules/library/render/src/test/resources/org/geotools/renderer/lite/test-data/point.properties 2012-05-26 18:46:14 UTC (rev 38758) +++ trunk/modules/library/render/src/test/resources/org/geotools/renderer/lite/test-data/point.properties 2012-05-26 18:46:42 UTC (rev 38759) @@ -1,4 +1,4 @@ -_=id:int,geom:Point:4326,code:String,ax:double,ay:double,rotation:double +_=id:int,geom:Point:srid=4326,code:String,ax:double,ay:double,rotation:double point.0=0|POINT(0 0)|U+0021|0|0|0 point.1=1|POINT(2 4)|U+0021|1|1|0 point.2=2|POINT(4 4)|U+0022|1|1|-90 Modified: trunk/modules/plugin/epsg-hsql/src/test/java/org/geotools/referencing/CRSTest.java =================================================================== --- trunk/modules/plugin/epsg-hsql/src/test/java/org/geotools/referencing/CRSTest.java 2012-05-26 18:46:14 UTC (rev 38758) +++ trunk/modules/plugin/epsg-hsql/src/test/java/org/geotools/referencing/CRSTest.java 2012-05-26 18:46:42 UTC (rev 38759) @@ -39,6 +39,7 @@ import org.geotools.factory.Hints; import org.geotools.factory.GeoTools; import org.geotools.metadata.iso.citation.Citations; +import org.geotools.referencing.CRS.AxisOrder; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.geotools.referencing.factory.OrderedAxisAuthorityFactory; @@ -417,6 +418,18 @@ } } + public void testSRSAxisOrder2() throws Exception { + try { + Hints.putSystemDefault(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE); + CoordinateReferenceSystem crsEN = CRS.decode("EPSG:4326"); + assertEquals(AxisOrder.EAST_NORTH, CRS.getAxisOrder(crsEN)); + CoordinateReferenceSystem crsNE = CRS.decode("urn:ogc:def:crs:EPSG::4326"); + assertEquals(AxisOrder.NORTH_EAST, CRS.getAxisOrder(crsNE)); + } finally { + Hints.removeSystemDefault(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER); + } + } + /** * Tests similarity transform on the example provided in the EPSG projection guide, page 99 * @throws Exception |