Skip to content

Commit

Permalink
Support of GeoJson Point for GeoPoint field
Browse files Browse the repository at this point in the history
  • Loading branch information
heemin32 committed Sep 27, 2022
1 parent bb47419 commit 5d8d760
Show file tree
Hide file tree
Showing 8 changed files with 509 additions and 125 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Add BWC version 2.3.1 ([#4513](https://github.com/opensearch-project/OpenSearch/pull/4513))
- [Segment Replication] Add snapshot and restore tests for segment replication feature ([#3993](https://github.com/opensearch-project/OpenSearch/pull/3993))
- Added missing javadocs for `:example-plugins` modules ([#4540](https://github.com/opensearch-project/OpenSearch/pull/4540))
- Add support for GeoJson Point type in GeoPoint field ([#4597](https://github.com/opensearch-project/OpenSearch/pull/4597))
### Dependencies
- Bumps `log4j-core` from 2.18.0 to 2.19.0

Expand Down
6 changes: 5 additions & 1 deletion server/src/main/java/org/opensearch/common/geo/GeoPoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,11 @@ public GeoPoint resetFromString(String value, final boolean ignoreZValue, Effect
public GeoPoint resetFromCoordinates(String value, final boolean ignoreZValue) {
String[] vals = value.split(",");
if (vals.length > 3) {
throw new OpenSearchParseException("failed to parse [{}], expected 2 or 3 coordinates " + "but found: [{}]", vals.length);
throw new OpenSearchParseException(
"failed to parse [{}], expected 2 or 3 coordinates " + "but found: [{}]",
vals.length,
vals.length
);
}
final double lat;
final double lon;
Expand Down
131 changes: 34 additions & 97 deletions server/src/main/java/org/opensearch/common/geo/GeoUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@
import org.opensearch.common.xcontent.LoggingDeprecationHandler;
import org.opensearch.common.xcontent.NamedXContentRegistry;
import org.opensearch.common.xcontent.XContentParser;
import org.opensearch.common.xcontent.XContentParser.Token;
import org.opensearch.common.xcontent.XContentSubParser;
import org.opensearch.common.xcontent.support.MapXContentParser;
import org.opensearch.common.xcontent.support.XContentMapValues;
import org.opensearch.geometry.Point;
import org.opensearch.index.fielddata.FieldData;
import org.opensearch.index.fielddata.GeoPointValues;
import org.opensearch.index.fielddata.MultiGeoPointValues;
Expand Down Expand Up @@ -95,6 +95,12 @@ public class GeoUtils {
/** rounding error for quantized latitude and longitude values */
public static final double TOLERANCE = 1E-6;

public static final PointParser POINT_PARSER;

static {
POINT_PARSER = new PointParser(LONGITUDE, LATITUDE, true);
}

/** Returns true if latitude is actually a valid latitude value.*/
public static boolean isValidLatitude(double latitude) {
if (Double.isNaN(latitude) || Double.isInfinite(latitude) || latitude < GeoUtils.MIN_LAT || latitude > GeoUtils.MAX_LAT) {
Expand Down Expand Up @@ -444,113 +450,44 @@ public static GeoPoint parseGeoPoint(XContentParser parser, GeoPoint point, fina
* Parse a {@link GeoPoint} with a {@link XContentParser}. A geopoint has one of the following forms:
*
* <ul>
* <li>Object: <pre>{&quot;lat&quot;: <i>&lt;latitude&gt;</i>, &quot;lon&quot;: <i>&lt;longitude&gt;</i>}</pre></li>
* <li>String: <pre>&quot;<i>&lt;latitude&gt;</i>,<i>&lt;longitude&gt;</i>&quot;</pre></li>
* <li>Geohash: <pre>&quot;<i>&lt;geohash&gt;</i>&quot;</pre></li>
* <li>Array: <pre>[<i>&lt;longitude&gt;</i>,<i>&lt;latitude&gt;</i>]</pre></li>
* <li>Object: <pre>{@code {"lat": <latitude>, "lon": <longitude}}</pre></li>
* <li>String: <pre>{@code "<latitude>,<longitude>"}</pre></li>
* <li>GeoHash: <pre>{@code "<geohash>"}</pre></li>
* <li>WKT: <pre>{@code "POINT (<longitude> <latitude>)"}</pre></li>
* <li>Array: <pre>{@code [<longitude>, <latitude>]}</pre></li>
* <li>GeoJson: <pre>{@code {"type": "Point", "coordinates": [<longitude>, <latitude>]}}</pre><li>
* </ul>
*
*
* @param parser {@link XContentParser} to parse the value from
* @param point A {@link GeoPoint} that will be reset by the values parsed
* @param ignoreZValue tells to ignore z value or throw exception when there is a z value
* @param effectivePoint tells which point to use for GeoHash form
* @return new {@link GeoPoint} parsed from the parse
*/
public static GeoPoint parseGeoPoint(XContentParser parser, GeoPoint point, final boolean ignoreZValue, EffectivePoint effectivePoint)
throws IOException, OpenSearchParseException {
double lat = Double.NaN;
double lon = Double.NaN;
String geohash = null;
NumberFormatException numberFormatException = null;

if (parser.currentToken() == Token.START_OBJECT) {
try (XContentSubParser subParser = new XContentSubParser(parser)) {
while (subParser.nextToken() != Token.END_OBJECT) {
if (subParser.currentToken() == Token.FIELD_NAME) {
String field = subParser.currentName();
if (LATITUDE.equals(field)) {
subParser.nextToken();
switch (subParser.currentToken()) {
case VALUE_NUMBER:
case VALUE_STRING:
try {
lat = subParser.doubleValue(true);
} catch (NumberFormatException e) {
numberFormatException = e;
}
break;
default:
throw new OpenSearchParseException("latitude must be a number");
}
} else if (LONGITUDE.equals(field)) {
subParser.nextToken();
switch (subParser.currentToken()) {
case VALUE_NUMBER:
case VALUE_STRING:
try {
lon = subParser.doubleValue(true);
} catch (NumberFormatException e) {
numberFormatException = e;
}
break;
default:
throw new OpenSearchParseException("longitude must be a number");
}
} else if (GEOHASH.equals(field)) {
if (subParser.nextToken() == Token.VALUE_STRING) {
geohash = subParser.text();
} else {
throw new OpenSearchParseException("geohash must be a string");
}
} else {
throw new OpenSearchParseException("field must be either [{}], [{}] or [{}]", LATITUDE, LONGITUDE, GEOHASH);
}
} else {
throw new OpenSearchParseException("token [{}] not allowed", subParser.currentToken());
}
switch (parser.currentToken()) {
case START_OBJECT:
try (XContentSubParser subParser = new XContentSubParser(parser)) {
Point pointInObject = POINT_PARSER.parseObject(subParser, ignoreZValue, effectivePoint);
point.reset(pointInObject.getLat(), pointInObject.getLon());
}
}
if (geohash != null) {
if (!Double.isNaN(lat) || !Double.isNaN(lon)) {
throw new OpenSearchParseException("field must be either lat/lon or geohash");
} else {
return point.parseGeoHash(geohash, effectivePoint);
}
} else if (numberFormatException != null) {
throw new OpenSearchParseException("[{}] and [{}] must be valid double values", numberFormatException, LATITUDE, LONGITUDE);
} else if (Double.isNaN(lat)) {
throw new OpenSearchParseException("field [{}] missing", LATITUDE);
} else if (Double.isNaN(lon)) {
throw new OpenSearchParseException("field [{}] missing", LONGITUDE);
} else {
return point.reset(lat, lon);
}

} else if (parser.currentToken() == Token.START_ARRAY) {
try (XContentSubParser subParser = new XContentSubParser(parser)) {
int element = 0;
while (subParser.nextToken() != Token.END_ARRAY) {
if (subParser.currentToken() == Token.VALUE_NUMBER) {
element++;
if (element == 1) {
lon = subParser.doubleValue();
} else if (element == 2) {
lat = subParser.doubleValue();
} else if (element == 3) {
GeoPoint.assertZValue(ignoreZValue, subParser.doubleValue());
} else {
throw new OpenSearchParseException("[geo_point] field type does not accept > 3 dimensions");
}
} else {
throw new OpenSearchParseException("numeric value expected");
}
break;
case START_ARRAY:
try (XContentSubParser subParser = new XContentSubParser(parser)) {
Point pointInArray = POINT_PARSER.parseFromArray(subParser, ignoreZValue, "geo_point");
point.reset(pointInArray.getLat(), pointInArray.getLon());
}
}
return point.reset(lat, lon);
} else if (parser.currentToken() == Token.VALUE_STRING) {
String val = parser.text();
return point.resetFromString(val, ignoreZValue, effectivePoint);
} else {
throw new OpenSearchParseException("geo_point expected");
break;
case VALUE_STRING:
String val = parser.text();
point.resetFromString(val, ignoreZValue, effectivePoint);
break;
default:
throw new OpenSearchParseException("geo_point expected");
}
return point;
}

/**
Expand Down
Loading

0 comments on commit 5d8d760

Please sign in to comment.