diff options
Diffstat (limited to 'vespajlib/src/main/java')
5 files changed, 421 insertions, 3 deletions
diff --git a/vespajlib/src/main/java/com/yahoo/geo/DegreesParser.java b/vespajlib/src/main/java/com/yahoo/geo/DegreesParser.java index 9f3d3b837f8..58164801c7c 100644 --- a/vespajlib/src/main/java/com/yahoo/geo/DegreesParser.java +++ b/vespajlib/src/main/java/com/yahoo/geo/DegreesParser.java @@ -2,7 +2,7 @@ package com.yahoo.geo; /** - * utility for parsing geographical coordinates + * Utility for parsing geographical coordinates * * @author arnej27959 **/ diff --git a/vespajlib/src/main/java/com/yahoo/geo/DistanceParser.java b/vespajlib/src/main/java/com/yahoo/geo/DistanceParser.java new file mode 100644 index 00000000000..1ae68afa4ac --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/geo/DistanceParser.java @@ -0,0 +1,79 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package com.yahoo.geo; + +import com.google.common.annotations.Beta; + +/** + * Utility for parsing a geographical distance with unit. + **/ +@Beta +public class DistanceParser { + // according to wikipedia: + // Earth's equatorial radius = 6378137 meter - not used + // meters per mile = 1609.344 + // 180 degrees equals one half diameter equals PI*r + // Earth's polar radius = 6356752 meter + + public final static double m2deg = 180.0 / (Math.PI * 6356752.0); + public final static double km2deg = 1000.000 * 180.0 / (Math.PI * 6356752.0); + public final static double mi2deg = 1609.344 * 180.0 / (Math.PI * 6356752.0); + + private final double degrees; + + public double getDegrees() { return degrees; } + + /** + * Parse a distance in some kind of units, converting to geographical degrees. + * Note that the number and the unit should be separated by a single space only, + * or not separated at all. + * Supported units are "m", "km", "miles", and "deg", + * the last one meaning degrees with no conversion. + * For brevity "mi" = "miles" and "d" = "deg". + **/ + static public double parse(String distance) { + var parser = new DistanceParser(distance, false); + return parser.degrees; + } + + DistanceParser(String distance, boolean assumeMicroDegrees) { + if (distance.endsWith(" km")) { + double km = Double.valueOf(distance.substring(0, distance.length()-3)); + degrees = km * km2deg; + } else if (distance.endsWith(" m")) { + double meters = Double.valueOf(distance.substring(0, distance.length()-2)); + degrees = meters * m2deg; + } else if (distance.endsWith(" miles")) { + double miles = Double.valueOf(distance.substring(0, distance.length()-6)); + degrees = miles * mi2deg; + } else if (distance.endsWith(" mi")) { + double miles = Double.valueOf(distance.substring(0, distance.length()-3)); + degrees = miles * mi2deg; + } else if (distance.endsWith(" deg")) { + degrees = Double.valueOf(distance.substring(0, distance.length()-4)); + } else if (distance.endsWith(" d")) { + degrees = Double.valueOf(distance.substring(0, distance.length()-2)); + } else if (distance.endsWith("km")) { + double km = Double.valueOf(distance.substring(0, distance.length()-2)); + degrees = km * km2deg; + } else if (distance.endsWith("m")) { + double meters = Double.valueOf(distance.substring(0, distance.length()-1)); + degrees = meters * m2deg; + } else if (distance.endsWith("miles")) { + double miles = Double.valueOf(distance.substring(0, distance.length()-5)); + degrees = miles * mi2deg; + } else if (distance.endsWith("mi")) { + double miles = Double.valueOf(distance.substring(0, distance.length()-2)); + degrees = miles * mi2deg; + } else if (distance.endsWith("deg")) { + degrees = Double.valueOf(distance.substring(0, distance.length()-3)); + } else if (distance.endsWith("d")) { + degrees = Double.valueOf(distance.substring(0, distance.length()-1)); + } else if (assumeMicroDegrees) { + degrees = Integer.parseInt(distance) * 0.000001; + } else { + throw new IllegalArgumentException("missing unit for distance: "+distance); + } + } + +} diff --git a/vespajlib/src/main/java/com/yahoo/geo/OneDegreeParser.java b/vespajlib/src/main/java/com/yahoo/geo/OneDegreeParser.java new file mode 100644 index 00000000000..cf23a24e702 --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/geo/OneDegreeParser.java @@ -0,0 +1,282 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package com.yahoo.geo; + +/** + * Utility for parsing one geographical coordinate + * + * @author arnej27959 + **/ +class OneDegreeParser { + /** + * the parsed latitude (degrees north if positive) + **/ + public double latitude = 0; + public boolean foundLatitude = false; + + /** + * the parsed longitude (degrees east if positive) + **/ + public double longitude = 0; + public boolean foundLongitude = false; + + public static boolean isDigit(char ch) { + return (ch >= '0' && ch <= '9'); + } + public static boolean isCompassDirection(char ch) { + return (ch == 'N' || ch == 'S' || ch == 'E' || ch == 'W'); + } + + private String parseString = null; + private int len = 0; + private int pos = 0; + + public String toString() { + if (foundLatitude) { + return parseString + " -> latitude(" + latitude + ")"; + } else { + return parseString + " -> longitude(" + longitude + ")"; + } + } + + private char getNextChar() throws IllegalArgumentException { + if (pos == len) { + pos++; + return 0; + } else if (pos > len) { + throw new IllegalArgumentException("position after end of string when parsing <"+parseString+">"); + } else { + return parseString.charAt(pos++); + } + } + + /** + * Parse the given string. + * + * The string must contain either a latitude or a longitude. + * A latitude should contain "N" or "S" and a number signifying + * degrees north or south, or a signed number. + * A longitude should contain "E" or "W" and a number + * signifying degrees east or west, or a signed number. + * <br> + * Fractional degrees are recommended as the main input format, + * but degrees plus fractional minutes may be used for testing. + * You can use the degree sign (U+00B0 as seen in unicode at + * http://www.unicode.org/charts/PDF/U0080.pdf) to separate + * degrees from minutes, put the direction (NSEW) between as a + * separator, or use a small letter 'o' as a replacement for the + * degrees sign. + * <br> + * Some valid input formats: <br> + * "37.416383" and "-122.024683" → Sunnyvale <br> + * "N37.416383" and "W122.024683" → Sunnyvale <br> + * "37N24.983" and "122W01.481" → same <br> + * "N37\u00B024.983" and "W122\u00B001.481" → same <br> + * "63.418417" and "10.433033" → Trondheim <br> + * "N63.418417" and "E10.433033" → same <br> + * "N63o25.105" and "E10o25.982" → same <br> + * "E10o25.982" and "N63o25.105" → same <br> + * "N63.418417" and "E10.433033" → same <br> + * "63N25.105" and "10E25.982" → same <br> + * @param assumeNorthSouth Latitude assumed, otherwise longitude + * @param toParse Latitude or longitude string to parse + * + **/ + public OneDegreeParser(boolean assumeNorthSouth, String toParse) throws IllegalArgumentException { + this.parseString = toParse; + this.len = parseString.length(); + consumeString(assumeNorthSouth); + } + + private void consumeString(boolean assumeNorthSouth) throws IllegalArgumentException { + char ch = getNextChar(); + + double degrees = 0.0; + double minutes = 0.0; + double seconds = 0.0; + boolean degSet = false; + boolean minSet = false; + boolean secSet = false; + boolean dirSet = false; + boolean foundDot = false; + boolean foundDigits = false; + + boolean findingLatitude = false; + boolean findingLongitude = false; + + double sign = +1.0; + + int lastpos = -1; + + // sign must be first character in string if present: + if (ch == '+') { + // unary plus is a nop + ch = getNextChar(); + } else if (ch == '-') { + sign = -1.0; + ch = getNextChar(); + } + do { + // did we find a valid char? + boolean valid = false; + if (pos == lastpos) { + throw new IllegalArgumentException("internal logic error at <"+parseString+"> pos:"+pos); + } else { + lastpos = pos; + } + + // first, see if we can find some number + double accum = 0.0; + + if (isDigit(ch) || ch == '.') { + valid = true; + if (foundDigits) { + throw new IllegalArgumentException("found digits after not consuming previous digits when parsing <"+parseString+">"); + } + double divider = 1.0; + foundDot = false; + while (isDigit(ch)) { + foundDigits = true; + accum *= 10; + accum += (ch - '0'); + ch = getNextChar(); + } + if (ch == '.') { + foundDot = true; + ch = getNextChar(); + while (isDigit(ch)) { + foundDigits = true; + accum *= 10; + accum += (ch - '0'); + divider *= 10; + ch = getNextChar(); + } + } + if (!foundDigits) { + throw new IllegalArgumentException("just a . is not a valid number when parsing <"+parseString+">"); + } + accum /= divider; + } + + // next, did we find a separator after the number? + // degree sign is a separator after degrees, before minutes + if (ch == '\u00B0' || ch == 'o') { + valid = true; + if (degSet) { + throw new IllegalArgumentException("degrees sign only valid just after degrees when parsing <"+parseString+">"); + } + if (!foundDigits) { + throw new IllegalArgumentException("must have number before degrees sign when parsing <"+parseString+">"); + } + if (foundDot) { + throw new IllegalArgumentException("cannot have fractional degrees before degrees sign when parsing <"+parseString+">"); + } + ch = getNextChar(); + } + // apostrophe is a separator after minutes, before seconds + if (ch == '\'') { + if (minSet || !degSet || !foundDigits) { + throw new IllegalArgumentException("minutes sign only valid just after minutes when parsing <"+parseString+">"); + } + if (foundDot) { + throw new IllegalArgumentException("cannot have fractional minutes before minutes sign when parsing <"+parseString+">"); + } + ch = getNextChar(); + } + + // if we found some number, assign it into the next unset variable + if (foundDigits) { + valid = true; + if (degSet) { + if (minSet) { + if (secSet) { + throw new IllegalArgumentException("extra number after full field when parsing <"+parseString+">"); + } else { + seconds = accum; + secSet = true; + } + } else { + minutes = accum; + minSet = true; + if (foundDot) { + secSet = true; + } + } + } else { + degrees = accum; + degSet = true; + if (foundDot) { + minSet = true; + secSet = true; + } + } + foundDot = false; + foundDigits = false; + } + + // there may to be a direction (NSEW) somewhere, too + if (isCompassDirection(ch)) { + valid = true; + if (dirSet) { + throw new IllegalArgumentException("already set direction once, cannot add direction: "+ch+" when parsing <"+parseString+">"); + } + dirSet = true; + if (ch == 'S' || ch == 'W') { + sign = -1; + } else { + sign = 1; + } + if (ch == 'E' || ch == 'W') { + findingLongitude = true; + } else { + findingLatitude = true; + } + ch = getNextChar(); + } + + // lastly, did we find the end-of-string? + if (ch == 0) { + valid = true; + if (!dirSet) { + if (assumeNorthSouth) { + findingLatitude = true; + } else { + findingLongitude = true; + } + } + if (!degSet) { + throw new IllegalArgumentException("end of field without any number seen when parsing <"+parseString+">"); + } + degrees += minutes / 60.0; + degrees += seconds / 3600.0; + degrees *= sign; + + if (findingLatitude) { + if (degrees < -90.0 || degrees > 90.0) { + throw new IllegalArgumentException("out of range [-90,+90]: "+degrees+" when parsing <"+parseString+">"); + } + latitude = degrees; + foundLatitude = true; + } else if (findingLongitude) { + if (degrees < -180.0 || degrees > 180.0) { + throw new IllegalArgumentException("out of range [-180,+180]: "+degrees+" when parsing <"+parseString+">"); + } + longitude = degrees; + foundLongitude = true; + } + break; + } + if (!valid) { + throw new IllegalArgumentException("invalid character: "+ch+" when parsing <"+parseString+">"); + } + } while (ch != 0); + // everything parsed OK + if (foundLatitude && foundLongitude) { + throw new IllegalArgumentException("found both latitude and longitude from: "+parseString); + } + if (foundLatitude || foundLongitude) { + return; + } + throw new IllegalArgumentException("found neither latitude nor longitude from: "+parseString); + } +} diff --git a/vespajlib/src/main/java/com/yahoo/geo/ParsedDegree.java b/vespajlib/src/main/java/com/yahoo/geo/ParsedDegree.java new file mode 100644 index 00000000000..84b87614182 --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/geo/ParsedDegree.java @@ -0,0 +1,57 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +package com.yahoo.geo; + +/** + * Utility for holding one geographical coordinate + * + * @author arnej27959 + **/ +public class ParsedDegree { + /** + * the parsed latitude or longitude + * Degrees north or east if positive + * Degrees south or west if negative + **/ + public final double degrees; + + // one of these two flag will be true: + public final boolean isLatitude; + public final boolean isLongitude; + + public ParsedDegree(double value, boolean isLat, boolean isLon) { + this.degrees = value; + this.isLatitude = isLat; + this.isLongitude = isLon; + if (isLat && isLon) { + throw new IllegalArgumentException("value cannot be both latitude and longitude at once"); + } + if (isLat || isLon) { + return; + } + throw new IllegalArgumentException("value must be either latitude or longitude"); + } + + static public ParsedDegree fromString(String toParse, boolean assumeLatitude, boolean assumeLongitude) { + if (assumeLatitude && assumeLongitude) { + throw new IllegalArgumentException("value cannot be both latitude and longitude at once"); + } + var parser = new OneDegreeParser(assumeLatitude, toParse); + if (parser.foundLatitude) { + return new ParsedDegree(parser.latitude, true, false); + } + if (parser.foundLongitude) { + return new ParsedDegree(parser.longitude, false, true); + } + throw new IllegalArgumentException("could not parse: "+toParse); + } + + public String toString() { + if (isLatitude) { + return "Latitude: "+degrees+" degrees"; + } else { + return "Longitude: "+degrees+" degrees"; + } + } + +} diff --git a/vespajlib/src/main/java/com/yahoo/text/Utf8.java b/vespajlib/src/main/java/com/yahoo/text/Utf8.java index 6f40b590a64..cb8ca244fe2 100644 --- a/vespajlib/src/main/java/com/yahoo/text/Utf8.java +++ b/vespajlib/src/main/java/com/yahoo/text/Utf8.java @@ -13,10 +13,10 @@ import java.nio.charset.CodingErrorAction; import java.nio.charset.StandardCharsets; /** - * utility class with functions for handling UTF-8 + * Utility class with functions for handling UTF-8 * * @author arnej27959 - * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a> + * @author Steinar Knutsen * @author baldersheim * */ |