From 80aa9326d9926285e13b544c7d9ebd6730e01012 Mon Sep 17 00:00:00 2001 From: wenjj2000 Date: Mon, 4 Mar 2024 00:33:36 +0100 Subject: [PATCH 1/2] refactor : changing helper functions to private instead of public and removing their tests --- .../java/com/esri/core/geometry/Geohash.java | 25 +++-- .../com/esri/core/geometry/TestGeohash.java | 94 +++++++------------ 2 files changed, 48 insertions(+), 71 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/Geohash.java b/src/main/java/com/esri/core/geometry/Geohash.java index 739fda5e..6c0001bd 100644 --- a/src/main/java/com/esri/core/geometry/Geohash.java +++ b/src/main/java/com/esri/core/geometry/Geohash.java @@ -124,7 +124,7 @@ public static String toGeohash(Point2D pt, int characterLength) { * @return base32 string of the binStr in chunks of 5 binary digits */ - public static String binaryToBase32(String binStr) { + private static String binaryToBase32(String binStr) { StringBuilder base32Str = new StringBuilder(); for (int i = 0; i < binStr.length(); i += 5) { String chunk = binStr.substring(i, i + 5); @@ -142,7 +142,7 @@ public static String binaryToBase32(String binStr) { * @return A binary string representation of the value with the given range and precision */ - public static String convertToBinary( + private static String convertToBinary( double value, double[] r, int precision @@ -179,27 +179,26 @@ public static String containingGeohash(Envelope2D envelope) { double deltaLon = 360; double deltaLat = 180; - while(xmin == xmax && ymin == ymax && chars < 25){ - - if(chars%2 == 0){ + while (xmin == xmax && ymin == ymax && chars < 25) { + if (chars % 2 == 0) { deltaLon = deltaLon / 8; deltaLat = deltaLat / 4; - }else{ + } else { deltaLon = deltaLon / 4; deltaLat = deltaLat / 8; } - xmin = Math.floor(posMinX/deltaLon); - xmax = Math.floor(posMaxX/deltaLon); - ymin = Math.floor(posMinY/deltaLat); - ymax = Math.floor(posMaxY/deltaLat); + xmin = Math.floor(posMinX / deltaLon); + xmax = Math.floor(posMaxX / deltaLon); + ymin = Math.floor(posMinY / deltaLat); + ymax = Math.floor(posMaxY / deltaLat); chars++; } - if(chars == 1) return ""; - - return toGeohash(new Point2D(envelope.xmin, envelope.ymin), chars-1); + if (chars == 1) return ""; + + return toGeohash(new Point2D(envelope.xmin, envelope.ymin), chars - 1); } /** diff --git a/src/test/java/com/esri/core/geometry/TestGeohash.java b/src/test/java/com/esri/core/geometry/TestGeohash.java index 0788de1b..13ddd866 100644 --- a/src/test/java/com/esri/core/geometry/TestGeohash.java +++ b/src/test/java/com/esri/core/geometry/TestGeohash.java @@ -72,78 +72,56 @@ public void testGeohashToEnvelopeGoodDimensions2() { assertEquals(latDiff, env.ymax - env.ymin, delta); } - /** - * Check if BinaryToBase32 work as intended with a normal binary string - */ - @Test - public void testBinaryToBase32() { - String testStr = "011011111111011"; - assertEquals("ezv", Geohash.binaryToBase32(testStr)); - } - - @Test - public void testConvertToBinary() { - double lat = 40.7128; - double lon = -74.0060; - String latStr = Geohash.convertToBinary(lat, new double[] { -90, 90 }, 10); - String lonStr = Geohash.convertToBinary( - lon, - new double[] { -180, 180 }, - 10 - ); - - assertEquals("1011100111", latStr); - assertEquals("0100101101", lonStr); - } - @Test public void testToGeoHash() { - Point2D p1 = new Point2D(-4.329,48.669); + Point2D p1 = new Point2D(-4.329, 48.669); Point2D p2 = new Point2D(-30.382, 70.273); Point2D p3 = new Point2D(14.276, 37.691); + Point2D p4 = new Point2D(-143.923, 48.669); int chrLen = 5; String p1Hash = Geohash.toGeohash(p1, chrLen); String p2Hash = Geohash.toGeohash(p2, chrLen); String p3Hash = Geohash.toGeohash(p3, chrLen); + String p4Hash = Geohash.toGeohash(p4, chrLen); assertEquals("gbsuv", p1Hash); assertEquals("gk6ru", p2Hash); assertEquals("sqdnk", p3Hash); + assertEquals("bb9su", p4Hash); + } + + @Test + public void testToGeohashHasGoodPrecision() { + Point2D point = new Point2D(18.068581, 59.329323); + assertEquals(6, Geohash.toGeohash(point, 6).length()); } - @Test - public void testToGeohashHasGoodPrecision(){ - Point2D point = new Point2D(18.068581, 59.329323); - assertEquals(6, Geohash.toGeohash(point, 6).length()); - } - - @Test - public void testToGeohash2(){ - String expected = "u6sce"; - Point2D point = new Point2D(18.068581, 59.329323); - String geoHash = Geohash.toGeohash(point, 5); - - assertEquals(expected, geoHash); - } - - @Test - public void testContainingGeohashWithHugeValues(){ - Envelope2D envelope = new Envelope2D(-179, -89, 179, 89); - assertEquals("", Geohash.containingGeohash(envelope)); - } - - @Test - public void testContainingGeohash(){ - Envelope2D envelope = new Envelope2D(-179, -89, -140, -50); - assertEquals("0", Geohash.containingGeohash(envelope)); - } - - @Test - public void testContainingGeohash2(){ - Envelope2D envelope = new Envelope2D(18.078, 59.3564, 18.1,59.3344); - assertEquals("u6sce", Geohash.containingGeohash(envelope)); - } - + @Test + public void testToGeohash2() { + String expected = "u6sce"; + Point2D point = new Point2D(18.068581, 59.329323); + String geoHash = Geohash.toGeohash(point, 5); + + assertEquals(expected, geoHash); + } + + @Test + public void testContainingGeohashWithHugeValues() { + Envelope2D envelope = new Envelope2D(-179, -89, 179, 89); + assertEquals("", Geohash.containingGeohash(envelope)); + } + + @Test + public void testContainingGeohash() { + Envelope2D envelope = new Envelope2D(-179, -89, -140, -50); + assertEquals("0", Geohash.containingGeohash(envelope)); + } + + @Test + public void testContainingGeohash2() { + Envelope2D envelope = new Envelope2D(18.078, 59.3564, 18.1, 59.3344); + assertEquals("u6sce", Geohash.containingGeohash(envelope)); + } } From dcdb58d2fc8ad30d0a9fdfa2bc7be89f3790800b Mon Sep 17 00:00:00 2001 From: wenjj2000 Date: Mon, 4 Mar 2024 07:52:31 +0100 Subject: [PATCH 2/2] refactor : Changed toGeoHash function to use bitwise operations instead of strings to improve time complexity --- .../java/com/esri/core/geometry/Geohash.java | 67 +++++++++++-------- .../com/esri/core/geometry/TestGeohash.java | 8 +++ 2 files changed, 48 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/esri/core/geometry/Geohash.java b/src/main/java/com/esri/core/geometry/Geohash.java index 6c0001bd..3da9360d 100644 --- a/src/main/java/com/esri/core/geometry/Geohash.java +++ b/src/main/java/com/esri/core/geometry/Geohash.java @@ -91,74 +91,87 @@ public static String toGeohash(Point2D pt, int characterLength) { "CharacterLength cannot be less than 1" ); } + if (characterLength > 6) { + throw new InvalidParameterException("Max characterLength of 6"); + } int precision = characterLength * 5; double lat = pt.y; double lon = pt.x; - - String latBitStr = Geohash.convertToBinary( + long latBit = Geohash.convertToBinary( lat, new double[] { -90, 90 }, precision ); - String lonBitStr = Geohash.convertToBinary( + long lonBit = Geohash.convertToBinary( lon, new double[] { -180, 180 }, precision ); - StringBuilder interwovenBin = new StringBuilder(); - for (int i = 0; i < latBitStr.length(); i++) { - interwovenBin.append(lonBitStr.charAt(i)); - interwovenBin.append(latBitStr.charAt(i)); + long interwovenBin = 1; + for (int i = precision - 1; i >= 0; i--) { + long currLon = (lonBit >>> i) & 1; + long currLat = (latBit >>> i) & 1; + interwovenBin <<= 1; + interwovenBin |= currLon; + interwovenBin <<= 1; + interwovenBin |= currLat; } return Geohash - .binaryToBase32(interwovenBin.toString()) + .binaryToBase32(interwovenBin, precision * 2) .substring(0, characterLength); } /** * Computes the base32 value of the binary string given - * @param binStr Binary string with "0" || "1" that is to be converted to a base32 string + * @param binStr (long) Binary string with "0" || "1" that is to be converted to a base32 string + * @param len (int) number of significant bits * @return base32 string of the binStr in chunks of 5 binary digits */ - private static String binaryToBase32(String binStr) { + private static String binaryToBase32(long binStr, int len) { StringBuilder base32Str = new StringBuilder(); - for (int i = 0; i < binStr.length(); i += 5) { - String chunk = binStr.substring(i, i + 5); - int decVal = Integer.valueOf(chunk, 2); - base32Str.append(base32.charAt(decVal)); + // for (int i = 0; i < binStr.length(); i += 5) { + // String chunk = binStr.substring(i, i + 5); + // int decVal = Integer.valueOf(chunk, 2); + // base32Str.append(base32.charAt(decVal)); + // } + + for (int i = len - 5; i >= 0; i -= 5) { + // Extract a group of 5 bits + int group = (int) (binStr >>> i) & 0x1F; + + // Use the extracted group as an index to fetch the corresponding base32 character + base32Str.append(base32.charAt(group)); } + return base32Str.toString(); } /** * Converts the value given to a binary string with the given precision and range - * @param value The value to be converted to a binString - * @param r The range at which the value is to be compared with - * @param precision The Precision (number of bits) that the binary string needs - * @return A binary string representation of the value with the given range and precision + * @param value (double) The value to be converted to a binString + * @param r (double[]) The range at which the value is to be compared with + * @param precision (int) The Precision (number of bits) that the binary string needs + * @return (String) A binary string representation of the value with the given range and precision */ - private static String convertToBinary( - double value, - double[] r, - int precision - ) { - StringBuilder binString = new StringBuilder(); + private static long convertToBinary(double value, double[] r, int precision) { + int binVal = 1; for (int i = 0; i < precision; i++) { double mid = (r[0] + r[1]) / 2; if (value >= mid) { - binString.append("1"); + binVal = binVal << 1; + binVal = binVal | 1; r[0] = mid; } else { - binString.append("0"); + binVal = binVal << 1; r[1] = mid; } } - return binString.toString(); + return binVal; } /** diff --git a/src/test/java/com/esri/core/geometry/TestGeohash.java b/src/test/java/com/esri/core/geometry/TestGeohash.java index 13ddd866..662e65df 100644 --- a/src/test/java/com/esri/core/geometry/TestGeohash.java +++ b/src/test/java/com/esri/core/geometry/TestGeohash.java @@ -74,22 +74,30 @@ public void testGeohashToEnvelopeGoodDimensions2() { @Test public void testToGeoHash() { + Point2D p0 = new Point2D(0, 0); + Point2D p1 = new Point2D(-4.329, 48.669); Point2D p2 = new Point2D(-30.382, 70.273); Point2D p3 = new Point2D(14.276, 37.691); Point2D p4 = new Point2D(-143.923, 48.669); + Point2D p5 = new Point2D(-143.923, 48.669); int chrLen = 5; + String p0Hash = Geohash.toGeohash(p0, 1); + String p1Hash = Geohash.toGeohash(p1, chrLen); String p2Hash = Geohash.toGeohash(p2, chrLen); String p3Hash = Geohash.toGeohash(p3, chrLen); String p4Hash = Geohash.toGeohash(p4, chrLen); + String p5Hash = Geohash.toGeohash(p5, 6); + assertEquals("s", p0Hash); assertEquals("gbsuv", p1Hash); assertEquals("gk6ru", p2Hash); assertEquals("sqdnk", p3Hash); assertEquals("bb9su", p4Hash); + assertEquals("bb9sug", p5Hash); } @Test