package mil.nga.giat.geowave.core.store.data; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import mil.nga.giat.geowave.core.index.ByteArrayId; import mil.nga.giat.geowave.core.index.dimension.NumericDimensionDefinition; import mil.nga.giat.geowave.core.index.sfc.SFCDimensionDefinition; import mil.nga.giat.geowave.core.index.sfc.data.BasicNumericDataset; 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.store.dimension.NumericDimensionField; import mil.nga.giat.geowave.core.store.index.CommonIndexValue; import mil.nga.giat.geowave.core.store.index.PrimaryIndex; import org.apache.commons.lang3.ArrayUtils; import com.google.common.math.DoubleMath; /** * This class models all of the necessary information for persisting data in * Accumulo (following the common index model) and is used internally within * GeoWave as an intermediary object between the direct storage format and the * native data format. It also contains information about the persisted object * within a particular index such as the insertion ID in the index and the * number of duplicates for this entry in the index, and is used when reading * data from the index. */ public class CommonIndexedPersistenceEncoding extends IndexedPersistenceEncoding<CommonIndexValue> { public CommonIndexedPersistenceEncoding( final ByteArrayId adapterId, final ByteArrayId dataId, final ByteArrayId indexInsertionId, final int duplicateCount, final PersistentDataset<CommonIndexValue> commonData, final PersistentDataset<byte[]> unknownData ) { super( adapterId, dataId, indexInsertionId, duplicateCount, commonData, unknownData); } /** * Given an index, convert this persistent encoding to a set of insertion * IDs for that index * * @param index * the index * @return The insertions IDs for this object in the index */ public List<ByteArrayId> getInsertionIds( final PrimaryIndex index ) { final MultiDimensionalNumericData boxRangeData = getNumericData(index.getIndexModel().getDimensions()); final List<ByteArrayId> untrimmedResult = index.getIndexStrategy().getInsertionIds( boxRangeData); final int size = untrimmedResult.size(); if (size > 3) { // need at least 4 quadrants in a quadtree to create a // concave shape where the mbr overlaps an area that the // underlying polygon doesn't final Iterator<ByteArrayId> it = untrimmedResult.iterator(); while (it.hasNext()) { final ByteArrayId insertionId = it.next(); // final MultiDimensionalNumericData md = // correctForNormalizationError(index.getIndexStrategy().getRangeForId(insertionId)); // used to check the result of the index strategy if (LOGGER.isDebugEnabled() && checkCoverage( boxRangeData, index.getIndexStrategy().getRangeForId( insertionId))) { LOGGER.error("Index strategy produced an unmatching tile during encoding and storing an entry"); } if (!overlaps( index.getIndexStrategy().getRangeForId( insertionId).getDataPerDimension(), index)) { it.remove(); } } } return untrimmedResult; } /** * Tool can be used custom index strategies to check if the tiles actual * intersect with the provided bounding box. * * @param boxRangeData * @param innerTile * @return */ private boolean checkCoverage( final MultiDimensionalNumericData boxRangeData, final MultiDimensionalNumericData innerTile ) { for (int i = 0; i < boxRangeData.getDimensionCount(); i++) { final double i1 = innerTile.getDataPerDimension()[i].getMin(); final double i2 = innerTile.getDataPerDimension()[i].getMax(); final double j1 = boxRangeData.getDataPerDimension()[i].getMin(); final double j2 = boxRangeData.getDataPerDimension()[i].getMax(); final boolean overlaps = ((i1 < j2) || DoubleMath.fuzzyEquals( i1, j2, DOUBLE_TOLERANCE)) && ((i2 > j1) || DoubleMath.fuzzyEquals( i2, j1, DOUBLE_TOLERANCE)); if (!overlaps) { return false; } } return true; } /** * Given an ordered set of dimensions, convert this persistent encoding * common index data into a MultiDimensionalNumericData object that can then * be used by the Index * * @param dimensions * @return */ @SuppressWarnings({ "rawtypes", "unchecked" }) public MultiDimensionalNumericData getNumericData( final NumericDimensionField[] dimensions ) { final NumericData[] dataPerDimension = new NumericData[dimensions.length]; for (int d = 0; d < dimensions.length; d++) { CommonIndexValue val = getCommonData().getValue( dimensions[d].getFieldId()); if (val != null) { dataPerDimension[d] = dimensions[d].getNumericData(val); } } return new BasicNumericDataset( dataPerDimension); } private static class DimensionRangePair { NumericDimensionField[] dimensions; NumericData[] dataPerDimension; DimensionRangePair( final NumericDimensionField field, final NumericData data ) { dimensions = new NumericDimensionField[] { field }; dataPerDimension = new NumericData[] { data }; } void add( final NumericDimensionField field, final NumericData data ) { dimensions = ArrayUtils.add( dimensions, field); dataPerDimension = ArrayUtils.add( dataPerDimension, data); } } // Subclasses may want to override this behavior if the belief that the // index strategy is optimal // to avoid the extra cost of checking the result protected boolean overlaps( final NumericData[] insertTileRange, final PrimaryIndex index ) { @SuppressWarnings("rawtypes") final NumericDimensionDefinition[] dimensions = index.getIndexStrategy().getOrderedDimensionDefinitions(); final NumericDimensionField[] fields = index.getIndexModel().getDimensions(); Map<Class, NumericDimensionField> dimensionTypeToFieldMap = new HashMap<>(); for (NumericDimensionField field : fields) { dimensionTypeToFieldMap.put( field.getBaseDefinition().getClass(), field); } // Recall that each numeric data instance is extracted by a {@link // DimensionField}. More than one DimensionField // is associated with a {@link CommonIndexValue} entry (e.g. Lat/Long, // start/end). These DimensionField's share the // fieldId. // The infrastructure does not guarantee that CommonIndexValue can be // reconstructed fully from the NumericData. // However, provided in the correct order for interpretation, the // CommonIndexValue can use those numeric data items // to judge an overlap of range data. final Map<ByteArrayId, DimensionRangePair> fieldsRangeData = new HashMap<ByteArrayId, DimensionRangePair>( dimensions.length); for (int d = 0; d < dimensions.length; d++) { Class baseDefinitionCls; if (dimensions[d] instanceof SFCDimensionDefinition) { baseDefinitionCls = ((SFCDimensionDefinition) dimensions[d]).getDimensionDefinition().getClass(); } else { baseDefinitionCls = dimensions[d].getClass(); } NumericDimensionField field = dimensionTypeToFieldMap.get(baseDefinitionCls); if (field != null) { final ByteArrayId fieldId = field.getFieldId(); final DimensionRangePair fieldData = fieldsRangeData.get(fieldId); if (fieldData == null) { fieldsRangeData.put( fieldId, new DimensionRangePair( field, insertTileRange[d])); } else { fieldData.add( field, insertTileRange[d]); } } } for (final Entry<ByteArrayId, DimensionRangePair> entry : fieldsRangeData.entrySet()) { PersistentDataset<CommonIndexValue> commonData = getCommonData(); if (commonData != null) { CommonIndexValue value = commonData.getValue(entry.getKey()); if (value != null && !value.overlaps( entry.getValue().dimensions, entry.getValue().dataPerDimension)) { return false; } } } return true; } }