package mil.nga.giat.geowave.core.index.sfc.zorder; import java.math.BigInteger; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import mil.nga.giat.geowave.core.index.ByteArrayId; import mil.nga.giat.geowave.core.index.ByteArrayRange; import mil.nga.giat.geowave.core.index.PersistenceUtils; import mil.nga.giat.geowave.core.index.sfc.RangeDecomposition; import mil.nga.giat.geowave.core.index.sfc.SFCDimensionDefinition; import mil.nga.giat.geowave.core.index.sfc.SpaceFillingCurve; import mil.nga.giat.geowave.core.index.sfc.data.BasicNumericDataset; import mil.nga.giat.geowave.core.index.sfc.data.MultiDimensionalNumericData; /*** * Implementation of a ZOrder Space Filling Curve. Also called Morton, GeoHash, * etc. * */ public class ZOrderSFC implements SpaceFillingCurve { private SFCDimensionDefinition[] dimensionDefs; private int cardinalityPerDimension; private double binsPerDimension; protected ZOrderSFC() { super(); } /*** * Use the SFCFactory.createSpaceFillingCurve method - don't call this * constructor directly * */ public ZOrderSFC( final SFCDimensionDefinition[] dimensionDefs ) { init(dimensionDefs); } private void init( final SFCDimensionDefinition[] dimensionDefs ) { this.dimensionDefs = dimensionDefs; cardinalityPerDimension = 1; for (final SFCDimensionDefinition dimensionDef : dimensionDefs) { if (dimensionDef.getBitsOfPrecision() > cardinalityPerDimension) { cardinalityPerDimension = dimensionDef.getBitsOfPrecision(); } } binsPerDimension = Math.pow( 2, cardinalityPerDimension); } /*** * {@inheritDoc} */ @Override public byte[] getId( final double[] values ) { final double[] normalizedValues = new double[values.length]; for (int d = 0; d < values.length; d++) { normalizedValues[d] = dimensionDefs[d].normalize(values[d]); } return ZOrderUtils.encode( normalizedValues, cardinalityPerDimension, values.length); } @Override public MultiDimensionalNumericData getRanges( final byte[] id ) { return new BasicNumericDataset( ZOrderUtils.decodeRanges( id, cardinalityPerDimension, dimensionDefs)); } @Override public long[] getCoordinates( final byte[] id ) { return ZOrderUtils.decodeIndices( id, cardinalityPerDimension, dimensionDefs.length); } @Override public double[] getInsertionIdRangePerDimension() { final double[] retVal = new double[dimensionDefs.length]; for (int i = 0; i < dimensionDefs.length; i++) { retVal[i] = dimensionDefs[i].getRange() / binsPerDimension; } return retVal; } @Override public BigInteger getEstimatedIdCount( final MultiDimensionalNumericData data ) { final double[] mins = data.getMinValuesPerDimension(); final double[] maxes = data.getMaxValuesPerDimension(); BigInteger estimatedIdCount = BigInteger.valueOf(1); for (int d = 0; d < data.getDimensionCount(); d++) { final double binMin = dimensionDefs[d].normalize(mins[d]) * binsPerDimension; final double binMax = dimensionDefs[d].normalize(maxes[d]) * binsPerDimension; estimatedIdCount = estimatedIdCount.multiply(BigInteger.valueOf((long) (Math.abs(binMax - binMin) + 1))); } return estimatedIdCount; } /*** * {@inheritDoc} */ @Override public RangeDecomposition decomposeRange( final MultiDimensionalNumericData query, final boolean overInclusiveOnEdge, final int maxFilteredIndexedRanges ) { // TODO: Because the research and benchmarking show Hilbert to // outperform Z-Order // the optimization of full query decomposition is not implemented at // the moment for Z-Order final double[] queryMins = query.getMinValuesPerDimension(); final double[] queryMaxes = query.getMaxValuesPerDimension(); final double[] normalizedMins = new double[query.getDimensionCount()]; final double[] normalizedMaxes = new double[query.getDimensionCount()]; for (int d = 0; d < query.getDimensionCount(); d++) { normalizedMins[d] = dimensionDefs[d].normalize(queryMins[d]); normalizedMaxes[d] = dimensionDefs[d].normalize(queryMaxes[d]); } final byte[] minZorder = ZOrderUtils.encode( normalizedMins, cardinalityPerDimension, query.getDimensionCount()); final byte[] maxZorder = ZOrderUtils.encode( normalizedMaxes, cardinalityPerDimension, query.getDimensionCount()); return new RangeDecomposition( new ByteArrayRange[] { new ByteArrayRange( new ByteArrayId( minZorder), new ByteArrayId( maxZorder)) }); } /*** * {@inheritDoc} */ @Override public RangeDecomposition decomposeRangeFully( final MultiDimensionalNumericData query ) { return decomposeRange( query, true, -1); } @Override public byte[] toBinary() { final List<byte[]> dimensionDefBinaries = new ArrayList<byte[]>( dimensionDefs.length); int bufferLength = 4; for (final SFCDimensionDefinition sfcDimension : dimensionDefs) { final byte[] sfcDimensionBinary = PersistenceUtils.toBinary(sfcDimension); bufferLength += (sfcDimensionBinary.length + 4); dimensionDefBinaries.add(sfcDimensionBinary); } final ByteBuffer buf = ByteBuffer.allocate(bufferLength); buf.putInt(dimensionDefs.length); for (final byte[] dimensionDefBinary : dimensionDefBinaries) { buf.putInt(dimensionDefBinary.length); buf.put(dimensionDefBinary); } return buf.array(); } @Override public void fromBinary( final byte[] bytes ) { final ByteBuffer buf = ByteBuffer.wrap(bytes); final int numDimensions = buf.getInt(); dimensionDefs = new SFCDimensionDefinition[numDimensions]; for (int i = 0; i < numDimensions; i++) { final byte[] dim = new byte[buf.getInt()]; buf.get(dim); dimensionDefs[i] = PersistenceUtils.fromBinary( dim, SFCDimensionDefinition.class); } init(dimensionDefs); } @Override public int hashCode() { final int prime = 31; int result = 1; final String className = getClass().getName(); result = (prime * result) + ((className == null) ? 0 : className.hashCode()); result = (prime * result) + Arrays.hashCode(dimensionDefs); return result; } @Override public boolean equals( final Object obj ) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final ZOrderSFC other = (ZOrderSFC) obj; if (!Arrays.equals( dimensionDefs, other.dimensionDefs)) { return false; } return true; } @Override public long[] normalizeRange( final double minValue, final double maxValue, final int d ) { return new long[] { (long) (dimensionDefs[d].normalize(minValue) * binsPerDimension), (long) (dimensionDefs[d].normalize(maxValue) * binsPerDimension) }; } }