package com.vividsolutions.jump.workbench.ui.plugin.analysis;
import java.util.*;
import com.vividsolutions.jump.task.*;
import com.vividsolutions.jump.workbench.ui.GenericNames;
import com.vividsolutions.jts.geom.*;
import com.vividsolutions.jump.feature.*;
/**
* Exceutes a spatial query with a given mask FeatureCollection, source FeatureCollection,
* and predicate.
* Ensures result does not contain duplicates.
*
* @author Martin Davis
* @version 1.0
*/
public class SpatialQueryExecuter
{
private FeatureCollection maskFC;
private FeatureCollection sourceFC;
// private GeometryPredicate predicate;
private FeatureCollection queryFC;
private boolean complementResult = false;
private boolean allowDuplicatesInResult = false;
private boolean isExceptionThrown = false;
private Geometry geoms[] = new Geometry[2];
private Set resultSet = new HashSet();
private boolean createNewLayer = true;
public SpatialQueryExecuter(FeatureCollection maskFC, FeatureCollection sourceFC)
{
this.maskFC = maskFC;
this.sourceFC = sourceFC;
}
/**
* Sets whether duplicate features are allowed in the result set.
*
* @param isAllowDuplicates true if duplicates are allowed
*/
public void setAllowDuplicates(boolean isAllowDuplicates)
{
this.allowDuplicatesInResult = isAllowDuplicates;
}
/**
* Sets whether the result set should be complemented
*
* @param complementResult true if the result should be complemented
*/
public void setComplementResult(boolean complementResult)
{
this.complementResult = complementResult;
}
/**
* Gets the featurec collection to query.
* This may be indexed if this would improve performance.
*
* @param func
* @return
*/
private void createQueryFeatureCollection(GeometryPredicate pred)
{
boolean buildIndex = false;
if (maskFC.size() > 10) buildIndex = true;
if (sourceFC.size() > 100) buildIndex = true;
if (pred instanceof GeometryPredicate.DisjointPredicate) buildIndex = false;
if (buildIndex) {
queryFC = new IndexedFeatureCollection(sourceFC);
}
else {
queryFC = sourceFC;
}
}
private Iterator query(GeometryPredicate pred, double[] params, Geometry gMask)
{
Envelope queryEnv = gMask.getEnvelopeInternal();
// special hack for withinDistance
if (pred instanceof GeometryPredicate.WithinDistancePredicate) {
queryEnv.expandBy(params[0]);
}
boolean useQuery = true;
if (pred instanceof GeometryPredicate.DisjointPredicate) useQuery = false;
Iterator queryIt = null;
if (useQuery) {
Collection queryResult = queryFC.query(queryEnv);
queryIt = queryResult.iterator();
}
else {
queryIt = queryFC.iterator();
}
return queryIt;
}
public boolean isExceptionThrown() { return isExceptionThrown; }
public FeatureCollection getResultFC()
{
return new FeatureDataset(sourceFC.getFeatureSchema());
}
private boolean isInResult(Feature f)
{
return resultSet.contains(f);
}
/**
* Computes geomSrc.func(geomMask)
*
* @param monitor
* @param func
* @param params
* @param resultFC
*/
public void execute(TaskMonitor monitor,
GeometryPredicate func,
double[] params,
FeatureCollection resultFC
)
{
createQueryFeatureCollection(func);
int total = maskFC.size();
int count = 0;
for (Iterator iMask = maskFC.iterator(); iMask.hasNext(); ) {
monitor.report(count++, total, GenericNames.FEATURES);
if (monitor.isCancelRequested()) return;
Feature fMask = (Feature) iMask.next();
Geometry gMask = fMask.getGeometry();
Iterator queryIt = query(func, params, gMask);
for (; queryIt.hasNext(); ) {
Feature fSrc = (Feature) queryIt.next();
// optimization - if feature already in result no need to re-test
if (isInResult(fSrc))
continue;
Geometry gSrc = fSrc.getGeometry();
geoms[0] = gSrc;
geoms[1] = gMask;
boolean isInResult = isTrue(func, gSrc, gMask, params);
if (isInResult) {
if (allowDuplicatesInResult) {
addToResult(fSrc, resultFC);
}
else {
resultSet.add(fSrc);
}
}
}
}
if (! allowDuplicatesInResult) {
if (complementResult) {
loadComplement(resultFC);
}
else {
loadResult(resultFC);
}
}
}
private void loadComplement(FeatureCollection resultFC)
{
for (Iterator i = sourceFC.iterator(); i.hasNext(); ) {
Feature f = (Feature) i.next();
if (! resultSet.contains(f)) {
addToResult(f, resultFC);
}
}
}
private void loadResult(FeatureCollection resultFC)
{
for (Iterator i = resultSet.iterator(); i.hasNext(); ) {
Feature f = (Feature) i.next();
addToResult(f, resultFC);
}
}
private void addToResult(Feature f, FeatureCollection resultFC)
{
// Code modified by the Sunburned Surveyor to allow
// the creation of "normal" selections if a new
// layer isn't being created for the features
// selected as part of the spatial analysis.
if(this.createNewLayer == true)
{
Feature fResult = f.clone(true);
resultFC.add(fResult);
}
else
{
resultFC.add(f);
}
}
private boolean isTrue(GeometryPredicate func, Geometry geom0, Geometry geom1, double[] params)
{
try {
return func.isTrue(geom0, geom1, params);
}
catch (RuntimeException ex) {
// simply eat exceptions and report them by returning null
isExceptionThrown = true;
}
return false;
}
// Code added by the Sunburned Surveyor to allow
// the creation of "normal" selections if a new
// layer isn't being created for the features
// selected as part of the spatial analysis.
//
// This method is currently called by the SpatialQueryPlugIn object.
/**
* Sets a boolean flag that indicates if features selected as part of the
* spatial analysis should be placed on a new layer, or should be selected
* in the source layer.
*/
public void setCreateNewLayer(boolean argCreateNewLayer)
{
this.createNewLayer = argCreateNewLayer;
}
// Code added by the Sunburned Surveyor to allow
// the creation of "normal" selections if a new
// layer isn't being created for the features
// selected as part of the spatial analysis.
/**
* Returns a boolean flag that indicates if features selected as part of the
* spatial analysis should be placed on a new layer, or should be selected
* in the source layer.
*/
public boolean getCreateNewLayer()
{
return this.createNewLayer;
}
}