Filter ------ GeoTools makes use of the gt-opengis **Filter** and **Expression** interfaces in order to express constraints. This is most often used when making a Query to retrieve specific Features from a DataStore. Reference: * gt-opengis data :doc:`../opengis/model` for feature, featureType and filter * gt-opengis :doc:`../opengis/filter` model * gt-cql :doc:`../cql/index` * gt-xml :doc:`../xml/index` * http://docs.geoserver.org/stable/en/user/filter/function_reference.html You will find the use of Filter in an number of other locations: * it is used as part of a Style when we need to select what is drawn on the screen * Part of our FeatureType to express any special constraints on data values (such as the length of a String) Create Filter ^^^^^^^^^^^^^ Using CQL ''''''''' Most code examples in this wiki will assume you are using the "Common Query Language", this parser is provided by the **gt-cql** jar.:: Filter filter = CQL.toFilter("attName >= 5"); Using FilterFactory ''''''''''''''''''' You have a choice of which filter factory to use: * **FilterFactory** directly create a filter - according to the limitations of the standard standard. Use this if you are making a request of an external Web Feature Server and do not want to accidentally step out of bounds. * **FilterFactory2** directly create a filter. Has some additional methods for working with JTS Geometry. Here is an example:: FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2( GeoTools.getDefaultHints ); Filter filter = ff.contains( ff.property( "THE_GEOM"), ff.literal( geometry ) ); One thing you can do with with a FilterFactory (which you cannot do in CQL) is request features by their FeatureId:: FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2( GeoTools.getDefaultHints ); Set fids = new HashSet(); fids.add( ff.featureId("ROAD.1") ); fids.add( ff.featureId("ROAD.2") ); Filter filter = ff.id( fids ); From XML '''''''' Parsing a Filter 1.0 document:: Parser parser = new Parser( new org.geotools.filter.v1_0.OGCConfiguration() ); Filter filter = (Filter) parser.parse( inputstream ); Using Filter ^^^^^^^^^^^^ You can use a filter by hand to check an individual Feature:: if( filter.evaulate( feature ) ){ System.out.println( feature.getId() + " was selected" ); } Java Beans (and plenty of other objects) also work with filter:: if( filter.evaulate( bean ) ){ System.out.println( bean + " was selected" ); } If you look in the advanced guide you can find out about **PropertyAccessors** which is how Filter learns how to access new content. You can teach GeoTools how to work with your domain objects by implementing a custom Property Accessor. Using this facility GeoTools users have used filters with Java Beans, Maps and Collections and featureTypes. Writing XML ''''''''''' We have a traditional "Transformer" for quickly writing out an xml fragment for a filter:: FilterTransformer transform = new FilterTransformer(); transform.setIndentation(2); String xml = transform.transform( filter ); You can also **gt-xml** and its **OGCConfiguration** for encoding a filter:: //create the encoder with the filter 1.0 configuration Configuration = new org.geotools.filter.v1_0.OGCConfiguration(); Encoder encoder = new Encoder( configuration ); //encode encoder.encode( filter, org.geotools.filter.v1_0.OGC.FILTER, outputStream ); Handling Selection '''''''''''''''''' Often an application will want to remember the Feature that a user was working with for later. This section shows a couple of approaches to recording what Features a user has "selected". * Q: How to find Features using IDs Each Feature has a FeatureID; you can use these FeatureIDs to request the feature again later. If you have a Set of feature IDs, which you would like to query from a shapefile: .. literalinclude:: /../src/main/java/org/geotools/main/FilterExamples.java :language: java :start-after: // grabSelectedIds start :end-before: // grabSelectedIds end Keeping a Set of feature ids is the best way to handle selection. Using an Id filter as shown above is often very fast. * For databases this will result in a query based on the primary key * For shapefiles it will often be based on the row number * For a memory datastore the features are stored in a TreeSet sorted by feature id Q: How to find a Feature by Name CQL is very good for one off queries like this: .. literalinclude:: /../src/main/java/org/geotools/main/FilterExamples.java :language: java :start-after: // grabSelectedName start :end-before: // grabSelectedName end To select this feature while ignoring case we are going to have to use the FilterFactory (rather than CQL): .. literalinclude:: /../src/main/java/org/geotools/main/FilterExamples.java :language: java :start-after: // grabSelectedNameIgnoreCase start :end-before: // grabSelectedNameIgnoreCase end * Q: How find Features using Names If you have a Set of "names" which you would like to query from PostGIS. In this case we are doing a check for an attribute called "Name". .. literalinclude:: /../src/main/java/org/geotools/main/FilterExamples.java :language: java :start-after: // grabSelectedNames start :end-before: // grabSelectedNames end You may want to experiment with the option to ignore case. Handling Bounding Box ''''''''''''''''''''' Often your users start by selecting on something with a click. Translating that to a BoundingBox, and then into a query is a little involved. .. image:: /images/Filter_BBox.png You can make a bounding box query as shown below: .. literalinclude:: /../src/main/java/org/geotools/main/FilterExamples.java :language: java :start-after: // grabFeaturesInBoundingBox start :end-before: // grabFeaturesInBoundingBox end As you can see we had to sort out what the attribute name of the default geometry was in order to make the correct query. * Q: What features are on the screen? * BBOX One quick way to handle this is to treat the screen as a BBox and make a request for the contents. .. image:: /images/filter_screen.png Using a simple bounding box check is fast, but may retrieve more content then you will end up displaying.:: ReferencedEnvelope screen = new ReferencedEnvelope( x1, y1, x2, y2, worldCRS ); // Transform to dataCRS, ignoring difference in datum, 10 samples per edge ReferencedEnvelope world = screen.transform( dataCRS, true, 10 ); FilterFactory2 bounds = CommonFactoryFinder.getFilterFactory2( GeoTools.getDefaultHints ); Filter filter = ff.bbox( ff.property( "THE_GEOM" ), ff.literal( bounds ) ); * Intersects The other way is to constructing a more exact polygon that follows the shape of your screen when transformed into your data's coordinate reference system. .. image:: /images/filter_screen_polygon.png Using a more exact polygon will result in a slower check, but less features will be retrieved (good for working with a WFS where sending the content is expensive): .. literalinclude:: /../src/main/java/org/geotools/main/FilterExamples.java :language: java :start-after: // grabFeaturesInPolygon start :end-before: // grabFeaturesInPolygon end * BBox and then Intersects You can go faster by using both techniques, the bounds check will cut down on most of the features right away; and the more expensive Polygon for the final check. .. image:: /images/filter_screen2.png Advanced data sources like PostGIS perform these kind of checks internally, but you will notice a large difference when working with shape files.: .. literalinclude:: /../src/main/java/org/geotools/main/FilterExamples.java :language: java :start-after: // grabFeaturesOnScreen start :end-before: // grabFeaturesOnScreen end Q: What did I click on? .. image:: /images/Filter_BBox.png Construct a bounding box for the pixel, and "back project" it into your data's coordinate reference system. In the following example we have expanded our bounding box to be 3x3 pixels in order to make it easier to click on points and lines: In this example we have transformed our selection using 10 points for each edge of the rectangle: .. literalinclude:: /../src/main/java/org/geotools/main/FilterExamples.java :language: java :start-after: // click1 start :end-before: // click1 end * Use a point for to Check Polygon Layers You can turn the tables around when selecting polygons, and issue your query using a single point - and check what polygon(s) contain the point. .. image:: /images/filter_point.png Please note this is only suitable for working with Polygons as selecting a line using a single point is next to impossible.:: FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2( null ); Filter filter = ff.contains( ff.property( geometryAttributeName ), ff.literal( point ) ); * Use a distance Check Another common approach is to call geometry.buffer( distance ) and then select with the resulting polygon. Please consider making your request with a distance check instead. The "units" for the distance are the same as for your data; so if you are using "EPSG:3005" they will be in meters; if you are using "EPSG:4326" they will be in angular degrees.: .. literalinclude:: /../src/main/java/org/geotools/main/FilterExamples.java :language: java :start-after: // distance start :end-before: // distance end This technique also benefits from adding a BBox check in front. Expression ^^^^^^^^^^ We can go through the same steps to create an expression. * CQL Used for human readable code examples, "Common Query Language" parser is provided by **gt-cql** jar.:: Expression filter = CQL.toExpression("attName"); * FilterFactory You have a choice of which filter factory to use: * **FilterFactory** directly create a filter - according to the limitations of the standard standard. Use this if you are making a request of an external Web Feature Server and do not want to accidentally step out of bounds. * **FilterFactory2** directly create a filter. Has some additional methods for working with JTS Geometry. Here is an example:: FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2( GeoTools.getDefaultHints ); Expression propertyAccess = ff.property( "THE_GEOM"); Expression literal = ff.literal( geometry ); Expression math = ff.add( ff.literal(1), ff.literal(2) ); Expression math = ff.add( ff.literal(1), ff.literal(2) ); Expression function = function( "length', ff.property("CITY_NAME") ); Function ^^^^^^^^ Function is a little special as it is your chance to add new functions to the system. .. image:: /images/filter_function.PNG You accomplish this by providing three peices of information: * Function - implementation of Function * FunctionName - description of Function including name and argument names advertising that a Function is available * FunctionFactory - used to construct a Function on request by FilterFactory.function( name, expression ... ) If you are interested in implementing a function and registering it with GeoTools is the example used to explain the GeoTools plugin system. FunctionFinder ^^^^^^^^^^^^^^ You can directly create a Function with a FunctionFinder; this gives you access to the actual implementing class; which occasionally offer more options that are accessible through filter factory. * getAllFunctionDescriptions() * findFunctionDescription(String) * FunctionFinder.findFunction(String) * FunctionFinder.findFunction(String, List ) * FunctionFinder.findFunction(String, List) * gets(String, Class) * number(Object) * puts(Color) * puts(double) * puts(Object) Helper methods for those implementing Filter, these have been replaced with the much more powerful **Converters** class.:: // before text = Filters.puts( Color.BLACK ); // after text = Converters.convert( Color.BLACK, String.class ); * Filters.getFilterType(Filter) Assist GeoTools 2.2 code where FeatureType constants were used rather than instanceof checks.:: // before switch( Filters.getFilterType( filter ) ){ FilterType.GEOMETRY_BBOX: ... } // after if( filter instanceof BBOX ){ .. } * Filters.duplicate( filter ) Can be used to perform a deep copy of a Filter:: Filters utility = Filters(); Filter copy = filters.duplicate( filter ); Internally this method uses DuplicatingFilterVisitor:: DuplicatingFilterVisitor duplicate = new DuplicatingFilterVisitor(); Filter copy = (Filter) filter.accept( duplicate, null );