Filter¶
The filter API defines the first step of a Query, it is used when requesting data from a DataStore or Catalog. The Filter specification itself is maintained by the OGC and is used in a number of their specifications (Catalog, WFS, and so on).
In practice Filter
is used to select features that are of interest and the Expression
to
drill in and access information.
Although this forms a closed set of classes (for interoperability), you can extend the system with your own functions.
References:
Introduction¶
The Filter specification defines the filter
data structure used to perform selection.
A subset of this data structure is used to define expressions
to define, calculate or
extract information.
Here is an example of using filter and expression together:
final FilterFactory ff = CommonFactoryFinder.getFilterFactory(); Filter filter = ff.propertyLessThan( ff.property( "AGE"), ff.literal( 12 ) ); SimpleFeatureCollection features = featureSource.getFeatures( filter ); features.accepts( new FeatureVisitor<SimpleFeature>() ){ public void visit( SimpleFeature feature ){ Expression expression = ff.property( "NAME" ); String name = expression.evaulate( feature, String.class ); System.out.println( feature.getID() + " is named: "+ name ); } }, null );
There are a couple other ways to access feature content:
You can construct a filter or expression using the common query language
You can construct a filter or expression from XML
You can access the feature data structure directly
Here is the same example using common query language and direct feature access:
Filter filter = CQL.toFilter( "AGE < 12 " ); SimpleFeatureCollection features = featureSource.getFeatures( filter ); features.accepts( new FeatureVisitor<SimpleFeature>() ){ public void visit( SimpleFeature feature ){ String name = feature.getAttribute("NAME"); System.out.println( feature.getID() + " is named: "+ name ); } }, null );
You can also make use of a filter “by hand” evaluate against a Feature to test if it is:
TRUE: included in the set; or
FALSE: excluded from the set
Here is an example of how to evaluate a feature using a filter:
if( filter.evaluate( feature ) ){ // the feature was "selected" by the filter System.out.println( "Selected "+ feature.getId(); }
Filters in GeoTools are very forgiving:
Values don’t have to be exactly the right type.
GeoTools providers a Converters utility class to “type morph” objects into the required class.
What this means is you can mix and match types and not worry:
// the following should be considered equal filter = ff.equals( ff.literal("123"), ff.literal(123) );
You can use this facility yourself to automatically ask expressions to evaluate to the kind of object you need.:
Expression expression = ff.literal("#FFFFFF"); Color color = expression.evaluate( feature, Color.class );
You can also use a Filter on normal Java objects (i.e. POJOs)
If you are willing to write custom code you can define your own
PropertyAccessor
to teach filters how to work with your own data structures
Filter¶
The filter interface itself is designed to test set membership. That sounds dry - but it is accurate. A filter is used to test features one at a time to determine what you want returned to you. Being a spatial standard the tests are often spatial in nature (like intersects, bounding box).
You can think of Filter as similar to an SQL WHERE clause.
Not Object Oriented
A common mistake for programmers to make is to assume that you can implement Filter in order to define a custom filter.
The set of filter interfaces understood by the system is strictly limited to those defined by the Filter specification.
All hope is not lost - you can define your own custom Functions.
CQL¶
The common query language is a standard that appeared as part of the OGC Catalog specification. It defines an text syntax similar to SQL for defining Filters.:
Filter filter = CQL.toFilter("attName >= 5");
Expression percent = CQL.toExpression("ratio * 100");
In addition to the base standard the GeoTools community has allowed for a couple of extensions available using the ECQL class:
Filter filter = ECQL.toFilter("area( SHAPE ) BETWEEN 10000 AND 30000");
In this case the idea of comparing an area expression is an extension as the base CQL specification only allows property values to be compared.
FilterFactory¶
By using the FilterFactory
you can create objects by hand. The FilterFactory
interface is limited
to strict specification compliance.:
FilterFactory ff = CommonFactoryFinder.getFilterFactory( GeoTools.getDefaultHints() );
Filter filter = ff.propertyLessThan( ff.property( "AGE"), ff.literal( 12 ) );
FilterFactory¶
In the real world we need to go beyond the specification. FilterFactory
will let you work with JTS
Geometry instances (the specification is only defined to work with ISO Geometry).:
FilterFactory ff = CommonFactoryFinder.getFilterFactory( GeoTools.getDefaultHints() );
Filter filter = ff.contains( ff.property( "THE_GEOM"), ff.literal( geometry ) );
FilterFactory
also let’s you define your filters in a bit more of a free form manner. In the
specification all operations must have the PropertyName
expression first.
XML¶
Remember that Filter is a standard? Well it actually is an XML standard with documents that look like this:
<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc" xmlns:gml="http://www.opengis.net/gml">
<ogc:PropertyIsGreaterThanOrEqualTo>
<ogc:PropertyName>attName</ogc:PropertyName>
<ogc:Literal>5</ogc:Literal>
</ogc:PropertyIsGreaterThanOrEqualTo>
</ogc:Filter>
To parse an XML stream:
Configuration configuration = new org.geotools.filter.v1_0.OGCConfiguration();
Parser parser = new Parser( configuration );
Filter filter = (Filter) parser.parse( inputStream );
To parse a DOM fragment:
InputSource input = new InputSource( reader );
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document dom = db.parse( input );
Filter filter = null;
// first grab a filter node
NodeList nodes = dom.getElementsByTagName("Filter");
for (int j = 0; j < nodes.getLength(); j++) {
Element filterNode = (Element) nodes.item(j);
NodeList list = filterNode.getChildNodes();
Node child = null;
for (int i = 0; i < list.getLength(); i++) {
child = list.item(i);
if ((child == null) || (child.getNodeType() != Node.ELEMENT_NODE)) {
continue;
}
filter = FilterDOMParser.parseFilter(child);
}
}
System.out.println( "got:"+filter );
For more information, including examples for other versions of the filter specification, please review the gt-xml filter documentation.
Basic¶
The core filter abstractions are here. This set of interfaces is closed (you cannot make a new filter class and expect it to work).
Comparison¶
The heart of the filter data model is property comparisons; these filters allow you to test the attributes of your feature and select out only those features that match:
FilterFactory ff = CommonFactoryFinder.getFilterFactory();
Filter filter;
// the most common selection criteria is a simple equal test
ff.equal(ff.property("land_use"), ff.literal("URBAN"));
// You can also quickly test if a property has a value
filter = ff.isNull(ff.property("approved"));
// The usual array of property comparisons is supported
// the comparison is based on the kind of data so both
// numeric, date and string comparisons are supported.
filter = ff.less(ff.property("depth"), ff.literal(300));
filter = ff.lessOrEqual(ff.property("risk"), ff.literal(3.7));
filter = ff.greater(ff.property("name"), ff.literal("Smith"));
filter = ff.greaterOrEqual(ff.property("schedule"), ff.literal(new Date()));
// PropertyIsBetween is a short inclusive test between two values
filter = ff.between(ff.property("age"), ff.literal(20), ff.literal("29"));
filter = ff.between(ff.property("group"), ff.literal("A"), ff.literal("D"));
// In a similar fashion there is a short cut for notEqual
filter = ff.notEqual(ff.property("type"), ff.literal("draft"));
// pattern based "like" filter
filter = ff.like(ff.property("code"), "2300%");
// you can customise the wildcard characters used
filter = ff.like(ff.property("code"), "2300?", "*", "?", "\\");
Null vs Nil¶
A related topic to comparing a property value is testing to see if a property has a value
at all. In the simple case PropertyIsNull
can be used to check that a property exists; and
that the value is empty.
We also have the situation where a property is allowed to occur zero or many times; in this case we want a nice clear way to check that a Property does not exist at all (that is occur = zero).
FilterFactory ff = CommonFactoryFinder.getFilterFactory();
Filter filter;
// previous example tested if approved equals "null"
filter = ff.isNull(ff.property("approved"));
// this example checks if approved exists at all
filter = ff.isNil(ff.property("approved"), "no approval available");
MatchCase¶
By default property comparison is case sensitive; you can override this default when constructing your filter as shown below.
FilterFactory ff = CommonFactoryFinder.getFilterFactory();
// default is matchCase = true
Filter filter = ff.equal(ff.property("state"), ff.literal("queensland"));
// You can override this default with matchCase = false
filter = ff.equal(ff.property("state"), ff.literal("new south wales"), false);
// All property comparisons allow you to control case sensitivity
Filter welcome = ff.greater(ff.property("zone"), ff.literal("danger"), false);
This capability was added by the Filter 1.1 specification.
MatchAction¶
All filters that implement the MultiValuedFilter
interface, support filtering on operands that
return multiple values on evaluation. The way these filters handle multiple values can be modified
through the MatchAction
property.
The property can be retrieved through a simple getter:
filter.getMatchAction()
MatchAction
has three possible values:
MatchAction.ANY
When no
MatchAction
is specified, it is set to the defaultMatchAction.ANY
.Evaluates to true if any possible combinations of operands evaluates to true:
List<Integer> ages = Arrays.asList(new Integer[] {7, 8, 10, 15}); Filter filter = ff.greater(ff.literal(ages), ff.literal(12), false, MatchAction.ANY); System.out.println("Any: " + filter.evaluate(null)); // prints Any: true
MatchAction.ALL
Evaluates to true if all possible combinations of operands evaluates to true.:
List<Integer> ages = Arrays.asList(new Integer[] {7, 8, 10, 15}); Filter filter = ff.greater(ff.literal(ages), ff.literal(12), false, MatchAction.ALL); System.out.println("All: " + filter.evaluate(null)); // prints All: false
MatchAction.ONE
Evaluates to true if exactly one possible combination of values evaluates to true:
List<Integer> ages = Arrays.asList(new Integer[] {7, 8, 10, 15}); Filter filter = ff.greater(ff.literal(ages), ff.literal(12), false, MatchAction.ONE); System.out.println("One: " + filter.evaluate(null)); // prints One: true
Multiple values are possible in a couple situations: when working with application schemas, or working directly with java objects. When an expression is evaluated against rich content of this nature child references may return a multi-valued attribute.
As an example this filter tests whether all children are older than 12:
Filter filter = ff.greater(ff.property("child/age"), ff.literal(12), true, MatchAction.ALL);
Logical¶
Filters can be combined using the usual binary logic of AND, OR and NOT.
// you can use *not* to invert the test; this is especially handy
// with like filters (allowing you to select content that does not
// match the provided pattern)
filter = ff.not(ff.like(ff.property("code"), "230%"));
// you can also combine filters to narrow the results returned
filter =
ff.and(
ff.greater(ff.property("rainfall"), ff.literal(70)),
ff.equal(ff.property("land_use"), ff.literal("urban"), false));
filter =
ff.or(
ff.equal(ff.property("code"), ff.literal("approved")),
ff.greater(ff.property("funding"), ff.literal(23000)));
INCLUDES and EXCLUDES¶
There are two constants defined that can be used as Sentinel objects (or placeholders). Both of them represent “I don’t have a Filter”, but they differ in what client code is supposed to do about it.
Filter.INCLUDES
All content is included in the set. Would return EVERYTHING if used in a Query.
Filter.EXCLUDES
Don’t include any content. Would return an empty Collection if used in a Query.
These values are often used as default values in other data structures - for example the default
value for Query.getFilter()
is Filter.INCLUDES
.
These are static constants and do not require a constructor:
filter = Filter.INCLUDE; // no filter provided! include everything filter = Filter.EXCLUDE; // no filter provided! exclude everything
You can check for these values when optimizing:
public void draw( Filter filter ){ if( filter == Filter.EXCLUDES ) return; // draw nothing Query query = new Query( "roads", filter ); FeatureCollection collection = store.getFeatureSource( "roads" ).getFeatures( filter ); ... }
However do be careful as it is easy to get confused.:
if( filter == Filter.INCLUDES || filter.evaluate( feature ) ){ System.out.println( "Selected "+ feature.getId(); }
Identifier¶
The other interesting way to use a filter to more as a “selection” in the GIS sense. In this
case rather than evaluating the attributes we will directly match the FeatureId
.
The most common test is against FeatureId
:
FilterFactory ff = CommonFactoryFinder.getFilterFactory(); Filter filter = ff.id( ff.featureId("CITY.98734597823459687235"), ff.featureId("CITY.98734592345235823474"));
Formally this style of Id matching is not supposed to be mixed with the traditional attribute based evaluation (such as a bounding box filter).
You can also use a Set<FeatureId>
:
FilterFactory ff = CommonFactoryFinder.getFilterFactory(); Set<FeatureId> selected = new HashSet<>(); selected.add(ff.featureId("CITY.98734597823459687235")); selected.add(ff.featureId("CITY.98734592345235823474")); Filter filter = ff.id(selected);
The other place where identifiers are used are when working with versioned information. In this
case a ResourceId
is used that consists of both a fid
and a rid
.
ResourceId
can be used to explore versioned information:
FilterFactory ff = CommonFactoryFinder.getFilterFactory(); Filter filter; // grab a specific revision filter = ff.id(ff.featureId("CITY.98734597823459687235", "A457")); // You can also use ResourceId to grab a specific revision filter = ff.id(ff.resourceId("CITY.98734597823459687235", "A457", new Version())); // grab the one before that filter = ff.id( ff.resourceId( "CITY.98734597823459687235", "A457", new Version(Action.PREVIOUS))); // grab the one after that filter = ff.id(ff.resourceId("CITY.98734597823459687235", "A457", new Version(Action.NEXT))); // grab the first one filter = ff.id( ff.resourceId( "CITY.98734597823459687235", "A457", new Version(Action.FIRST))); // grab the first one (ie index = 1 ) filter = ff.id(ff.resourceId("CITY.98734597823459687235", "A457", new Version(1))); // grab the twelfth record in the sequence (ie index = 12 ) filter = ff.id(ff.resourceId("CITY.98734597823459687235", "A457", new Version(12))); // Grab the entry close to Jan 1985 DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT); df.setTimeZone(TimeZone.getTimeZone("GMT")); filter = ff.id( ff.resourceId( "CITY.98734597823459687235", "A457", new Version(df.parse("1985-1-1")))); // Grab all the entries in the 1990s filter = ff.id( ff.resourceId( "CITY.98734597823459687235", df.parse("1990-1-1"), df.parse("2000-1-1")));
Spatial¶
Spatial filters are also available.
Here is a quick example showing how to request features within a bounding box.
FilterFactory ff = CommonFactoryFinder.getFilterFactory();
ReferencedEnvelope bbox =
new ReferencedEnvelope(x1, x2, y1, y2, DefaultGeographicCRS.WGS84);
Filter filter = ff.bbox(ff.property("the_geom"), bbox);
Please review the gt-main filter examples for examples of using spatial filters.
Temporal¶
Temporal filters have been recently defined by the Filter 2.0 specification and are a new addition for GeoTools 8.0.
The gt-main module supplies some of the implementation classes we will need:
DefaultIntant
: this is an implementation of Instant used to represent a single point in time.DefaultPeriod
: this is an implementation of Period used to represent a range in time
Here is an example illustrating their construction and use with temporal filters:
// use the default implementations from gt-main
DateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
Date date1 = FORMAT.parse("2001-07-05T12:08:56.235-0700");
Instant temporalInstant = new DefaultInstant(new DefaultPosition(date1));
// Simple check if property is after provided temporal instant
Filter after = ff.after(ff.property("date"), ff.literal(temporalInstant));
// can also check of property is within a certain period
Date date2 = FORMAT.parse("2001-07-04T12:08:56.235-0700");
Instant temporalInstant2 = new DefaultInstant(new DefaultPosition(date2));
Period period = new DefaultPeriod(temporalInstant, temporalInstant2);
Filter within = ff.toverlaps(ff.property("constructed_date"), ff.literal(period));
Expression¶
Many of the filters mentioned above are presented as a comparison between two (or more) expressions. Expressions are used to access data held in a Feature (or POJO, or Record, or …).
The core expression abstractions are here - this set is open in that you can define new functions.
You evaluate an expression against a feature:
Object value = expression.evaluate( feature );
Or against a Java Bean, or even java.util.Map
:
Object value = expression.evaluate( bean );
Out of the box expression is typeless, and will do its best to convert the value to the type required.
To do this yourself you can evaluate with a specific type of object in mind:
Integer number = expression.evaulate( feature, Integer.class );
As an example of conversion here is an expression converting a String to a Color:
Expression expr = ff.literal("#FF0000")
Color color = expr.evaluate( null, Color.class );
Expressions are so useful that you will see them pop up in many parts of GeoTools. They are used by styles to select which data for portrayal and so forth.
PropertyName
The
PropertyName
expression is used to extract information from your data model.The most common use is to access a Feature Property.:
FilterFactory ff = CommonFactoryFinder.getFilterFactory( GeoTools.getDefaultHints() ); Expression expr = ff.property("name"); Object value = expr.evaluate( feature ); // evaluate if( value instanceof String){ name = (String) value; } else { name = "(invalid name)"; }
You can also ask for the value specifically as a String, null will be returned if the value cannot be forced into a String:
FilterFactory ff = CommonFactoryFinder.getFilterFactory( GeoTools.getDefaultHints() ); Expression expr = ff.property("name"); String name = expr.evaluate( feature, String ); // evaluate if( name == null ){ name = "(invalid name)"; }
X-Paths and Namespaces
It is possible to use XPath expressions in filters. This is particularly useful for evaluating nested properties against complex features. To evaluate XPath expressions, an
org.xml.sax.helpers.NamespaceSupport
object is needed to associate prefixes with namespace URI’s.FilterFactory
supports the creation ofPropertyName
expression with associated namespace context information.:FilterFactory ff = CommonFactoryFinder.getFilterFactory( GeoTools.getDefaultHints() ); NamespaceSupport namespaceSupport = new NamespaceSupport(); namespaceSupport.declarePrefix("foo", "urn:cgi:xmlns:CGI:GeoSciML:2.0" ); Filter filter = ff.greater(ff.property("foo:city/foo:size",namespaceSupport),ff.literal(300000));
Namespace context information can be retrieved from an existing
PropertyName
expression:PropertyName propertyName = ff.property("foo:city/foo:size", namespaceSupport); NamespaceSupport namespaceSupport2 = propertyName.getNamespaceContext(); // now namespaceSupport2 == namespaceSupport !
PropertyName.getNamespaceContext()
will return null when thePropertyName
expression does not contain or does not support Namespace context information.Functions
You can create functions using
FilterFactory
:FilterFactory ff = CommonFactoryFinder.getFilterFactory( GeoTools.getDefaultHints() ); PropertyName a = ff.property("testInteger"); Literal b = ff.literal( 1004.0 ); Function min = ff.function("min", a, b );
For functions that take more than a couple parameters you will need to use an Array:
FilterFactory ff = CommonFactoryFinder.getFilterFactory(null); PropertyName property = ff.property("name"); Literal search = ff.literal("foo"); Literal replace = ff.literal("bar"); Literal all = ff.literal( true ); Function f = ff.function("strReplace", new Expression[]{property,search,replace,all});
What can you do when a Function is not found - creating the Function will fail!
The Symbology Encoding 2.0 specification has the concept of a
fallbackValue
- while we have not made it available via the factory yet you can use theFunctionFinder
.:FunctionFinder finder = new FunctionFinder(null); finder.findFunction("pi", Collections.emptyList(), ff.literal(Math.PI));
Note
that the literal value provided above is only used when the expression is evaluated in context of a service that does not support the
pi()
function.
FilterVisitor¶
FilterVisitor
is used to traverse the filter data structure. Common uses include:
Asking questions about the content of a Filter
Performing analysis and optimizations on a Filter (say replacing “1+1” with “2” )
Transforming the filter (think search and replace)
All of these activities have something in common:
the contents of the Filter need to be examined
a result or answer needs to be built up
Here is a quick code example showing the use of a visitor to traverse the data structure:
// The visitor will be called on each object
// in your filter
class FindNames extends DefaultFilterVisitor {
public Set<String> found = new HashSet<String>();
/** We are only interested in property name expressions */
public Object visit( PropertyName expression, Object data ) {
found.add( expression.getPropertyName() );
return found;
}
}
// Pass the visitor to your filter to start the traversal
FindNames visitor = new FindNames();
filter.accept( visitor, null );
System.out.println("Property Names found "+visitor.found );
For more examples please see gt-main where several visitors are defined for your use.
FilterCapabilities¶
GeoTools is very good about ensuring that your Filters and Expressions perform as expected; however it will do what you say (even at a great cost to performance!). Different web services and databases have different native capabilities. For any functionality that cannot be provided natively GeoTools will perform the work locally in Java. Indeed for simple file formats almost everything occurs locally in Java.
The FilterCapabilities
data structure is used to describe the native abilities of a
WebFeatureService
. We also use this data structure to describe the abilities of the different
JDBC DataStores
for working with Databases. Of special interest is the list of FunctionNames
supported.
This data structure is not commonly used in day to day GeoTools work; it is mostly of interest to those implementing support for new web or database services.