summaryrefslogtreecommitdiffstats
path: root/vespajlib/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'vespajlib/src/main/java')
-rw-r--r--vespajlib/src/main/java/com/yahoo/geo/DegreesParser.java2
-rw-r--r--vespajlib/src/main/java/com/yahoo/geo/DistanceParser.java79
-rw-r--r--vespajlib/src/main/java/com/yahoo/geo/OneDegreeParser.java282
-rw-r--r--vespajlib/src/main/java/com/yahoo/geo/ParsedDegree.java57
-rw-r--r--vespajlib/src/main/java/com/yahoo/text/Utf8.java4
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
*
*/