Filter ------ GeoTools makes use of ``gt-api`` interfaces ``Filter`` and ``Expression`` in order to define constraints. This is most often used when making a Query to retrieve specific ``Features`` from a ``DataStore``. Reference: * ``gt-api`` data :doc:`../api/model` for ``Feature``, ``FeatureType`` and ``Filter`` * ``gt-api`` :doc:`../api/filter` model * ``gt-cql`` :doc:`../cql/index` * ``gt-xml`` :doc:`../xml/index` * `Filter Function Reference `__ (GeoServer User Manual) 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 guide will assume you are using the "Common Query Language", this parser is provided by the ``gt-cql`` module.:: 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. Use this if you are making a request of an external Web Feature Server and do not want to accidentally step out of bounds. * ``FilterFactory`` directly create a filter. Has some additional methods for working with JTS ``Geometry``. Here is an example:: FilterFactory ff = CommonFactoryFinder.getFilterFactory( 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``:: FilterFactory ff = CommonFactoryFinder.getFilterFactory( 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 ); FilterFactory bounds = CommonFactoryFinder.getFilterFactory( 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`` 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.:: FilterFactory ff = CommonFactoryFinder.getFilterFactory( 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. * ``FilterFactory`` directly create a filter. Has some additional methods for working with JTS Geometry. Here is an example:: FilterFactory ff = CommonFactoryFinder.getFilterFactory( 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 pieces 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 ); * ``Filters.attributeNames(filter, SimpleFeatureType)`` * ``Filters.propertyNames(expression)`` * ``Filters.propertyNames(expression, SimpleFeatureType)`` * ``Filters.propertyNames(filter)`` * ``Filters.propertyNames(filter, SimpleFeatureType)`` * ``Filters.findPropertyName( filter );`` Methods use to query the structure of the provided ``Filter``. Internally these usually use a ``FilterVisitor`` to traverse the provided ``Filter`` to determine the answer. Here is a quick example using ``findPropertyName( filter )``:: String xpath = Filters.findPropertyName(b); if( xpath != null ){ // proeprty name found with indicated xpath expression } else { // filter does not contain any references to PropertyName } * ``Filters.hasChildren(Filter)`` * ``Filters.children(Filter)`` * ``Filters.children(Filter, boolean)`` ``Query`` methods regard "children" only apply to filters *and*, *or* and *not* which are used to combine the results of several filters. * ``Filters.and(FilterFactory, Filter, Filter)`` * ``Filters.or(FilterFactory, Filter, Filter)`` Used to combine two filters; will sensibly "append" filters onto an existing *and* or *or* filter. The result is a new filter combining the two filters as requested. Tip: Used to update GeoTools 2.2 code where Filter was mutable. * ``Filters.removeFilter(Filter, Filter)`` * ``Filters.removeFilter(Filter, Filter, boolean)`` Used to separate out a child from the provided filter. Much like *and* and *or* above these methods return a modified filter. Capabilities ^^^^^^^^^^^^ You can also use the ``FilterFactory`` to fill in a filter capabilities data structure describing the abilities of a web or database service. However we also have a ``Capabilities`` user class which is helpful both in acting as a "builder" when creating a ``FilterCapabilities`` data structure; and also in making use of the result. * ``Capabilities`` as a builder:: Capabilities capabilities = new Capabilities(); capabilities.addType( Beyond.class ); // add to SpatialCapabilities capabilities.addType( PropertyIsEqualTo.class ); // add to ScalarCapabilities capabilities.addName( "NullCheck" ); // will enable PropertyIsNull use capabilities.addName( "Mul" ); // will enable hasSimpleArithmatic capabilities.addName( "random" ); // a function returning a random number capabilities.addName( "Length", 1 ); // single argument function capabilities.addName( "toDegrees", "radians" ); // single argument function capabilities.addName( "length", "expression" ); * Using Capabilities:: Capabilities capabilities = new Capabilities( filterCapabilities ); FilterFactory ff = CommonFactoryFinder.getFilterFactory(null); Filter filter = ff.between( ff.literal(0), ff.property("x"), ff.literal( 2 ) ); if( capabilities.supports( filter ) ){ // native support for "between" available }