package me.osm.gazetter.utils; /** * Locality preserving hashing. * {@link http://blog.notdot.net/2009/11/Damn-Cool-Algorithms-Spatial-indexing-with-Quadtrees-and-Hilbert-Curves} * */ public class HilbertCurveHasher { private static final long FFFFFFFF = 4294967295l; private static final int A = 0; private static final int B = 1; private static final int C = 2; private static final int D = 3; private static final int hilbert_map[][][][] = new int[4][2][2][2]; private static final int inverse_hilbert_map[][][] = new int[4][4][3]; static { // 'a': {(0, 0): (0, 'd'), (0, 1): (1, 'a'), (1, 0): (3, 'b'), (1, 1): (2, 'a')}, // 'b': {(0, 0): (2, 'b'), (0, 1): (1, 'b'), (1, 0): (3, 'a'), (1, 1): (0, 'c')}, // 'c': {(0, 0): (2, 'c'), (0, 1): (3, 'd'), (1, 0): (1, 'c'), (1, 1): (0, 'b')}, // 'd': {(0, 0): (0, 'a'), (0, 1): (3, 'c'), (1, 0): (1, 'd'), (1, 1): (2, 'd')} hilbert_map[A] = new int[][][]{{/*(0, 0):*/{0, D}, /*(0, 1):*/{1, A}}, {/*(1, 0):*/{3, B}, /*(1, 1):*/{2, A}}}; hilbert_map[B] = new int[][][]{{/*(0, 0):*/{2, B}, /*(0, 1):*/{1, B}}, {/*(1, 0):*/{3, A}, /*(1, 1):*/{0, C}}}; hilbert_map[C] = new int[][][]{{/*(0, 0):*/{2, C}, /*(0, 1):*/{3, D}}, {/*(1, 0):*/{1, C}, /*(1, 1):*/{0, B}}}; hilbert_map[D] = new int[][][]{{/*(0, 0):*/{0, A}, /*(0, 1):*/{3, C}}, {/*(1, 0):*/{1, D}, /*(1, 1):*/{2, D}}}; // 'a': {0: (0, 0, 'd'), 1: (0, 1, 'a'), 2: (1, 1, 'a'), 3: (1, 0, 'b')}, // 'b': {0: (1, 1, 'c'), 1: (0, 1, 'b'), 2: (0, 0, 'b'), 3: (1, 0, 'a')}, // 'c': {0: (1, 1, 'b'), 1: (1, 0, 'c'), 2: (0, 0, 'c'), 3: (0, 1, 'd')}, // 'd': {0: (0, 0, 'a'), 1: (1, 0, 'd'), 2: (1, 1, 'd'), 3: (0, 1, 'c')} inverse_hilbert_map[A] = new int[][]{{0, 0, D}, {0, 1, A}, {1, 1, A}, {1, 0, B}}; inverse_hilbert_map[B] = new int[][]{{1, 1, C}, {0, 1, B}, {0, 0, B}, {1, 0, A}}; inverse_hilbert_map[C] = new int[][]{{1, 1, B}, {1, 0, C}, {0, 0, C}, {0, 1, D}}; inverse_hilbert_map[D] = new int[][]{{0, 0, A}, {1, 0, D}, {1, 1, D}, {0, 1, C}}; } /** * Converts pair of coordinates into long HilbertCurve hash. * with depth 16 (32 bits of resulting hash) * */ public static long encode(double x, double y) { int[] intCoordinates = intCoordinates(x, y); return encode(intCoordinates[0], intCoordinates[1]); } /** * Converts long HilbertCurve hash into pair of coordinates. * */ public static double[] decode(long hash) { long[] decode = decodeL(hash); return doubleCoordinates(decode[0], decode[1]); } /** * Converts pair of coordinates into long HilbertCurve hash. * with depth 16 (32 bits of resulting hash) * */ public static long encode(long x, long y) { return encode(x, y, 16); } /** * Decodes hash into binary coordinates. * */ public static long[] decodeL(long hash) { return decode(hash, 16); } /** * Encodes pair of long x,y with provided HilbertHash algorithm order. * */ public static long encode(long x, long y, int order) { int currentSquare = A; long position = 0; for(int i = order - 1; i >= 0; i--) { position <<= 2; int quadX = (x & (1 << i)) == 0 ? 0 : 1; int quadY = (y & (1 << i)) == 0 ? 0 : 1; int quadPosition = hilbert_map[currentSquare][quadX][quadY][0]; currentSquare = hilbert_map[currentSquare][quadX][quadY][1]; position |= quadPosition; } return position; } /** * Decode long HilbertCurve hash into [x, y] long coordinates array * with given HilbertCurve algorithm order * */ public static long[] decode(long hash, int order) { int position = A; long x = 0; long y = 0; for(int i = order - 1; i >=0; i--) { int p = (int) ((hash >> (i * 2)) & 3); int quadX = inverse_hilbert_map[position][p][0]; int quadY = inverse_hilbert_map[position][p][1]; position = inverse_hilbert_map[position][p][2]; x = quadX == 0 ? x : (x | (1 << i)); y = quadY == 0 ? y : (y | (1 << i)); } return new long[]{x, y}; } /** * Convert double x, y into int [x, y] * */ private static int[] intCoordinates(double x, double y) { return new int[]{ (int) (new Double((x + 180.0) * FFFFFFFF / 360.0).longValue() & FFFFFFFF), (int) (new Double((y + 90.0) * FFFFFFFF / 180.0).longValue() & FFFFFFFF) }; } /** * Convert long x, y into double [x, y] * */ private static double[] doubleCoordinates(long x, long y) { return new double[]{ (360.0d * x / 0xFFFFFFFF) - 180.0, (180.0d * y / 0xFFFFFFFF) - 90.0 }; } }