package mil.nga.giat.geowave.core.store.filter; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import mil.nga.giat.geowave.core.index.ByteArrayId; import mil.nga.giat.geowave.core.index.FloatCompareUtils; import mil.nga.giat.geowave.core.index.PersistenceUtils; import mil.nga.giat.geowave.core.index.sfc.data.BasicNumericDataset; import mil.nga.giat.geowave.core.index.sfc.data.BinnedNumericDataset; import mil.nga.giat.geowave.core.index.sfc.data.MultiDimensionalNumericData; import mil.nga.giat.geowave.core.index.sfc.data.NumericData; import mil.nga.giat.geowave.core.index.sfc.data.NumericRange; import mil.nga.giat.geowave.core.store.data.CommonIndexedPersistenceEncoding; import mil.nga.giat.geowave.core.store.data.IndexedPersistenceEncoding; import mil.nga.giat.geowave.core.store.dimension.NumericDimensionField; import mil.nga.giat.geowave.core.store.index.CommonIndexModel; /** * This filter can perform fine-grained acceptance testing on generic * dimensions, but is limited to only using MBR (min-max in a single dimension, * hyper-cubes in multi-dimensional space) * */ public class BasicQueryFilter implements DistributableQueryFilter { protected interface BasicQueryCompareOp { public boolean compare( double dataMin, double dataMax, double queryMin, double queryMax ); } public enum BasicQueryCompareOperation implements BasicQueryCompareOp { CONTAINS { @Override public boolean compare( double dataMin, double dataMax, double queryMin, double queryMax ) { // checking if data range contains query range return !((dataMin < queryMin) || (dataMax > queryMax)); } }, OVERLAPS { @Override public boolean compare( double dataMin, double dataMax, double queryMin, double queryMax ) { // per definition, it shouldn't allow only boundary points to // overlap (stricter than intersect, see DE-9IM definitions) return !((dataMax <= queryMin) || (dataMin >= queryMax)) && !EQUALS.compare( dataMin, dataMax, queryMin, queryMax) && !CONTAINS.compare( dataMin, dataMax, queryMin, queryMax) && !WITHIN.compare( dataMin, dataMax, queryMin, queryMax); } }, INTERSECTS { @Override public boolean compare( double dataMin, double dataMax, double queryMin, double queryMax ) { // similar to overlap but a bit relaxed (allows boundary points // to touch) // this is equivalent to !((dataMax < queryMin) || (dataMin > // queryMax)); return !DISJOINT.compare( dataMin, dataMax, queryMin, queryMax); } }, TOUCHES { @Override public boolean compare( double dataMin, double dataMax, double queryMin, double queryMax ) { return (FloatCompareUtils.checkDoublesEqual( dataMin, queryMax)) || (FloatCompareUtils.checkDoublesEqual( dataMax, queryMin)); } }, WITHIN { @Override public boolean compare( double dataMin, double dataMax, double queryMin, double queryMax ) { // checking if query range is within the data range // this is equivalent to (queryMin >= dataMin) && (queryMax <= // dataMax); return CONTAINS.compare( queryMin, queryMax, dataMin, dataMax); } }, DISJOINT { @Override public boolean compare( double dataMin, double dataMax, double queryMin, double queryMax ) { return ((dataMax < queryMin) || (dataMin > queryMax)); } }, CROSSES { @Override public boolean compare( double dataMin, double dataMax, double queryMin, double queryMax ) { // accordingly to the def. intersection point must be interior // to both source geometries. // this is not possible in 1D data so always returns false return false; } }, EQUALS { @Override public boolean compare( double dataMin, double dataMax, double queryMin, double queryMax ) { return (FloatCompareUtils.checkDoublesEqual( dataMin, queryMin)) && (FloatCompareUtils.checkDoublesEqual( dataMax, queryMax)); } } }; protected Map<ByteArrayId, List<MultiDimensionalNumericData>> binnedConstraints; protected NumericDimensionField<?>[] dimensionFields; // this is referenced for serialization purposes only protected MultiDimensionalNumericData constraints; protected BasicQueryCompareOperation compareOp = BasicQueryCompareOperation.INTERSECTS; protected BasicQueryFilter() {} public BasicQueryFilter( final MultiDimensionalNumericData constraints, final NumericDimensionField<?>[] dimensionFields ) { init( constraints, dimensionFields); } public BasicQueryFilter( final MultiDimensionalNumericData constraints, final NumericDimensionField<?>[] dimensionFields, final BasicQueryCompareOperation compareOp ) { init( constraints, dimensionFields); this.compareOp = compareOp; } private void init( final MultiDimensionalNumericData constraints, final NumericDimensionField<?>[] dimensionFields ) { this.dimensionFields = dimensionFields; binnedConstraints = new HashMap<ByteArrayId, List<MultiDimensionalNumericData>>(); this.constraints = constraints; final BinnedNumericDataset[] queries = BinnedNumericDataset.applyBins( constraints, dimensionFields); for (final BinnedNumericDataset q : queries) { final ByteArrayId binId = new ByteArrayId( q.getBinId()); List<MultiDimensionalNumericData> ranges = binnedConstraints.get(binId); if (ranges == null) { ranges = new ArrayList<MultiDimensionalNumericData>(); binnedConstraints.put( binId, ranges); } ranges.add(q); } } protected boolean validateConstraints( final BasicQueryCompareOp op, final MultiDimensionalNumericData queryRange, final MultiDimensionalNumericData dataRange ) { final NumericData[] queryRangePerDimension = queryRange.getDataPerDimension(); final double[] minPerDimension = dataRange.getMinValuesPerDimension(); final double[] maxPerDimension = dataRange.getMaxValuesPerDimension(); boolean ok = true; for (int d = 0; d < dimensionFields.length && ok; d++) { ok &= op.compare( minPerDimension[d], maxPerDimension[d], queryRangePerDimension[d].getMin(), queryRangePerDimension[d].getMax()); } return ok; } @Override public boolean accept( final CommonIndexModel indexModel, final IndexedPersistenceEncoding<?> persistenceEncoding ) { if (!(persistenceEncoding instanceof CommonIndexedPersistenceEncoding)) return false; final BinnedNumericDataset[] dataRanges = BinnedNumericDataset.applyBins( ((CommonIndexedPersistenceEncoding) persistenceEncoding).getNumericData(dimensionFields), dimensionFields); // check that at least one data range overlaps at least one query range for (final BinnedNumericDataset dataRange : dataRanges) { final List<MultiDimensionalNumericData> queries = binnedConstraints.get(new ByteArrayId( dataRange.getBinId())); if (queries != null) { for (final MultiDimensionalNumericData query : queries) { if ((query != null) && validateConstraints( compareOp, query, dataRange)) { return true; } } } } return false; } @Override public byte[] toBinary() { int byteBufferLength = 8; final int dimensions = Math.min( constraints.getDimensionCount(), dimensionFields.length); final List<byte[]> lengthDimensionAndQueryBinaries = new ArrayList<byte[]>( dimensions); final NumericData[] dataPerDimension = constraints.getDataPerDimension(); for (int d = 0; d < dimensions; d++) { final NumericDimensionField<?> dimension = dimensionFields[d]; final NumericData data = dataPerDimension[d]; final byte[] dimensionBinary = PersistenceUtils.toBinary(dimension); final int currentDimensionByteBufferLength = (20 + dimensionBinary.length); final ByteBuffer buf = ByteBuffer.allocate(currentDimensionByteBufferLength); buf.putInt(dimensionBinary.length); buf.putDouble(data.getMin()); buf.putDouble(data.getMax()); buf.put(dimensionBinary); byteBufferLength += currentDimensionByteBufferLength; lengthDimensionAndQueryBinaries.add(buf.array()); } final ByteBuffer buf = ByteBuffer.allocate(byteBufferLength); buf.putInt(this.compareOp.ordinal()); buf.putInt(dimensions); for (final byte[] binary : lengthDimensionAndQueryBinaries) { buf.put(binary); } return buf.array(); } @Override public void fromBinary( final byte[] bytes ) { final ByteBuffer buf = ByteBuffer.wrap(bytes); this.compareOp = BasicQueryCompareOperation.values()[buf.getInt()]; final int numDimensions = buf.getInt(); dimensionFields = new NumericDimensionField<?>[numDimensions]; final NumericData[] data = new NumericData[numDimensions]; for (int d = 0; d < numDimensions; d++) { final byte[] field = new byte[buf.getInt()]; data[d] = new NumericRange( buf.getDouble(), buf.getDouble()); buf.get(field); dimensionFields[d] = PersistenceUtils.fromBinary( field, NumericDimensionField.class); } constraints = new BasicNumericDataset( data); init( constraints, dimensionFields); } }