Added by jgarnett, last edited by jgarnett on Sep 23, 2008  (view change)

Labels

 
(None)

There are many ways to use a filter, here are some examples from the mailing list.

Related:

Handling Selection

Often an application will want to remember the Fetaure that a user was working with for later. This section shows a couple of approaches to recording what Features a user has "selected".

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<String> of feature IDs, which you would like to query from a shapefile:

FeatureCollection grabSelectedIds( Set<String> selection ){
   FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2( GeoTools.getDefaultHints );

   Set<FeatureId> fids = new HashSet<FeatureId>();
   for( String id = selection ){
      FeatureId fid = ff.featureId( id );
      fids.add( fid );
   }
   Filter filter = ff.id( fids );
   return featureSource.getFeatures( filter );
}

Keeping a Set<String> 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

How to find a Feature by Name

CQL is very good for one off queries like this:

FeatureCollection grabSelectedName(String name) throws Exception {
        return featureSource.getFeatures(CQL.toFilter("Name = '" + name + "'"));
    }

To select this feature while ignoring case we are going to have to use the FilterFactory (rather than CQL):

FeatureCollection grabSelectedNameIgnoreCase( String name ) throws Exception {
       FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2( null );

       Filter filter = ff.equal( ff.property( "Name"), ff.literal( name ), false );
       return featureSource.getFeatures( filter );
    }

How find Features using Names

If you have a Set<String> of "names" which you would like to query from PostGIS. In this case we are doing a check for an attribute called "Name".

FeatureCollection grabSelectedNames( Set<String> selectedNames ) throws Exception {
   FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2( null );

   List<Filter> match = new ArrayList<Filter>();
   for( String name : selectedNames ){
      Filter aMatch = ff.equals( ff.property( "Name"), ff.literal( name ) );
      match.add( aMatch );
   }
   Filter filter = ff.or( match );
   return featureSource.getFeatures( filter );
}

You may want to experiment with the option to ignore case:

Bounding Box

You can make a bounding box query as shown below:

FeatureCollection grabFeaturesInBoundingBox( double x1, double y1, double x2, double y2) throws Exception {
    FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2( null );
    FeatureType schema = featureSource.getSchema();
    
    // usually "THE_GEOM" for shapefiles
    String geometryPropertyName = schema.getGeometryDescriptor().getLocalName();
    CoordinateReferenceSystem crs = schema.getGeometryDescriptor().getCoordinateReferenceSystem();
    
    ReferencedEnvelope bbox = new ReferencedEnvelope( x1,y1, x2, y2, crs );
    
    Filter filter = ff.bbox( ff.property( geometryPropertyName ), bbox );
    return featureSource.getFeatures( filter );
}

What features are on the screen

There are two ways to go about answering this question, use a bounding box query as shown above, or constructing a more exact polygon that follows the shape of your screen when projected onto the
round earth.

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 ) );

Alternative: Using a Polygon

Using a more exact polygon will result in a slower check, but less features will be retirved (good for working
with a WFS where sending the content is expensive):

// pending ... create polygon from bounds
//
FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2( GeoTools.getDefaultHints );
Filter filter = ff.not( ff.disjoint( ff.property( "THE_GEOM" ), ff.literal( polygon) ));

Optimization: Using Both

You can go faster by using both techniques, the bounds check will cut down on most of the features
right away; and for the remaining ones the polygon check will be performed. Advanced data sources like
PostGIS perform these kind of checks all the time; but you will notice a large difference when
working with shape files.

FilterFactory2 bounds = CommonFactoryFinder.getFilterFactory2( GeoTools.getDefaultHints );

Filter boundsCheck = ff.bbox( ff.property("THE_GEOM"), ff.literal( bounds ) );
Filter polygonCheck = ff.not( ff.disjoint( ff.property("THE_GEOM"), ff.literal( polygon) ));

Filter filter = ff.and( boundsCheck, polygonCheck );

What did I click on

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:

ReferencedEnvelope pixels = new ReferencedEnvelope( x-1,y-1, x+1, y+1, screenCRS );
MathTransform screen2world= CRS.findMathTransform(screenCRS, dataCRS);

ReferencedEnvelope selected = JTS.transform( pixels, null, screen2world, 10 );

FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2( GeoTools.getDefaultHints );
Filter filter = ff.bbox( ff.property("THE_GEOM" ), ff.literal( selected ) );

Alternative: Use a point for to Check Polygon Layers

Often people make the mistake of generating a point for the pixel on the screen, this works fine when querying against a polygon FeatureSource, but it is almost impossible to select a Point or Line this way:

FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2( null );
Filter filter = ff.contains( ff.property( "POLYGON" ), ff.literal( point ) );

Alternative: Use a distance Check

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.

FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2( null );
Filter filter = ff.distance( ff.property( "POLYGON" ), ff.literal( point ), 10 );

This is a straight math test; nothing fancy will happen as you move towards the poles. If you want to get fancy make a real polygon in a projection measured in meters, transform it to your data crs and use the resulting shape to perform your query.

Note this technique also benefits from adding a bounds check in front:

FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2( null );

Coordinate coord = new Coordinate( x, y );
Point point = geometryFactory.createPoint(coord);

ReferencedEnvelope bounds = new ReferencedEnvelope( x-5,y-5, x+5, y+5, dataCRS );

Filter boundsCheck = ff.bbox( "THE_GEOM" ), ff.literal( selected ) );
Filter distanceCheck = ff.distance( ff.property( "POLYGON" ), ff.literal( point ), 10 );

Filter filter = ff.and( boundsCheck, distanceCheck );

Examples

The following examples have been provided on the user list (often in due to confusion over the above examples).

Polygon Interaction

Thanks to Aaron Parks for sending us this great example of using the bounding box of a polygon to quickly isolate interesting features; which can then be checked one by one for "not disjoint" (ie the features touch or overlap our polygon).

FeatureCollection fcResult=null;

FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2( GeoTools.getDefaultHints() );
		
Feature feature =null; //org.geotools.feature.Feature
	
Filter polyCheck = null;
Filter andFil=null;
Filter boundsCheck =null;
		
String qryStr = null;

FeatureIterator it = polygonCollection.features();
try {
    while(it.hasNext()){
        feature = it.next();    			
        qryStr=getBBoxqryStr(feature); //returns something like: "BBOX(the_geom,10,20,30,40)"
        try {
            boundsCheck=CQL.toFilter(qryStr);
        } catch (CQLException e1) {
            log.error("Unable to create CQL : {}, Reason: {}", qryStr , e1);
            continue;
        }

        polyCheck=ff.not(ff.disjoint(ff.property("the_geom"),ff.literal( feature.getPrimaryGeometry() )));
        andFil=ff.and(boundsCheck, polyCheck);
			
        try {
            fcResult=fsPoint.getFeatures(andFil);
        } catch (IOException e1) {
            log.error("Unable to run filter, Reason:{}", e1);
            continue;
        }
			
        if(!fcResult.isEmpty()){
            int resultSize = fcResult.size();
            String resultID = feature.getID();
            putIntoResult(resultID, resultSize);
            log.info("Comparison Done found {} shapes at feature: {}", resultSize, resultID );
        }
    } 
} finally {
    polygonCollection.close( it );
}