package mil.nga.giat.geowave.core.index.sfc.zorder; import java.util.Arrays; import java.util.BitSet; import mil.nga.giat.geowave.core.index.sfc.SFCDimensionDefinition; import mil.nga.giat.geowave.core.index.sfc.data.NumericRange; /** * Convenience methods used to decode/encode Z-Order space filling curve values * (using a simple bit-interleaving approach). * */ public class ZOrderUtils { public static NumericRange[] decodeRanges( final byte[] bytes, final int bitsPerDimension, final SFCDimensionDefinition[] dimensionDefinitions ) { final byte[] littleEndianBytes = swapEndianFormat(bytes); final BitSet bitSet = BitSet.valueOf(littleEndianBytes); final NumericRange[] normalizedValues = new NumericRange[dimensionDefinitions.length]; for (int d = 0; d < dimensionDefinitions.length; d++) { final BitSet dimensionSet = new BitSet(); int j = 0; for (int i = d; i < (bitsPerDimension * dimensionDefinitions.length); i += dimensionDefinitions.length) { dimensionSet.set( j++, bitSet.get(i)); } normalizedValues[d] = decode( dimensionSet, 0, 1, dimensionDefinitions[d]); } return normalizedValues; } public static long[] decodeIndices( final byte[] bytes, final int bitsPerDimension, final int numDimensions ) { final byte[] littleEndianBytes = swapEndianFormat(bytes); final BitSet bitSet = BitSet.valueOf(littleEndianBytes); final long[] coordinates = new long[numDimensions]; final long rangePerDimension = (long) Math.pow( 2, bitsPerDimension); for (int d = 0; d < numDimensions; d++) { final BitSet dimensionSet = new BitSet(); int j = 0; for (int i = d; i < (bitsPerDimension * numDimensions); i += numDimensions) { dimensionSet.set( j++, bitSet.get(i)); } coordinates[d] = decodeIndex( dimensionSet, rangePerDimension); } return coordinates; } private static long decodeIndex( final BitSet bs, final long rangePerDimension ) { long floor = 0; long ceiling = rangePerDimension; long mid = 0; for (int i = 0; i < bs.length(); i++) { mid = (floor + ceiling) / 2; if (bs.get(i)) { floor = mid; } else { ceiling = mid; } } return mid; } private static NumericRange decode( final BitSet bs, double floor, double ceiling, final SFCDimensionDefinition dimensionDefinition ) { double mid = 0; for (int i = 0; i < bs.length(); i++) { mid = (floor + ceiling) / 2; if (bs.get(i)) { floor = mid; } else { ceiling = mid; } } return new NumericRange( dimensionDefinition.denormalize(floor), dimensionDefinition.denormalize(ceiling)); } public static byte[] encode( final double[] normalizedValues, final int bitsPerDimension, final int numDimensions ) { final BitSet[] bitSets = new BitSet[numDimensions]; for (int d = 0; d < numDimensions; d++) { bitSets[d] = getBits( normalizedValues[d], 0, 1, bitsPerDimension); } int usedBits = bitsPerDimension * numDimensions; int usedBytes = (int) Math.ceil(usedBits / 8.0); int bitsetLength = (usedBytes * 8); int bitOffset = bitsetLength - usedBits; // round up to a bitset divisible by 8 final BitSet combinedBitSet = new BitSet( bitsetLength); int j = bitOffset; for (int i = 0; i < bitsPerDimension; i++) { for (int d = 0; d < numDimensions; d++) { combinedBitSet.set( j++, bitSets[d].get(i)); } } final byte[] littleEndianBytes = combinedBitSet.toByteArray(); byte[] retVal = swapEndianFormat(littleEndianBytes); if (retVal.length < usedBytes) { return Arrays.copyOf( retVal, usedBytes); } return retVal; } public static byte[] swapEndianFormat( final byte[] b ) { final byte[] endianSwappedBytes = new byte[b.length]; for (int i = 0; i < b.length; i++) { endianSwappedBytes[i] = swapEndianFormat(b[i]); } return endianSwappedBytes; } private static byte swapEndianFormat( final byte b ) { int converted = 0x00; converted ^= (b & 0b1000_0000) >> 7; converted ^= (b & 0b0100_0000) >> 5; converted ^= (b & 0b0010_0000) >> 3; converted ^= (b & 0b0001_0000) >> 1; converted ^= (b & 0b0000_1000) << 1; converted ^= (b & 0b0000_0100) << 3; converted ^= (b & 0b0000_0010) << 5; converted ^= (b & 0b0000_0001) << 7; return (byte) (converted & 0xFF); } private static BitSet getBits( final double value, double floor, double ceiling, final int bitsPerDimension ) { final BitSet buffer = new BitSet( bitsPerDimension); for (int i = 0; i < bitsPerDimension; i++) { final double mid = (floor + ceiling) / 2; if (value >= mid) { buffer.set(i); floor = mid; } else { ceiling = mid; } } return buffer; } }