├── example └── java │ ├── .gitignore │ ├── pom.xml │ └── src │ ├── test │ └── java │ │ └── com │ │ └── joekarl │ │ └── binaryQuadkey │ │ ├── StringQuadkeyTest.java │ │ └── BinaryQuadkeyTest.java │ └── main │ └── java │ └── com │ └── joekarl │ └── binaryQuadkey │ ├── StringQuadkey.java │ ├── Main.java │ └── BinaryQuadkey.java └── README.md /example/java/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | target 3 | *.iml 4 | -------------------------------------------------------------------------------- /example/java/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.joekarl 4 | binaryQuadkey 5 | 0.0.0 6 | 7 | 8 | 9 | 10 | org.apache.maven.plugins 11 | maven-shade-plugin 12 | 2.3 13 | 14 | 15 | package 16 | 17 | shade 18 | 19 | 20 | 21 | 22 | com.joekarl.binaryQuadkey.Main 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | maven-compiler-plugin 31 | 32 | 1.7 33 | 1.7 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | junit 42 | junit 43 | 4.12 44 | test 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/java/src/test/java/com/joekarl/binaryQuadkey/StringQuadkeyTest.java: -------------------------------------------------------------------------------- 1 | package com.joekarl.binaryQuadkey; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | /** 7 | * Created by karl on 5/10/15. 8 | */ 9 | public class StringQuadkeyTest { 10 | @Test 11 | public void createsFromTileXYCorrectly() { 12 | String expectedQuadkey = "0231121"; 13 | int x = 29, y = 50, zoomLevel = 7; 14 | String result = StringQuadkey.fromTileXY(x, y, zoomLevel); 15 | System.out.println(expectedQuadkey); 16 | System.out.println(result); 17 | Assert.assertEquals(expectedQuadkey, result); 18 | } 19 | 20 | @Test 21 | public void createsFromTileXYCorrectly2() { 22 | String expectedQuadkey = "1202102332221212"; 23 | int x = 35210, y = 21493, zoomLevel = 16; 24 | String result = StringQuadkey.fromTileXY(x, y, zoomLevel); 25 | System.out.println(expectedQuadkey); 26 | System.out.println(result); 27 | Assert.assertEquals(expectedQuadkey, result); 28 | } 29 | 30 | @Test 31 | public void createsFromLonLatCorrectly() { 32 | String expectedQuadkey = "02123022310022332"; 33 | double lon = -122.676537, lat = 45.523007; 34 | int zoomLevel = 17; 35 | String result = StringQuadkey.fromLonLat(lon, lat, zoomLevel); 36 | System.out.println(expectedQuadkey); 37 | System.out.println(result); 38 | Assert.assertEquals(expectedQuadkey, result); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/java/src/main/java/com/joekarl/binaryQuadkey/StringQuadkey.java: -------------------------------------------------------------------------------- 1 | package com.joekarl.binaryQuadkey; 2 | 3 | /** 4 | * Created by karl on 5/10/15. 5 | */ 6 | public class StringQuadkey { 7 | 8 | public static String fromTileXY(int x, int y, int zoomLevel) { 9 | char[] quadkeyChars = new char[zoomLevel]; 10 | for (int i = zoomLevel; i > 0; --i) { 11 | char digit = '0'; 12 | final int mask = 1 << (i - 1); 13 | if ((x & mask) != 0) { 14 | digit++; 15 | } 16 | if ((y & mask) != 0) { 17 | digit++; 18 | digit++; 19 | } 20 | quadkeyChars[zoomLevel - i] = digit; 21 | } 22 | return new String(quadkeyChars); 23 | } 24 | 25 | // via http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Java 26 | public static String fromLonLat(double lon, double lat, int zoomLevel) { 27 | int xtile = (int) Math.floor( (lon + 180) / 360 * (1 << zoomLevel) ) ; 28 | int ytile = (int) Math.floor( (1 - Math.log(Math.tan(Math.toRadians(lat)) + 1 / Math.cos(Math.toRadians(lat))) / Math.PI) / 2 * (1 << zoomLevel) ) ; 29 | if (xtile < 0) { 30 | xtile = 0; 31 | } 32 | if (xtile >= (1 << zoomLevel)) { 33 | xtile = ((1 << zoomLevel) - 1); 34 | } 35 | if (ytile < 0) { 36 | ytile = 0; 37 | } 38 | if (ytile >= (1 << zoomLevel)) { 39 | ytile = ((1 << zoomLevel) - 1); 40 | } 41 | return fromTileXY(xtile, ytile, zoomLevel); 42 | } 43 | 44 | public static boolean isParentOf(String parentQk, String childQk) { 45 | int childZoomLevel = childQk.length(); 46 | int parentZoomLevel = parentQk.length(); 47 | if (childZoomLevel <= parentZoomLevel) { 48 | // must be at least one zoom level higher 49 | return false; 50 | } 51 | return childQk.startsWith(parentQk); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /example/java/src/main/java/com/joekarl/binaryQuadkey/Main.java: -------------------------------------------------------------------------------- 1 | package com.joekarl.binaryQuadkey; 2 | 3 | import java.util.*; 4 | 5 | public class Main { 6 | 7 | static final long SEED = 8972347823904L; 8 | static final long DIFF_SEED = 8972347823902354L; 9 | static final int KEY_LENGTH = 15; 10 | static final int NUM_KEYS = 10000000; 11 | 12 | public Main() { 13 | long binaryStart, binaryEnd, stringStart, stringEnd; 14 | 15 | // just warm up the jvm 16 | for (int i = 0; i < 10000; ++i) { 17 | genBinaryQuadkeys(100, 15, SEED); 18 | genStringQuadkeys(100, 15, SEED); 19 | } 20 | 21 | String parentQk = "01030212"; 22 | long parentBinaryQk = BinaryQuadkey.fromString(parentQk); 23 | String equalityQk = "012301230123012"; 24 | long equalityBinaryQk = BinaryQuadkey.fromString(equalityQk); 25 | 26 | boolean equality; 27 | boolean parent; 28 | 29 | long[] binaryQks = genBinaryQuadkeys(NUM_KEYS, KEY_LENGTH, SEED); 30 | binaryStart = System.currentTimeMillis(); 31 | equality = false; 32 | parent = false; 33 | for (long qk : binaryQks) { 34 | parent = BinaryQuadkey.isParentOf(parentBinaryQk, qk); 35 | } 36 | for (long qk : binaryQks) { 37 | equality = qk == equalityBinaryQk; 38 | } 39 | 40 | binaryEnd = System.currentTimeMillis(); 41 | 42 | System.out.println("printing equality/parent so it won't be optimized out " + equality + parent); 43 | 44 | String[] stringQks = genStringQuadkeys(NUM_KEYS, KEY_LENGTH, SEED); 45 | stringStart = System.currentTimeMillis(); 46 | equality = false; 47 | parent = false; 48 | for (String qk : stringQks) { 49 | parent = StringQuadkey.isParentOf(parentQk, qk); 50 | } 51 | for (String qk : stringQks) { 52 | equality = qk.equals(equalityQk); 53 | } 54 | stringEnd = System.currentTimeMillis(); 55 | 56 | System.out.println("printing equality/parent so it won't be optimized out " + equality + parent); 57 | 58 | System.out.println("Binary time: " + (binaryEnd - binaryStart)); 59 | System.out.println("String time: " + (stringEnd - stringStart)); 60 | } 61 | 62 | long[] genBinaryQuadkeys(int n, int zoomLevel, long seed) { 63 | Random r = new Random(seed); 64 | long[] quadkeys = new long[n]; 65 | for (int i = 0; i < n; ++i) { 66 | double lon = r.nextDouble(); 67 | double lat = r.nextDouble(); 68 | quadkeys[i] = BinaryQuadkey.fromLonLat(lon, lat, zoomLevel); 69 | } 70 | return quadkeys; 71 | } 72 | 73 | String[] genStringQuadkeys(int n, int zoomLevel, long seed) { 74 | Random r = new Random(seed); 75 | String[] quadkeys = new String[n]; 76 | for (int i = 0; i < n; ++i) { 77 | double lon = r.nextDouble(); 78 | double lat = r.nextDouble(); 79 | quadkeys[i] = StringQuadkey.fromLonLat(lon, lat, zoomLevel); 80 | } 81 | return quadkeys; 82 | } 83 | 84 | public static void main(String ... args) { 85 | new Main(); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /example/java/src/main/java/com/joekarl/binaryQuadkey/BinaryQuadkey.java: -------------------------------------------------------------------------------- 1 | package com.joekarl.binaryQuadkey; 2 | 3 | public class BinaryQuadkey { 4 | private static final long[] PREFIX_MASKS = { 5 | 0b0000000000000000000000000000000000000000000000000000000000000000L, 6 | 0b1100000000000000000000000000000000000000000000000000000000000000L, 7 | 0b1111000000000000000000000000000000000000000000000000000000000000L, 8 | 0b1111110000000000000000000000000000000000000000000000000000000000L, 9 | 0b1111111100000000000000000000000000000000000000000000000000000000L, 10 | 0b1111111111000000000000000000000000000000000000000000000000000000L, 11 | 0b1111111111110000000000000000000000000000000000000000000000000000L, 12 | 0b1111111111111100000000000000000000000000000000000000000000000000L, 13 | 0b1111111111111111000000000000000000000000000000000000000000000000L, 14 | 0b1111111111111111110000000000000000000000000000000000000000000000L, 15 | 0b1111111111111111111100000000000000000000000000000000000000000000L, 16 | 0b1111111111111111111111000000000000000000000000000000000000000000L, 17 | 0b1111111111111111111111110000000000000000000000000000000000000000L, 18 | 0b1111111111111111111111111100000000000000000000000000000000000000L, 19 | 0b1111111111111111111111111111000000000000000000000000000000000000L, 20 | 0b1111111111111111111111111111110000000000000000000000000000000000L, 21 | 0b1111111111111111111111111111111100000000000000000000000000000000L, 22 | 0b1111111111111111111111111111111111000000000000000000000000000000L, 23 | 0b1111111111111111111111111111111111110000000000000000000000000000L, 24 | 0b1111111111111111111111111111111111111100000000000000000000000000L, 25 | 0b1111111111111111111111111111111111111111000000000000000000000000L, 26 | 0b1111111111111111111111111111111111111111110000000000000000000000L, 27 | 0b1111111111111111111111111111111111111111111100000000000000000000L, 28 | 0b1111111111111111111111111111111111111111111111000000000000000000L 29 | }; 30 | 31 | 32 | public static long fromTileXY(int x, int y, int zoomLevel) { 33 | long binaryQuadkey = 0L; 34 | for (int i = zoomLevel; i > 0; --i) { 35 | int mask = 1 << (i - 1); 36 | long bitLocation = (64 - ((zoomLevel - i + 1) * 2) + 1); 37 | if ((x & mask) != 0) { 38 | binaryQuadkey |= 0b1L << (bitLocation - 1); 39 | } 40 | if ((y & mask) != 0) { 41 | binaryQuadkey |= 0b1L << bitLocation; 42 | } 43 | } 44 | binaryQuadkey |= (long)zoomLevel; 45 | return binaryQuadkey; 46 | } 47 | 48 | // via http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Java 49 | public static long fromLonLat(double lon, double lat, int zoomLevel) { 50 | int xtile = (int) Math.floor( (lon + 180) / 360 * (1 << zoomLevel) ) ; 51 | int ytile = (int) Math.floor( (1 - Math.log(Math.tan(Math.toRadians(lat)) + 1 / Math.cos(Math.toRadians(lat))) / Math.PI) / 2 * (1 << zoomLevel) ) ; 52 | if (xtile < 0) { 53 | xtile = 0; 54 | } 55 | if (xtile >= (1 << zoomLevel)) { 56 | xtile = ((1 << zoomLevel) - 1); 57 | } 58 | if (ytile < 0) { 59 | ytile = 0; 60 | } 61 | if (ytile >= (1 << zoomLevel)) { 62 | ytile = ((1 << zoomLevel) - 1); 63 | } 64 | return fromTileXY(xtile, ytile, zoomLevel); 65 | } 66 | 67 | public static long fromString(String qkString) { 68 | long zoomLevel = qkString.length(); 69 | long binaryQuadkey = 0L; 70 | for (int i = 0; i < zoomLevel; ++i) { 71 | long bitLocation = (64 - ((i + 1) * 2)); 72 | char bitChar = qkString.charAt(i); 73 | binaryQuadkey |= ((long) Character.getNumericValue(bitChar)) << bitLocation; 74 | } 75 | binaryQuadkey |= zoomLevel; 76 | return binaryQuadkey; 77 | } 78 | 79 | public static String toString(long binaryQk) { 80 | int zoomLevel = extractZoomLevel(binaryQk); 81 | char[] quadkeyChars = new char[zoomLevel]; 82 | for (int i = 0; i < zoomLevel; ++i) { 83 | long bitLocation = (64 - ((i + 1) * 2)); 84 | int charBits = (int)((binaryQk & (0b11L << bitLocation)) >> bitLocation); 85 | quadkeyChars[i] = Character.forDigit(charBits, 10); 86 | } 87 | return new String(quadkeyChars); 88 | } 89 | 90 | public static int extractZoomLevel(long binaryQk) { 91 | return (int) (binaryQk & 0b11111L); 92 | } 93 | 94 | public static boolean isParentOf(long parentQk, long childQk) { 95 | int childZoomLevel = extractZoomLevel(childQk); 96 | int parentZoomLevel = extractZoomLevel(parentQk); 97 | if (childZoomLevel <= parentZoomLevel) { 98 | // must be at least one zoom level higher 99 | return false; 100 | } 101 | long mask = PREFIX_MASKS[parentZoomLevel]; 102 | return (parentQk & mask) == (childQk & mask); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /example/java/src/test/java/com/joekarl/binaryQuadkey/BinaryQuadkeyTest.java: -------------------------------------------------------------------------------- 1 | package com.joekarl.binaryQuadkey; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class BinaryQuadkeyTest { 7 | 8 | @Test 9 | public void createsFromTileXYCorrectly() { 10 | long expectedBinaryQuadkey = 0b0010110101100100000000000000000000000000000000000000000000000111L; 11 | int x = 29, y = 50, zoomLevel = 7; 12 | long result = BinaryQuadkey.fromTileXY(x, y, zoomLevel); 13 | System.out.println(Long.toBinaryString(expectedBinaryQuadkey)); 14 | System.out.println(Long.toBinaryString(result)); 15 | Assert.assertEquals(expectedBinaryQuadkey, result); 16 | } 17 | 18 | @Test 19 | public void createsFromTileXYCorrectly2() { 20 | long expectedBinaryQuadkey = BinaryQuadkey.fromString("1202102332221212"); 21 | int x = 35210, y = 21493, zoomLevel = 16; 22 | long result = BinaryQuadkey.fromTileXY(x, y, zoomLevel); 23 | System.out.println(Long.toBinaryString(expectedBinaryQuadkey)); 24 | System.out.println(Long.toBinaryString(result)); 25 | Assert.assertEquals(expectedBinaryQuadkey, result); 26 | } 27 | 28 | @Test 29 | public void createsFromLonLatCorrectly() { 30 | long expectedBinaryQuadkey = BinaryQuadkey.fromString("02123022310022332"); 31 | double lon = -122.676537, lat = 45.523007; 32 | int zoomLevel = 17; 33 | long result = BinaryQuadkey.fromLonLat(lon, lat, zoomLevel); 34 | System.out.println(Long.toBinaryString(expectedBinaryQuadkey)); 35 | System.out.println(Long.toBinaryString(result)); 36 | Assert.assertEquals(expectedBinaryQuadkey, result); 37 | } 38 | 39 | @Test 40 | public void parsesQuadkeyString() { 41 | long expectedBinaryQuadkey = 0b0010010011100001110010011011001000000000000000000000000000010000L; 42 | String quadkey = "0210320130212302"; 43 | long result = BinaryQuadkey.fromString(quadkey); 44 | System.out.println(Long.toBinaryString(expectedBinaryQuadkey)); 45 | System.out.println(Long.toBinaryString(result)); 46 | Assert.assertEquals(expectedBinaryQuadkey, result); 47 | } 48 | 49 | @Test 50 | public void parsesQuadkeyString2() { 51 | long expectedBinaryQuadkey = 0b0010000000000000000000000000000000000000000000000000000000000010L; 52 | String quadkey = "02"; 53 | long result = BinaryQuadkey.fromString(quadkey); 54 | System.out.println(Long.toBinaryString(expectedBinaryQuadkey)); 55 | System.out.println(Long.toBinaryString(result)); 56 | Assert.assertEquals(expectedBinaryQuadkey, result); 57 | } 58 | 59 | @Test 60 | public void parsesQuadkeyString3() { 61 | long expectedBinaryQuadkey = 0b0000000000000000000000000000000000000000000000000000000000000001L; 62 | String quadkey = "0"; 63 | long result = BinaryQuadkey.fromString(quadkey); 64 | System.out.println(Long.toBinaryString(expectedBinaryQuadkey)); 65 | System.out.println(Long.toBinaryString(result)); 66 | Assert.assertEquals(expectedBinaryQuadkey, result); 67 | } 68 | 69 | @Test 70 | public void quadkeyToBinaryQuadkey() { 71 | String expectedQuadkeyString = "0210320130212302"; 72 | long binaryQuadkey = 0b0010010011100001110010011011001000000000000000000000000000010000L; 73 | String result = BinaryQuadkey.toString(binaryQuadkey); 74 | System.out.println(expectedQuadkeyString); 75 | System.out.println(result); 76 | Assert.assertEquals(expectedQuadkeyString, result); 77 | } 78 | 79 | @Test 80 | public void extractsZoomLevel() { 81 | int expectedZoomLevel = 16; 82 | long binaryQuadkey = 0b0010010011100001110010011011001000000000000000000000000000010000L; 83 | int result = BinaryQuadkey.extractZoomLevel(binaryQuadkey); 84 | Assert.assertEquals(expectedZoomLevel, result); 85 | } 86 | 87 | @Test 88 | public void prefexChecking_identifiesParent() { 89 | String qkA = "0210320130212302"; 90 | String qkB = "02103201"; 91 | long binaryQkA = BinaryQuadkey.fromString(qkA); 92 | long binaryQkB = BinaryQuadkey.fromString(qkB); 93 | Assert.assertTrue(BinaryQuadkey.isParentOf(binaryQkB, binaryQkA)); 94 | } 95 | 96 | @Test 97 | public void prefexChecking_identifiesNonParent() { 98 | String qkA = "0210320130212302"; 99 | String qkB = "02103202"; 100 | long binaryQkA = BinaryQuadkey.fromString(qkA); 101 | long binaryQkB = BinaryQuadkey.fromString(qkB); 102 | Assert.assertFalse(BinaryQuadkey.isParentOf(binaryQkB, binaryQkA)); 103 | } 104 | 105 | @Test 106 | public void prefixChecking_childBiggerThanParent() { 107 | String qkA = "0210320130212302"; 108 | String qkB = "02103201"; 109 | long binaryQkA = BinaryQuadkey.fromString(qkA); 110 | long binaryQkB = BinaryQuadkey.fromString(qkB); 111 | Assert.assertFalse(BinaryQuadkey.isParentOf(binaryQkA, binaryQkB)); 112 | } 113 | 114 | @Test 115 | public void prefixChecking_childSameSizeAsParent() { 116 | String qkA = "0210320130212302"; 117 | String qkB = "0210320130212302"; 118 | long binaryQkA = BinaryQuadkey.fromString(qkA); 119 | long binaryQkB = BinaryQuadkey.fromString(qkB); 120 | Assert.assertFalse(BinaryQuadkey.isParentOf(binaryQkA, binaryQkB)); 121 | } 122 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Binary quadkeys 2 | 3 | As per the Microsoft documentation (https://msdn.microsoft.com/en-us/library/bb259689.aspx), 4 | quadkeys are generally implemented as a string displaying what position in the bing tile system 5 | a the quadkey resides in. This is great because you have an easily human readable geohash that 6 | allows prefix searching to determine whether a higher zoom level quadkey resides inside of a lower 7 | level quadkey. Searching like this is fairly optimized. 8 | 9 | Unfortunately, this means that a quadkey takes up a lot of space (due to it being a string). 10 | In particular, at higher zoom levels this space can lead to wasted space and performance 11 | problems when comparing quadkeys to each other. 12 | 13 | Due to these problems, it would be nice to have a format that exhibits the same benefits of a 14 | quadkey (prefix searching, geohash) but without the space or performance constraints. 15 | 16 | It turns out, because a quadkey has an upper bound on length (23 characters) we can encode a 17 | quadkey into a unsigned 64bit integer. Such an encoding would require packing the quadkey's zoom level into 18 | the integer to preserve the notion of the length of the quadkey. Also because each quadkey is just 19 | an 64 bits, space will be conserved in cases above zoom level 8. Prefix matching can be 20 | acheived as well by doing bitmasking and equality checking (leading to constant time comparisons 21 | instead of linear time comparisons). 22 | 23 | ## Format 24 | 25 | A binary quadkey should be encoded as follows: 26 | * The first (zoom level * 2) bits are the quadkey portion 27 | * Every 2 bits in the quadkey portion represent one character of the quadkey 28 | * The final 5 bits in the number represent the zoom level of the quadkey 29 | * The final 5 bits must be >= 1 and <= 23 30 | * The remaining bits are not used for anything and are undefined 31 | 32 | ``` 33 | quadkey '03120312' 34 | binary quadkey 0b0011011000110110 0000000000000000000000000000000000000000000 01000 35 | | | | 36 | | quadkey bits | undefined bits | zoom level 8 37 | 38 | normal quadkey space = 64 bits (sizeof(char) == 1) 39 | binary quadkey space = 64 bits 40 | ``` 41 | 42 | ``` 43 | quadkey '02' 44 | binary quadkey 0b0010 0000000000000000000000000000000000000000000000000000000 00010 45 | | | | 46 | | | undefined bits | zoom level 2 47 | 48 | normal quadkey space = 16 bits (sizeof(char) == 1) 49 | binary quadkey space = 64 bits 50 | ``` 51 | 52 | ``` 53 | quadkey '0210320130212302' 54 | binary quadkey 0b00100100111000011100100110110010 000000000000000000000000000 10000 55 | | | | 56 | | quadkey bits | undefined bits | zoom level 16 57 | 58 | normal quadkey space = 128 bits (sizeof(char) == 1) 59 | binary quadkey space = 64 bits 60 | ``` 61 | 62 | ## Space comparison 63 | 64 | Assumes sizeof(char) == 1, for other char encodings muliply string size by sizeof(char) 65 | 66 | * The largest string quadkey (zoom level 23) will take up 23 x 8 bits = 184 bits 67 | * The largest binary quadkey will take up 64 bits 68 | * The largest binary quadkey in decimal is the number `18446744073709289495` which even as a 69 | string is only 20 x 8 bits = 160 bits (though in practice a string version of a binary quadkey 70 | would not be transferred or stored) 71 | 72 | * 500 zoom level 15 string quadkeys = 500 x 15 x 8 bits = 7.5 kb 73 | * 500 zoom level 15 binary quadkeys = 500 x 64 bits = 4 kb 74 | 75 | * 500 zoom level 20 string quadkeys = 500 x 20 x 8 bits = 10 kb 76 | * 500 zoom level 20 binary quadkeys = 500 x 64 bits = 4 kb 77 | 78 | * 3,000,000 zoom level 15 string quadkeys = 3,000,000 x 15 x 8 bits = 360 mb 79 | * 3,000,000 zoom level 15 binary quadkeys = 3,000,000 x 64 bits = 192 mb 80 | 81 | ## Comparing binary quadkeys 82 | 83 | ### Same zoom level 84 | 85 | For same zoom level quadkeys, a simple integer comparison can be done `qkA == qkB`. 86 | 87 | ### Prefix comparison 88 | 89 | To check to see if a quadkey (`qkA`) is contained by another quadkey (`qkB`) 90 | * Extract the zoom level via bitmask `qkBZoomLevel = qkB & 0b11111` 91 | * Convert both quadkeys to prefix `qkPrefixA = qkA >> 64 - qkBZoomLevel` `qkPrefixB = qkB >> 64 - qkBZoomLevel` 92 | * Compare the prefixes `qkPrefixA == qkPrefixB` 93 | 94 | ## Binary quadkey to string quadkey 95 | 96 | ``` 97 | binary_qk = 2417097 // 0b00100100111000011100100110110010 000000000000000000000000000 10000 98 | zoom_level = binary_qk & 0b11111 //16 99 | str_quadkey = "" 100 | for i = 0; i <= zoom_level; i++ { 101 | // extract each qk digit 102 | location = 64 - i * 2 103 | char_code = (binary_qk & (0b11 << location) >> location) 104 | str_quadkey += (char)(48 + char_code) 105 | } 106 | ``` 107 | 108 | ## Pros vs string quadkeys 109 | 110 | * Consistent storage for all quadkeys, always 64 bits 111 | * No dynamic memory storage for quadkeys (super important when dealing with cpu cache efficiency) 112 | * Comparisons can be made in constant time O(1) vs linear time O(n) for string quadkeys 113 | * Faster iteration of a list of quadkeys due to simple array storage and cache optimizations 114 | * Better indexing support in databases for integers vs strings 115 | * Most applications use UTF-8 instead of ASCII encoding which can result in 2x-4x more bloat 116 | for string quadkeys than what is listed in the space comparison section (due to chars being 2 117 | bytes or 4 bytes in UTF-8 or other encodings) **Note** also applicable to languages that default 118 | to UTF-8 string such as Java, Go, or Python 3, Javascript 119 | 120 | ## Cons vs string quadkeys 121 | 122 | * More complicated 123 | * Space wasted for zoom levels < 8 124 | * Languages with no 64bit integers cannot be represented natively (ie Javascript, well, it's 125 | kindof supported but not really...) 126 | * Lose disk locality if storing information via quadkey (nearby quadkeys are no longer nearby on 127 | disk) 128 | 129 | ## Uses 130 | 131 | * Storage or transmission of large amounts of high zoom level quadkeys (ie individual locations 132 | or a set of quadkeys representing a geometric area at high zoom level) 133 | * Speedy comparison of large amounts of quadkeys 134 | * Speedy iteration of large numbers of quadkeys 135 | --------------------------------------------------------------------------------