diff --git a/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/GeoEmptyValueSource.java b/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/GeoEmptyValueSource.java index 64fa25540ded1..6aaf20acf62cb 100644 --- a/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/GeoEmptyValueSource.java +++ b/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/GeoEmptyValueSource.java @@ -11,8 +11,8 @@ import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.search.DoubleValues; import org.elasticsearch.index.fielddata.IndexFieldData; -import org.elasticsearch.index.fielddata.LeafGeoPointFieldData; import org.elasticsearch.index.fielddata.MultiGeoPointValues; +import org.elasticsearch.index.fielddata.plain.LeafGeoPointFieldData; import java.io.IOException; @@ -28,7 +28,7 @@ final class GeoEmptyValueSource extends FieldDataBasedDoubleValuesSource { @Override public DoubleValues getValues(LeafReaderContext leaf, DoubleValues scores) { LeafGeoPointFieldData leafData = (LeafGeoPointFieldData) fieldData.load(leaf); - final MultiGeoPointValues values = leafData.getGeoPointValues(); + final MultiGeoPointValues values = leafData.getPointValues(); // shade return new DoubleValues() { @Override public double doubleValue() { diff --git a/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/GeoLatitudeValueSource.java b/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/GeoLatitudeValueSource.java index d64671ec06688..b8c9f95784ad5 100644 --- a/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/GeoLatitudeValueSource.java +++ b/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/GeoLatitudeValueSource.java @@ -11,8 +11,8 @@ import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.search.DoubleValues; import org.elasticsearch.index.fielddata.IndexFieldData; -import org.elasticsearch.index.fielddata.LeafGeoPointFieldData; import org.elasticsearch.index.fielddata.MultiGeoPointValues; +import org.elasticsearch.index.fielddata.plain.LeafGeoPointFieldData; import java.io.IOException; @@ -28,7 +28,7 @@ final class GeoLatitudeValueSource extends FieldDataBasedDoubleValuesSource { @Override public DoubleValues getValues(LeafReaderContext leaf, DoubleValues scores) { LeafGeoPointFieldData leafData = (LeafGeoPointFieldData) fieldData.load(leaf); - final MultiGeoPointValues values = leafData.getGeoPointValues(); + final MultiGeoPointValues values = leafData.getPointValues(); return new DoubleValues() { @Override public double doubleValue() throws IOException { diff --git a/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/GeoLongitudeValueSource.java b/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/GeoLongitudeValueSource.java index 33f3602e2d702..dec6beb8dc732 100644 --- a/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/GeoLongitudeValueSource.java +++ b/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/GeoLongitudeValueSource.java @@ -11,8 +11,8 @@ import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.search.DoubleValues; import org.elasticsearch.index.fielddata.IndexFieldData; -import org.elasticsearch.index.fielddata.LeafGeoPointFieldData; import org.elasticsearch.index.fielddata.MultiGeoPointValues; +import org.elasticsearch.index.fielddata.plain.LeafGeoPointFieldData; import java.io.IOException; @@ -28,7 +28,7 @@ final class GeoLongitudeValueSource extends FieldDataBasedDoubleValuesSource { @Override public DoubleValues getValues(LeafReaderContext leaf, DoubleValues scores) { LeafGeoPointFieldData leafData = (LeafGeoPointFieldData) fieldData.load(leaf); - final MultiGeoPointValues values = leafData.getGeoPointValues(); + final MultiGeoPointValues values = leafData.getPointValues(); return new DoubleValues() { @Override public double doubleValue() throws IOException { @@ -53,8 +53,7 @@ public boolean equals(Object obj) { if (obj == null) return false; if (getClass() != obj.getClass()) return false; GeoLongitudeValueSource other = (GeoLongitudeValueSource) obj; - if (fieldData.equals(other.fieldData) == false) return false; - return true; + return fieldData.equals(other.fieldData); } @Override diff --git a/server/src/internalClusterTest/java/org/elasticsearch/search/aggregations/MissingValueIT.java b/server/src/internalClusterTest/java/org/elasticsearch/search/aggregations/MissingValueIT.java index b044353ebbf9e..96636b57f2774 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/search/aggregations/MissingValueIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/search/aggregations/MissingValueIT.java @@ -218,7 +218,7 @@ public void testGeoCentroid() { assertSearchResponse(response); GeoCentroid centroid = response.getAggregations().get("centroid"); GeoPoint point = new GeoPoint(1.5, 1.5); - assertThat(point.lat(), closeTo(centroid.centroid().lat(), 1E-5)); - assertThat(point.lon(), closeTo(centroid.centroid().lon(), 1E-5)); + assertThat(point.getY(), closeTo(centroid.centroid().getY(), 1E-5)); + assertThat(point.getX(), closeTo(centroid.centroid().getX(), 1E-5)); } } diff --git a/server/src/internalClusterTest/java/org/elasticsearch/search/aggregations/metrics/GeoCentroidIT.java b/server/src/internalClusterTest/java/org/elasticsearch/search/aggregations/metrics/GeoCentroidIT.java index f377a5be1c845..b2329bb0ef530 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/search/aggregations/metrics/GeoCentroidIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/search/aggregations/metrics/GeoCentroidIT.java @@ -45,8 +45,7 @@ public void testEmptyAggregation() throws Exception { assertThat(response.getHits().getTotalHits().value, equalTo(0L)); assertThat(geoCentroid, notNullValue()); assertThat(geoCentroid.getName(), equalTo(aggName)); - GeoPoint centroid = geoCentroid.centroid(); - assertThat(centroid, equalTo(null)); + assertThat(geoCentroid.centroid(), equalTo(null)); assertEquals(0, geoCentroid.count()); } @@ -59,8 +58,7 @@ public void testUnmapped() throws Exception { GeoCentroid geoCentroid = response.getAggregations().get(aggName); assertThat(geoCentroid, notNullValue()); assertThat(geoCentroid.getName(), equalTo(aggName)); - GeoPoint centroid = geoCentroid.centroid(); - assertThat(centroid, equalTo(null)); + assertThat(geoCentroid.centroid(), equalTo(null)); assertEquals(0, geoCentroid.count()); } @@ -73,9 +71,7 @@ public void testPartiallyUnmapped() throws Exception { GeoCentroid geoCentroid = response.getAggregations().get(aggName); assertThat(geoCentroid, notNullValue()); assertThat(geoCentroid.getName(), equalTo(aggName)); - GeoPoint centroid = geoCentroid.centroid(); - assertThat(centroid.lat(), closeTo(singleCentroid.lat(), GEOHASH_TOLERANCE)); - assertThat(centroid.lon(), closeTo(singleCentroid.lon(), GEOHASH_TOLERANCE)); + assertSameCentroid(geoCentroid.centroid(), singleCentroid); assertEquals(numDocs, geoCentroid.count()); } @@ -89,13 +85,11 @@ public void testSingleValuedField() throws Exception { GeoCentroid geoCentroid = response.getAggregations().get(aggName); assertThat(geoCentroid, notNullValue()); assertThat(geoCentroid.getName(), equalTo(aggName)); - GeoPoint centroid = geoCentroid.centroid(); - assertThat(centroid.lat(), closeTo(singleCentroid.lat(), GEOHASH_TOLERANCE)); - assertThat(centroid.lon(), closeTo(singleCentroid.lon(), GEOHASH_TOLERANCE)); + assertSameCentroid(geoCentroid.centroid(), singleCentroid); assertEquals(numDocs, geoCentroid.count()); } - public void testSingleValueFieldGetProperty() throws Exception { + public void testSingleValueFieldGetProperty() { SearchResponse response = client().prepareSearch(IDX_NAME) .setQuery(matchAllQuery()) .addAggregation(global("global").subAggregation(geoCentroid(aggName).field(SINGLE_VALUED_FIELD_NAME))) @@ -113,9 +107,7 @@ public void testSingleValueFieldGetProperty() throws Exception { assertThat(geoCentroid, notNullValue()); assertThat(geoCentroid.getName(), equalTo(aggName)); assertThat((GeoCentroid) ((InternalAggregation) global).getProperty(aggName), sameInstance(geoCentroid)); - GeoPoint centroid = geoCentroid.centroid(); - assertThat(centroid.lat(), closeTo(singleCentroid.lat(), GEOHASH_TOLERANCE)); - assertThat(centroid.lon(), closeTo(singleCentroid.lon(), GEOHASH_TOLERANCE)); + assertSameCentroid(geoCentroid.centroid(), singleCentroid); assertThat( ((GeoPoint) ((InternalAggregation) global).getProperty(aggName + ".value")).lat(), closeTo(singleCentroid.lat(), GEOHASH_TOLERANCE) @@ -139,13 +131,11 @@ public void testMultiValuedField() throws Exception { GeoCentroid geoCentroid = searchResponse.getAggregations().get(aggName); assertThat(geoCentroid, notNullValue()); assertThat(geoCentroid.getName(), equalTo(aggName)); - GeoPoint centroid = geoCentroid.centroid(); - assertThat(centroid.lat(), closeTo(multiCentroid.lat(), GEOHASH_TOLERANCE)); - assertThat(centroid.lon(), closeTo(multiCentroid.lon(), GEOHASH_TOLERANCE)); + assertSameCentroid(geoCentroid.centroid(), multiCentroid); assertEquals(2 * numDocs, geoCentroid.count()); } - public void testSingleValueFieldAsSubAggToGeohashGrid() throws Exception { + public void testSingleValueFieldAsSubAggToGeohashGrid() { SearchResponse response = client().prepareSearch(HIGH_CARD_IDX_NAME) .addAggregation( geohashGrid("geoGrid").field(SINGLE_VALUED_FIELD_NAME).subAggregation(geoCentroid(aggName).field(SINGLE_VALUED_FIELD_NAME)) @@ -161,16 +151,7 @@ public void testSingleValueFieldAsSubAggToGeohashGrid() throws Exception { String geohash = cell.getKeyAsString(); GeoPoint expectedCentroid = expectedCentroidsForGeoHash.get(geohash); GeoCentroid centroidAgg = cell.getAggregations().get(aggName); - assertThat( - "Geohash " + geohash + " has wrong centroid latitude ", - expectedCentroid.lat(), - closeTo(centroidAgg.centroid().lat(), GEOHASH_TOLERANCE) - ); - assertThat( - "Geohash " + geohash + " has wrong centroid longitude", - expectedCentroid.lon(), - closeTo(centroidAgg.centroid().lon(), GEOHASH_TOLERANCE) - ); + assertSameCentroid(centroidAgg.centroid(), expectedCentroid); } } } diff --git a/server/src/internalClusterTest/java/org/elasticsearch/search/geo/GeoPointScriptDocValuesIT.java b/server/src/internalClusterTest/java/org/elasticsearch/search/geo/GeoPointScriptDocValuesIT.java index cbd403ba8c9d0..55994458388a4 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/search/geo/GeoPointScriptDocValuesIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/search/geo/GeoPointScriptDocValuesIT.java @@ -11,7 +11,7 @@ import org.apache.lucene.geo.GeoEncodingUtils; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.common.document.DocumentField; -import org.elasticsearch.common.geo.GeoBoundingBox; +import org.elasticsearch.common.geo.BoundingBox; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.geo.GeometryTestUtils; import org.elasticsearch.index.fielddata.ScriptDocValues; @@ -66,52 +66,52 @@ protected Map, Object>> pluginScripts() { private double scriptHeight(Map vars) { Map doc = (Map) vars.get("doc"); - ScriptDocValues.Geometry geometry = assertGeometry(doc); + ScriptDocValues.Geometry geometry = assertGeometry(doc); if (geometry.size() == 0) { return Double.NaN; } else { - GeoBoundingBox boundingBox = geometry.getBoundingBox(); + BoundingBox boundingBox = geometry.getBoundingBox(); return boundingBox.topLeft().lat() - boundingBox.bottomRight().lat(); } } private double scriptWidth(Map vars) { Map doc = (Map) vars.get("doc"); - ScriptDocValues.Geometry geometry = assertGeometry(doc); + ScriptDocValues.Geometry geometry = assertGeometry(doc); if (geometry.size() == 0) { return Double.NaN; } else { - GeoBoundingBox boundingBox = geometry.getBoundingBox(); + BoundingBox boundingBox = geometry.getBoundingBox(); return boundingBox.bottomRight().lon() - boundingBox.topLeft().lon(); } } private double scriptLat(Map vars) { Map doc = (Map) vars.get("doc"); - ScriptDocValues.Geometry geometry = assertGeometry(doc); + ScriptDocValues.Geometry geometry = assertGeometry(doc); return geometry.size() == 0 ? Double.NaN : geometry.getCentroid().lat(); } private double scriptLon(Map vars) { Map doc = (Map) vars.get("doc"); - ScriptDocValues.Geometry geometry = assertGeometry(doc); + ScriptDocValues.Geometry geometry = assertGeometry(doc); return geometry.size() == 0 ? Double.NaN : geometry.getCentroid().lon(); } private double scriptLabelLat(Map vars) { Map doc = (Map) vars.get("doc"); - ScriptDocValues.Geometry geometry = assertGeometry(doc); + ScriptDocValues.Geometry geometry = assertGeometry(doc); return geometry.size() == 0 ? Double.NaN : geometry.getLabelPosition().lat(); } private double scriptLabelLon(Map vars) { Map doc = (Map) vars.get("doc"); - ScriptDocValues.Geometry geometry = assertGeometry(doc); + ScriptDocValues.Geometry geometry = assertGeometry(doc); return geometry.size() == 0 ? Double.NaN : geometry.getLabelPosition().lon(); } - private ScriptDocValues.Geometry assertGeometry(Map doc) { - ScriptDocValues.Geometry geometry = (ScriptDocValues.Geometry) doc.get("location"); + private ScriptDocValues.Geometry assertGeometry(Map doc) { + ScriptDocValues.Geometry geometry = (ScriptDocValues.Geometry) doc.get("location"); if (geometry.size() == 0) { assertThat(geometry.getBoundingBox(), Matchers.nullValue()); assertThat(geometry.getCentroid(), Matchers.nullValue()); diff --git a/server/src/main/java/org/elasticsearch/common/geo/BoundingBox.java b/server/src/main/java/org/elasticsearch/common/geo/BoundingBox.java new file mode 100644 index 0000000000000..c1c2007b111e5 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/common/geo/BoundingBox.java @@ -0,0 +1,197 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +package org.elasticsearch.common.geo; + +import org.elasticsearch.ElasticsearchParseException; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.geometry.Geometry; +import org.elasticsearch.geometry.Rectangle; +import org.elasticsearch.geometry.ShapeType; +import org.elasticsearch.geometry.utils.StandardValidator; +import org.elasticsearch.geometry.utils.WellKnownText; +import org.elasticsearch.xcontent.ParseField; +import org.elasticsearch.xcontent.ToXContentFragment; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.XContentParser; + +import java.io.IOException; +import java.text.ParseException; +import java.util.Objects; + +/** + * A class representing a Bounding-Box for use by Geo and Cartesian queries and aggregations + * that deal with extents/rectangles representing rectangular areas of interest. + */ +public abstract class BoundingBox implements ToXContentFragment, Writeable { + static final ParseField TOP_RIGHT_FIELD = new ParseField("top_right"); + static final ParseField BOTTOM_LEFT_FIELD = new ParseField("bottom_left"); + static final ParseField TOP_FIELD = new ParseField("top"); + static final ParseField BOTTOM_FIELD = new ParseField("bottom"); + static final ParseField LEFT_FIELD = new ParseField("left"); + static final ParseField RIGHT_FIELD = new ParseField("right"); + static final ParseField WKT_FIELD = new ParseField("wkt"); + public static final ParseField BOUNDS_FIELD = new ParseField("bounds"); + public static final ParseField TOP_LEFT_FIELD = new ParseField("top_left"); + public static final ParseField BOTTOM_RIGHT_FIELD = new ParseField("bottom_right"); + protected final T topLeft; + protected final T bottomRight; + + public BoundingBox(T topLeft, T bottomRight) { + this.topLeft = topLeft; + this.bottomRight = bottomRight; + } + + public boolean isUnbounded() { + return Double.isNaN(left()) || Double.isNaN(top()) || Double.isNaN(right()) || Double.isNaN(bottom()); + } + + public T topLeft() { + return topLeft; + } + + public T bottomRight() { + return bottomRight; + } + + public final double top() { + return topLeft.getY(); + } + + public final double bottom() { + return bottomRight.getY(); + } + + public final double left() { + return topLeft.getX(); + } + + public final double right() { + return bottomRight.getX(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + toXContentFragment(builder); + builder.endObject(); + return builder; + } + + public abstract XContentBuilder toXContentFragment(XContentBuilder builder) throws IOException; + + protected abstract static class BoundsParser> { + protected double top = Double.NaN; + protected double bottom = Double.NaN; + protected double left = Double.NaN; + protected double right = Double.NaN; + protected Rectangle envelope = null; + final XContentParser parser; + + protected BoundsParser(XContentParser parser) { + this.parser = parser; + } + + public T parseBoundingBox() throws IOException, ElasticsearchParseException { + XContentParser.Token token = parser.currentToken(); + if (token != XContentParser.Token.START_OBJECT) { + throw new ElasticsearchParseException("failed to parse bounding box. Expected start object but found [{}]", token); + } + String currentFieldName; + + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + parser.nextToken(); + if (WKT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { + try { + Geometry geometry = WellKnownText.fromWKT(StandardValidator.instance(true), true, parser.text()); + if (ShapeType.ENVELOPE.equals(geometry.type()) == false) { + throw new ElasticsearchParseException( + "failed to parse WKT bounding box. [" + + geometry.type() + + "] found. expected [" + + ShapeType.ENVELOPE + + "]" + ); + } + envelope = (Rectangle) geometry; + } catch (ParseException | IllegalArgumentException e) { + throw new ElasticsearchParseException("failed to parse WKT bounding box", e); + } + } else if (TOP_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { + top = parser.doubleValue(); + } else if (BOTTOM_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { + bottom = parser.doubleValue(); + } else if (LEFT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { + left = parser.doubleValue(); + } else if (RIGHT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { + right = parser.doubleValue(); + } else { + if (TOP_LEFT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { + SpatialPoint point = parsePointWith(parser, GeoUtils.EffectivePoint.TOP_LEFT); + this.top = point.getY(); + this.left = point.getX(); + } else if (BOTTOM_RIGHT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { + SpatialPoint point = parsePointWith(parser, GeoUtils.EffectivePoint.BOTTOM_RIGHT); + this.bottom = point.getY(); + this.right = point.getX(); + } else if (TOP_RIGHT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { + SpatialPoint point = parsePointWith(parser, GeoUtils.EffectivePoint.TOP_RIGHT); + this.top = point.getY(); + this.right = point.getX(); + } else if (BOTTOM_LEFT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { + SpatialPoint point = parsePointWith(parser, GeoUtils.EffectivePoint.BOTTOM_LEFT); + this.bottom = point.getY(); + this.left = point.getX(); + } else { + throw new ElasticsearchParseException("failed to parse bounding box. unexpected field [{}]", currentFieldName); + } + } + } else { + throw new ElasticsearchParseException("failed to parse bounding box. field name expected but [{}] found", token); + } + } + if (envelope != null) { + if (Double.isNaN(top) == false + || Double.isNaN(bottom) == false + || Double.isNaN(left) == false + || Double.isNaN(right) == false) { + throw new ElasticsearchParseException( + "failed to parse bounding box. Conflicting definition found " + "using well-known text and explicit corners." + ); + } + return createWithEnvelope(); + } + return createWithBounds(); + } + + protected abstract T createWithEnvelope(); + + protected abstract T createWithBounds(); + + protected abstract SpatialPoint parsePointWith(XContentParser parser, GeoUtils.EffectivePoint effectivePoint) throws IOException; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BoundingBox that = (BoundingBox) o; + return topLeft.equals(that.topLeft) && bottomRight.equals(that.bottomRight); + } + + @Override + public int hashCode() { + return Objects.hash(topLeft, bottomRight); + } + + @Override + public String toString() { + return "BBOX (" + left() + ", " + right() + ", " + top() + ", " + bottom() + ")"; + } +} diff --git a/server/src/main/java/org/elasticsearch/common/geo/GeoBoundingBox.java b/server/src/main/java/org/elasticsearch/common/geo/GeoBoundingBox.java index 18dda203255c6..a4c9faa3852c0 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/GeoBoundingBox.java +++ b/server/src/main/java/org/elasticsearch/common/geo/GeoBoundingBox.java @@ -10,108 +10,58 @@ import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.common.io.stream.Writeable; -import org.elasticsearch.geometry.Geometry; -import org.elasticsearch.geometry.Rectangle; -import org.elasticsearch.geometry.ShapeType; -import org.elasticsearch.geometry.utils.StandardValidator; -import org.elasticsearch.geometry.utils.WellKnownText; import org.elasticsearch.xcontent.ParseField; -import org.elasticsearch.xcontent.ToXContentFragment; import org.elasticsearch.xcontent.XContentBuilder; import org.elasticsearch.xcontent.XContentParser; import java.io.IOException; -import java.text.ParseException; -import java.util.Objects; /** * A class representing a Geo-Bounding-Box for use by Geo queries and aggregations * that deal with extents/rectangles representing rectangular areas of interest. */ -public class GeoBoundingBox implements ToXContentFragment, Writeable { - static final ParseField TOP_RIGHT_FIELD = new ParseField("top_right"); - static final ParseField BOTTOM_LEFT_FIELD = new ParseField("bottom_left"); - static final ParseField TOP_FIELD = new ParseField("top"); - static final ParseField BOTTOM_FIELD = new ParseField("bottom"); - static final ParseField LEFT_FIELD = new ParseField("left"); - static final ParseField RIGHT_FIELD = new ParseField("right"); - static final ParseField WKT_FIELD = new ParseField("wkt"); - public static final ParseField BOUNDS_FIELD = new ParseField("bounds"); +public class GeoBoundingBox extends BoundingBox { public static final ParseField LAT_FIELD = new ParseField("lat"); public static final ParseField LON_FIELD = new ParseField("lon"); - public static final ParseField TOP_LEFT_FIELD = new ParseField("top_left"); - public static final ParseField BOTTOM_RIGHT_FIELD = new ParseField("bottom_right"); - - private final GeoPoint topLeft; - private final GeoPoint bottomRight; public GeoBoundingBox(GeoPoint topLeft, GeoPoint bottomRight) { - this.topLeft = topLeft; - this.bottomRight = bottomRight; + super(topLeft, bottomRight); } public GeoBoundingBox(StreamInput input) throws IOException { - this.topLeft = input.readGeoPoint(); - this.bottomRight = input.readGeoPoint(); - } - - public boolean isUnbounded() { - return Double.isNaN(topLeft.lon()) - || Double.isNaN(topLeft.lat()) - || Double.isNaN(bottomRight.lon()) - || Double.isNaN(bottomRight.lat()); + super(input.readGeoPoint(), input.readGeoPoint()); } + @Override public GeoPoint topLeft() { return topLeft; } + @Override public GeoPoint bottomRight() { return bottomRight; } - public double top() { - return topLeft.lat(); - } - - public double bottom() { - return bottomRight.lat(); - } - - public double left() { - return topLeft.lon(); - } - - public double right() { - return bottomRight.lon(); - } - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(); - toXContentFragment(builder, true); + public XContentBuilder toXContentFragment(XContentBuilder builder) throws IOException { + builder.startObject(TOP_LEFT_FIELD.getPreferredName()); + builder.field(LAT_FIELD.getPreferredName(), topLeft.getY()); + builder.field(LON_FIELD.getPreferredName(), topLeft.getX()); + builder.endObject(); + builder.startObject(BOTTOM_RIGHT_FIELD.getPreferredName()); + builder.field(LAT_FIELD.getPreferredName(), bottomRight.getY()); + builder.field(LON_FIELD.getPreferredName(), bottomRight.getX()); builder.endObject(); return builder; } - public XContentBuilder toXContentFragment(XContentBuilder builder, boolean buildLatLonFields) throws IOException { - if (buildLatLonFields) { - builder.startObject(TOP_LEFT_FIELD.getPreferredName()); - builder.field(LAT_FIELD.getPreferredName(), topLeft.lat()); - builder.field(LON_FIELD.getPreferredName(), topLeft.lon()); - builder.endObject(); - } else { - builder.array(TOP_LEFT_FIELD.getPreferredName(), topLeft.lon(), topLeft.lat()); - } - if (buildLatLonFields) { - builder.startObject(BOTTOM_RIGHT_FIELD.getPreferredName()); - builder.field(LAT_FIELD.getPreferredName(), bottomRight.lat()); - builder.field(LON_FIELD.getPreferredName(), bottomRight.lon()); - builder.endObject(); - } else { - builder.array(BOTTOM_RIGHT_FIELD.getPreferredName(), bottomRight.lon(), bottomRight.lat()); - } + /** + * There exists a special case where we use an array format for building the XContent for the bounds. + * Specifically the GeoBoundingBoxQueryBuilder makes use of this. All other cases build a keyed map. + */ + public XContentBuilder toXContentFragmentWithArray(XContentBuilder builder) throws IOException { + builder.array(TOP_LEFT_FIELD.getPreferredName(), topLeft.getX(), topLeft.getY()); + builder.array(BOTTOM_RIGHT_FIELD.getPreferredName(), bottomRight.getX(), bottomRight.getY()); return builder; } @@ -141,106 +91,36 @@ public void writeTo(StreamOutput out) throws IOException { out.writeGeoPoint(bottomRight); } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - GeoBoundingBox that = (GeoBoundingBox) o; - return topLeft.equals(that.topLeft) && bottomRight.equals(that.bottomRight); - } + protected static class GeoBoundsParser extends BoundsParser { + GeoBoundsParser(XContentParser parser) { + super(parser); + } - @Override - public int hashCode() { - return Objects.hash(topLeft, bottomRight); - } + @Override + protected GeoBoundingBox createWithEnvelope() { + GeoPoint topLeft = new GeoPoint(envelope.getMaxLat(), envelope.getMinLon()); + GeoPoint bottomRight = new GeoPoint(envelope.getMinLat(), envelope.getMaxLon()); + return new GeoBoundingBox(topLeft, bottomRight); + } - @Override - public String toString() { - return "BBOX (" + topLeft.lon() + ", " + bottomRight.lon() + ", " + topLeft.lat() + ", " + bottomRight.lat() + ")"; + @Override + protected GeoBoundingBox createWithBounds() { + GeoPoint topLeft = new GeoPoint(top, left); + GeoPoint bottomRight = new GeoPoint(bottom, right); + return new GeoBoundingBox(topLeft, bottomRight); + } + + @Override + protected SpatialPoint parsePointWith(XContentParser parser, GeoUtils.EffectivePoint effectivePoint) throws IOException { + return GeoUtils.parseGeoPoint(parser, false, effectivePoint); + } } /** * Parses the bounding box and returns bottom, top, left, right coordinates */ public static GeoBoundingBox parseBoundingBox(XContentParser parser) throws IOException, ElasticsearchParseException { - XContentParser.Token token = parser.currentToken(); - if (token != XContentParser.Token.START_OBJECT) { - throw new ElasticsearchParseException("failed to parse bounding box. Expected start object but found [{}]", token); - } - - double top = Double.NaN; - double bottom = Double.NaN; - double left = Double.NaN; - double right = Double.NaN; - - String currentFieldName; - Rectangle envelope = null; - - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - currentFieldName = parser.currentName(); - parser.nextToken(); - if (WKT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - try { - Geometry geometry = WellKnownText.fromWKT(StandardValidator.instance(true), true, parser.text()); - if (ShapeType.ENVELOPE.equals(geometry.type()) == false) { - throw new ElasticsearchParseException( - "failed to parse WKT bounding box. [" + geometry.type() + "] found. expected [" + ShapeType.ENVELOPE + "]" - ); - } - envelope = (Rectangle) geometry; - } catch (ParseException | IllegalArgumentException e) { - throw new ElasticsearchParseException("failed to parse WKT bounding box", e); - } - } else if (TOP_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - top = parser.doubleValue(); - } else if (BOTTOM_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - bottom = parser.doubleValue(); - } else if (LEFT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - left = parser.doubleValue(); - } else if (RIGHT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - right = parser.doubleValue(); - } else { - if (TOP_LEFT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - GeoPoint sparse = GeoUtils.parseGeoPoint(parser, false, GeoUtils.EffectivePoint.TOP_LEFT); - top = sparse.getLat(); - left = sparse.getLon(); - } else if (BOTTOM_RIGHT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - GeoPoint sparse = GeoUtils.parseGeoPoint(parser, false, GeoUtils.EffectivePoint.BOTTOM_RIGHT); - bottom = sparse.getLat(); - right = sparse.getLon(); - } else if (TOP_RIGHT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - GeoPoint sparse = GeoUtils.parseGeoPoint(parser, false, GeoUtils.EffectivePoint.TOP_RIGHT); - top = sparse.getLat(); - right = sparse.getLon(); - } else if (BOTTOM_LEFT_FIELD.match(currentFieldName, parser.getDeprecationHandler())) { - GeoPoint sparse = GeoUtils.parseGeoPoint(parser, false, GeoUtils.EffectivePoint.BOTTOM_LEFT); - bottom = sparse.getLat(); - left = sparse.getLon(); - } else { - throw new ElasticsearchParseException("failed to parse bounding box. unexpected field [{}]", currentFieldName); - } - } - } else { - throw new ElasticsearchParseException("failed to parse bounding box. field name expected but [{}] found", token); - } - } - if (envelope != null) { - if (Double.isNaN(top) == false - || Double.isNaN(bottom) == false - || Double.isNaN(left) == false - || Double.isNaN(right) == false) { - throw new ElasticsearchParseException( - "failed to parse bounding box. Conflicting definition found " + "using well-known text and explicit corners." - ); - } - GeoPoint topLeft = new GeoPoint(envelope.getMaxLat(), envelope.getMinLon()); - GeoPoint bottomRight = new GeoPoint(envelope.getMinLat(), envelope.getMaxLon()); - return new GeoBoundingBox(topLeft, bottomRight); - } - GeoPoint topLeft = new GeoPoint(top, left); - GeoPoint bottomRight = new GeoPoint(bottom, right); - return new GeoBoundingBox(topLeft, bottomRight); + GeoBoundsParser bounds = new GeoBoundsParser(parser); + return bounds.parseBoundingBox(); } - } diff --git a/server/src/main/java/org/elasticsearch/common/geo/GeoPoint.java b/server/src/main/java/org/elasticsearch/common/geo/GeoPoint.java index d94a5c77ad630..5c609e0458ea7 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/GeoPoint.java +++ b/server/src/main/java/org/elasticsearch/common/geo/GeoPoint.java @@ -30,7 +30,7 @@ import java.util.Arrays; import java.util.Locale; -public class GeoPoint implements ToXContentFragment { +public class GeoPoint implements SpatialPoint, ToXContentFragment { protected double lat; protected double lon; @@ -52,8 +52,8 @@ public GeoPoint(double lat, double lon) { this.lon = lon; } - public GeoPoint(GeoPoint template) { - this(template.getLat(), template.getLon()); + public GeoPoint(SpatialPoint template) { + this(template.getY(), template.getX()); } public GeoPoint reset(double lat, double lon) { @@ -189,6 +189,16 @@ public double getLon() { return this.lon; } + @Override + public double getX() { + return this.lon; + } + + @Override + public double getY() { + return this.lat; + } + public String geohash() { return Geohash.stringEncode(lon, lat); } diff --git a/server/src/main/java/org/elasticsearch/common/geo/GeoUtils.java b/server/src/main/java/org/elasticsearch/common/geo/GeoUtils.java index b90aa885ba869..d65169148635c 100644 --- a/server/src/main/java/org/elasticsearch/common/geo/GeoUtils.java +++ b/server/src/main/java/org/elasticsearch/common/geo/GeoUtils.java @@ -537,7 +537,7 @@ public boolean advanceExact(int doc) throws IOException { @Override public double doubleValue() throws IOException { final GeoPoint from = fromPoints[0]; - final GeoPoint to = singleValues.geoPointValue(); + final GeoPoint to = singleValues.pointValue(); return distance.calculate(from.lat(), from.lon(), to.lat(), to.lon(), unit); } diff --git a/server/src/main/java/org/elasticsearch/common/geo/SpatialPoint.java b/server/src/main/java/org/elasticsearch/common/geo/SpatialPoint.java new file mode 100644 index 0000000000000..a12aa336e8bdc --- /dev/null +++ b/server/src/main/java/org/elasticsearch/common/geo/SpatialPoint.java @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.common.geo; + +/** + * To facilitate maximizing the use of common code between GeoPoint and projected CRS + * we introduced this ElasticPoint as an interface of commonality. + */ +public interface SpatialPoint { + double getX(); + + double getY(); +} diff --git a/server/src/main/java/org/elasticsearch/index/fielddata/FieldData.java b/server/src/main/java/org/elasticsearch/index/fielddata/FieldData.java index fca2c002d4238..581c417b53a51 100644 --- a/server/src/main/java/org/elasticsearch/index/fielddata/FieldData.java +++ b/server/src/main/java/org/elasticsearch/index/fielddata/FieldData.java @@ -14,6 +14,7 @@ import org.apache.lucene.index.SortedNumericDocValues; import org.apache.lucene.index.SortedSetDocValues; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.geo.SpatialPoint; import java.io.IOException; import java.util.ArrayList; @@ -86,7 +87,7 @@ public boolean advanceExact(int doc) throws IOException { * Returns a {@link DocValueBits} representing all documents from pointValues that have * a value. */ - public static DocValueBits docsWithValue(final MultiGeoPointValues pointValues) { + public static DocValueBits docsWithValue(final MultiPointValues pointValues) { return new DocValueBits() { @Override public boolean advanceExact(int doc) throws IOException { @@ -217,7 +218,7 @@ public static NumericDoubleValues unwrapSingleton(SortedNumericDoubleValues valu * if the wrapped {@link SortedNumericDocValues} is a singleton. */ public static GeoPointValues unwrapSingleton(MultiGeoPointValues values) { - return values.getGeoPointValues(); + return values.getPointValues(); } /** diff --git a/server/src/main/java/org/elasticsearch/index/fielddata/GeoPointScriptFieldData.java b/server/src/main/java/org/elasticsearch/index/fielddata/GeoPointScriptFieldData.java index 01e95e4398422..49caa1af2f7ad 100644 --- a/server/src/main/java/org/elasticsearch/index/fielddata/GeoPointScriptFieldData.java +++ b/server/src/main/java/org/elasticsearch/index/fielddata/GeoPointScriptFieldData.java @@ -12,7 +12,7 @@ import org.apache.lucene.index.SortedNumericDocValues; import org.apache.lucene.search.SortField; import org.elasticsearch.common.util.BigArrays; -import org.elasticsearch.index.fielddata.plain.AbstractLeafGeoPointFieldData; +import org.elasticsearch.index.fielddata.plain.LeafGeoPointFieldData; import org.elasticsearch.indices.breaker.CircuitBreakerService; import org.elasticsearch.script.GeoPointFieldScript; import org.elasticsearch.script.field.ToScriptFieldFactory; @@ -23,7 +23,7 @@ import org.elasticsearch.search.sort.BucketedSort; import org.elasticsearch.search.sort.SortOrder; -public class GeoPointScriptFieldData implements IndexGeoPointFieldData { +public final class GeoPointScriptFieldData implements IndexGeoPointFieldData { public static class Builder implements IndexFieldData.Builder { private final String name; private final GeoPointFieldScript.LeafFactory leafFactory; @@ -89,9 +89,9 @@ public ValuesSourceType getValuesSourceType() { } @Override - public LeafGeoPointFieldData load(LeafReaderContext context) { + public LeafPointFieldData load(LeafReaderContext context) { GeoPointFieldScript script = leafFactory.newInstance(context); - return new AbstractLeafGeoPointFieldData(toScriptFieldFactory) { + return new LeafGeoPointFieldData(toScriptFieldFactory) { @Override public SortedNumericDocValues getSortedNumericDocValues() { return new GeoPointScriptDocValues(script); @@ -110,7 +110,7 @@ public void close() { } @Override - public LeafGeoPointFieldData loadDirect(LeafReaderContext context) { + public LeafPointFieldData loadDirect(LeafReaderContext context) { return load(context); } } diff --git a/server/src/main/java/org/elasticsearch/index/fielddata/GeoPointValues.java b/server/src/main/java/org/elasticsearch/index/fielddata/GeoPointValues.java index ecca68f774b5b..e621ee74c6ad1 100644 --- a/server/src/main/java/org/elasticsearch/index/fielddata/GeoPointValues.java +++ b/server/src/main/java/org/elasticsearch/index/fielddata/GeoPointValues.java @@ -16,28 +16,20 @@ /** * Per-document geo-point values. */ -public final class GeoPointValues { +public final class GeoPointValues extends PointValues { private final GeoPoint point = new GeoPoint(); - private final NumericDocValues values; GeoPointValues(NumericDocValues values) { - this.values = values; - } - - /** - * Advance this instance to the given document id - * @return true if there is a value for this document - */ - public boolean advanceExact(int doc) throws IOException { - return values.advanceExact(doc); + super(values); } /** * Get the {@link GeoPoint} associated with the current document. * The returned {@link GeoPoint} might be reused across calls. */ - public GeoPoint geoPointValue() throws IOException { + @Override + public GeoPoint pointValue() throws IOException { return point.resetFromEncoded(values.longValue()); } } diff --git a/server/src/main/java/org/elasticsearch/index/fielddata/IndexGeoPointFieldData.java b/server/src/main/java/org/elasticsearch/index/fielddata/IndexGeoPointFieldData.java index 234dba623f7f9..211ee34050b84 100644 --- a/server/src/main/java/org/elasticsearch/index/fielddata/IndexGeoPointFieldData.java +++ b/server/src/main/java/org/elasticsearch/index/fielddata/IndexGeoPointFieldData.java @@ -11,4 +11,4 @@ /** * Specialization of {@link IndexFieldData} for geo points. */ -public interface IndexGeoPointFieldData extends IndexFieldData {} +public interface IndexGeoPointFieldData extends IndexPointFieldData {} diff --git a/server/src/main/java/org/elasticsearch/index/fielddata/IndexPointFieldData.java b/server/src/main/java/org/elasticsearch/index/fielddata/IndexPointFieldData.java new file mode 100644 index 0000000000000..7373e01c71c0b --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/fielddata/IndexPointFieldData.java @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.index.fielddata; + +import org.elasticsearch.common.geo.SpatialPoint; + +/** + * Specialization of {@link IndexFieldData} for geo points and points. + */ +public interface IndexPointFieldData> extends IndexFieldData> {} diff --git a/server/src/main/java/org/elasticsearch/index/fielddata/LeafGeoPointFieldData.java b/server/src/main/java/org/elasticsearch/index/fielddata/LeafPointFieldData.java similarity index 62% rename from server/src/main/java/org/elasticsearch/index/fielddata/LeafGeoPointFieldData.java rename to server/src/main/java/org/elasticsearch/index/fielddata/LeafPointFieldData.java index 0d59ec8d8d720..a30664b909d17 100644 --- a/server/src/main/java/org/elasticsearch/index/fielddata/LeafGeoPointFieldData.java +++ b/server/src/main/java/org/elasticsearch/index/fielddata/LeafPointFieldData.java @@ -8,21 +8,20 @@ package org.elasticsearch.index.fielddata; import org.apache.lucene.index.SortedNumericDocValues; +import org.elasticsearch.common.geo.SpatialPoint; /** - * {@link LeafFieldData} specialization for geo points. + * {@link LeafFieldData} specialization for geo points and points. */ -public abstract class LeafGeoPointFieldData implements LeafFieldData { +public abstract class LeafPointFieldData> implements LeafFieldData { /** - * Return geo-point values. + * Return geo-point or point values. */ - public final MultiGeoPointValues getGeoPointValues() { - return new MultiGeoPointValues(getSortedNumericDocValues()); - } + public abstract T getPointValues(); /** - * Return the internal representation of geo_point doc values as a {@link SortedNumericDocValues}. + * Return the internal representation of geo_point or point doc values as a {@link SortedNumericDocValues}. * A point is encoded as a long that can be decoded by using * {@link org.elasticsearch.common.geo.GeoPoint#resetFromEncoded(long)} */ diff --git a/server/src/main/java/org/elasticsearch/index/fielddata/MultiGeoPointValues.java b/server/src/main/java/org/elasticsearch/index/fielddata/MultiGeoPointValues.java index e19c9046bff19..ac870385631fb 100644 --- a/server/src/main/java/org/elasticsearch/index/fielddata/MultiGeoPointValues.java +++ b/server/src/main/java/org/elasticsearch/index/fielddata/MultiGeoPointValues.java @@ -18,62 +18,35 @@ * A stateful lightweight per document set of {@link GeoPoint} values. * To iterate over values in a document use the following pattern: *
- *   GeoPointValues values = ..;
- *   values.setDocId(docId);
- *   final int numValues = values.count();
+ *   MultiGeoPointValues values = ..;
+ *   values.advanceExact(docId);
+ *   final int numValues = values.docValueCount();
  *   for (int i = 0; i < numValues; i++) {
- *       GeoPoint value = values.valueAt(i);
+ *       GeoPoint value = values.nextValue();
  *       // process value
  *   }
  * 
* The set of values associated with a document might contain duplicates and * comes in a non-specified order. */ -public final class MultiGeoPointValues { - +public class MultiGeoPointValues extends MultiPointValues { private final GeoPoint point = new GeoPoint(); - private final SortedNumericDocValues numericValues; - /** - * Creates a new {@link MultiGeoPointValues} instance - */ public MultiGeoPointValues(SortedNumericDocValues numericValues) { - this.numericValues = numericValues; - } - - /** - * Advance this instance to the given document id - * @return true if there is a value for this document - */ - public boolean advanceExact(int doc) throws IOException { - return numericValues.advanceExact(doc); + super(numericValues); } - /** - * Return the number of geo points the current document has. - */ - public int docValueCount() { - return numericValues.docValueCount(); - } - - /** - * Return the next value associated with the current document. This must not be - * called more than {@link #docValueCount()} times. - * - * Note: the returned {@link GeoPoint} might be shared across invocations. - * - * @return the next value for the current docID set to {@link #advanceExact(int)}. - */ + @Override public GeoPoint nextValue() throws IOException { return point.resetFromEncoded(numericValues.nextValue()); } /** - * Returns a single-valued view of the {@link MultiGeoPointValues} if possible, otherwise null. + * Returns a single-valued view of the {@link MultiPointValues} if possible, otherwise null. */ - GeoPointValues getGeoPointValues() { + @Override + protected GeoPointValues getPointValues() { final NumericDocValues singleton = DocValues.unwrapSingleton(numericValues); return singleton != null ? new GeoPointValues(singleton) : null; } - } diff --git a/server/src/main/java/org/elasticsearch/index/fielddata/MultiPointValues.java b/server/src/main/java/org/elasticsearch/index/fielddata/MultiPointValues.java new file mode 100644 index 0000000000000..0b295ed7c799f --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/fielddata/MultiPointValues.java @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +package org.elasticsearch.index.fielddata; + +import org.apache.lucene.index.SortedNumericDocValues; +import org.elasticsearch.common.geo.GeoPoint; +import org.elasticsearch.common.geo.SpatialPoint; + +import java.io.IOException; + +/** + * A stateful lightweight per document set of {@link SpatialPoint} values. + */ +public abstract class MultiPointValues { + + protected final SortedNumericDocValues numericValues; + + /** + * Creates a new {@link MultiPointValues} instance + */ + protected MultiPointValues(SortedNumericDocValues numericValues) { + this.numericValues = numericValues; + } + + /** + * Advance this instance to the given document id + * @return true if there is a value for this document + */ + public boolean advanceExact(int doc) throws IOException { + return numericValues.advanceExact(doc); + } + + /** + * Return the number of geo points the current document has. + */ + public int docValueCount() { + return numericValues.docValueCount(); + } + + /** + * Return the next value associated with the current document. This must not be + * called more than {@link #docValueCount()} times. + * + * Note: the returned {@link GeoPoint} might be shared across invocations. + * + * @return the next value for the current docID set to {@link #advanceExact(int)}. + */ + public abstract T nextValue() throws IOException; + + /** + * Returns a single-valued view of the {@link MultiPointValues} if possible, otherwise null. + */ + protected abstract PointValues getPointValues(); +} diff --git a/server/src/main/java/org/elasticsearch/index/fielddata/PointValues.java b/server/src/main/java/org/elasticsearch/index/fielddata/PointValues.java new file mode 100644 index 0000000000000..278476626ae39 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/fielddata/PointValues.java @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.index.fielddata; + +import org.apache.lucene.index.NumericDocValues; +import org.elasticsearch.common.geo.SpatialPoint; + +import java.io.IOException; + +/** + * Per-document geo-point or point values. + */ +public abstract class PointValues { + + protected final NumericDocValues values; + + protected PointValues(NumericDocValues values) { + this.values = values; + } + + /** + * Advance this instance to the given document id + * @return true if there is a value for this document + */ + public boolean advanceExact(int doc) throws IOException { + return values.advanceExact(doc); + } + + public abstract T pointValue() throws IOException; +} diff --git a/server/src/main/java/org/elasticsearch/index/fielddata/ScriptDocValues.java b/server/src/main/java/org/elasticsearch/index/fielddata/ScriptDocValues.java index de7320ab07564..78e0c14b81e20 100644 --- a/server/src/main/java/org/elasticsearch/index/fielddata/ScriptDocValues.java +++ b/server/src/main/java/org/elasticsearch/index/fielddata/ScriptDocValues.java @@ -11,9 +11,11 @@ import org.apache.lucene.util.ArrayUtil; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRefBuilder; +import org.elasticsearch.common.geo.BoundingBox; import org.elasticsearch.common.geo.GeoBoundingBox; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoUtils; +import org.elasticsearch.common.geo.SpatialPoint; import org.elasticsearch.geometry.utils.Geohash; import org.elasticsearch.script.field.DocValuesScriptFieldFactory; @@ -220,9 +222,9 @@ public int size() { } } - public abstract static class Geometry extends ScriptDocValues { + public abstract static class BaseGeometry extends ScriptDocValues { - public Geometry(Supplier supplier) { + public BaseGeometry(Supplier supplier) { super(supplier); } @@ -230,35 +232,52 @@ public Geometry(Supplier supplier) { public abstract int getDimensionalType(); /** Returns the bounding box of this geometry */ - public abstract GeoBoundingBox getBoundingBox(); + public abstract BoundingBox getBoundingBox(); /** Returns the suggested label position */ - public abstract GeoPoint getLabelPosition(); + public abstract T getLabelPosition(); /** Returns the centroid of this geometry */ - public abstract GeoPoint getCentroid(); + public abstract T getCentroid(); + } + + public interface Geometry { + /** Returns the dimensional type of this geometry */ + int getDimensionalType(); + + /** Returns the bounding box of this geometry */ + GeoBoundingBox getBoundingBox(); + + /** Returns the suggested label position */ + GeoPoint getLabelPosition(); + + /** Returns the centroid of this geometry */ + GeoPoint getCentroid(); + + /** returns the size of the geometry */ + int size(); /** Returns the width of the bounding box diagonal in the spherical Mercator projection (meters) */ - public abstract double getMercatorWidth(); + double getMercatorWidth(); /** Returns the height of the bounding box diagonal in the spherical Mercator projection (meters) */ - public abstract double getMercatorHeight(); + double getMercatorHeight(); } - public interface GeometrySupplier extends Supplier { + public interface GeometrySupplier extends Supplier { - GeoPoint getInternalCentroid(); + T getInternalCentroid(); - GeoBoundingBox getInternalBoundingBox(); + BoundingBox getInternalBoundingBox(); - GeoPoint getInternalLabelPosition(); + T getInternalLabelPosition(); } - public static class GeoPoints extends Geometry { + public static class GeoPoints extends BaseGeometry implements Geometry { - private final GeometrySupplier geometrySupplier; + private final GeometrySupplier geometrySupplier; - public GeoPoints(GeometrySupplier supplier) { + public GeoPoints(GeometrySupplier supplier) { super(supplier); geometrySupplier = supplier; } @@ -366,7 +385,7 @@ public double getMercatorHeight() { @Override public GeoBoundingBox getBoundingBox() { - return size() == 0 ? null : geometrySupplier.getInternalBoundingBox(); + return size() == 0 ? null : (GeoBoundingBox) geometrySupplier.getInternalBoundingBox(); } @Override diff --git a/server/src/main/java/org/elasticsearch/index/fielddata/plain/AbstractLatLonPointIndexFieldData.java b/server/src/main/java/org/elasticsearch/index/fielddata/plain/AbstractLatLonPointIndexFieldData.java deleted file mode 100644 index 9d242e627b8c4..0000000000000 --- a/server/src/main/java/org/elasticsearch/index/fielddata/plain/AbstractLatLonPointIndexFieldData.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ -package org.elasticsearch.index.fielddata.plain; - -import org.apache.lucene.document.LatLonDocValuesField; -import org.apache.lucene.index.DocValuesType; -import org.apache.lucene.index.FieldInfo; -import org.apache.lucene.index.LeafReader; -import org.apache.lucene.index.LeafReaderContext; -import org.apache.lucene.search.SortField; -import org.elasticsearch.common.util.BigArrays; -import org.elasticsearch.core.Nullable; -import org.elasticsearch.index.fielddata.IndexFieldData; -import org.elasticsearch.index.fielddata.IndexFieldData.XFieldComparatorSource.Nested; -import org.elasticsearch.index.fielddata.IndexFieldDataCache; -import org.elasticsearch.index.fielddata.IndexGeoPointFieldData; -import org.elasticsearch.index.fielddata.LeafGeoPointFieldData; -import org.elasticsearch.index.fielddata.MultiGeoPointValues; -import org.elasticsearch.indices.breaker.CircuitBreakerService; -import org.elasticsearch.script.field.ToScriptFieldFactory; -import org.elasticsearch.search.DocValueFormat; -import org.elasticsearch.search.MultiValueMode; -import org.elasticsearch.search.aggregations.support.ValuesSourceType; -import org.elasticsearch.search.sort.BucketedSort; -import org.elasticsearch.search.sort.SortOrder; - -public abstract class AbstractLatLonPointIndexFieldData implements IndexGeoPointFieldData { - - protected final String fieldName; - protected final ValuesSourceType valuesSourceType; - protected final ToScriptFieldFactory toScriptFieldFactory; - - AbstractLatLonPointIndexFieldData( - String fieldName, - ValuesSourceType valuesSourceType, - ToScriptFieldFactory toScriptFieldFactory - ) { - this.fieldName = fieldName; - this.valuesSourceType = valuesSourceType; - this.toScriptFieldFactory = toScriptFieldFactory; - } - - @Override - public final String getFieldName() { - return fieldName; - } - - @Override - public ValuesSourceType getValuesSourceType() { - return valuesSourceType; - } - - @Override - public SortField sortField( - @Nullable Object missingValue, - MultiValueMode sortMode, - XFieldComparatorSource.Nested nested, - boolean reverse - ) { - throw new IllegalArgumentException("can't sort on geo_point field without using specific sorting feature, like geo_distance"); - } - - @Override - public BucketedSort newBucketedSort( - BigArrays bigArrays, - Object missingValue, - MultiValueMode sortMode, - Nested nested, - SortOrder sortOrder, - DocValueFormat format, - int bucketSize, - BucketedSort.ExtraData extra - ) { - throw new IllegalArgumentException("can't sort on geo_point field without using specific sorting feature, like geo_distance"); - } - - public static class LatLonPointIndexFieldData extends AbstractLatLonPointIndexFieldData { - public LatLonPointIndexFieldData( - String fieldName, - ValuesSourceType valuesSourceType, - ToScriptFieldFactory toScriptFieldFactory - ) { - super(fieldName, valuesSourceType, toScriptFieldFactory); - } - - @Override - public LeafGeoPointFieldData load(LeafReaderContext context) { - LeafReader reader = context.reader(); - FieldInfo info = reader.getFieldInfos().fieldInfo(fieldName); - if (info != null) { - checkCompatible(info); - } - return new LatLonPointDVLeafFieldData(reader, fieldName, toScriptFieldFactory); - } - - @Override - public LeafGeoPointFieldData loadDirect(LeafReaderContext context) throws Exception { - return load(context); - } - - /** helper: checks a fieldinfo and throws exception if its definitely not a LatLonDocValuesField */ - static void checkCompatible(FieldInfo fieldInfo) { - // dv properties could be "unset", if you e.g. used only StoredField with this same name in the segment. - if (fieldInfo.getDocValuesType() != DocValuesType.NONE - && fieldInfo.getDocValuesType() != LatLonDocValuesField.TYPE.docValuesType()) { - throw new IllegalArgumentException( - "field=\"" - + fieldInfo.name - + "\" was indexed with docValuesType=" - + fieldInfo.getDocValuesType() - + " but this type has docValuesType=" - + LatLonDocValuesField.TYPE.docValuesType() - + ", is the field really a LatLonDocValuesField?" - ); - } - } - } - - public static class Builder implements IndexFieldData.Builder { - private final String name; - private final ValuesSourceType valuesSourceType; - private final ToScriptFieldFactory toScriptFieldFactory; - - public Builder(String name, ValuesSourceType valuesSourceType, ToScriptFieldFactory toScriptFieldFactory) { - this.name = name; - this.valuesSourceType = valuesSourceType; - this.toScriptFieldFactory = toScriptFieldFactory; - } - - @Override - public IndexFieldData build(IndexFieldDataCache cache, CircuitBreakerService breakerService) { - // ignore breaker - return new LatLonPointIndexFieldData(name, valuesSourceType, toScriptFieldFactory); - } - } -} diff --git a/server/src/main/java/org/elasticsearch/index/fielddata/plain/AbstractPointIndexFieldData.java b/server/src/main/java/org/elasticsearch/index/fielddata/plain/AbstractPointIndexFieldData.java new file mode 100644 index 0000000000000..b1b5c9dc094d2 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/fielddata/plain/AbstractPointIndexFieldData.java @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +package org.elasticsearch.index.fielddata.plain; + +import org.apache.lucene.search.SortField; +import org.elasticsearch.common.geo.SpatialPoint; +import org.elasticsearch.common.util.BigArrays; +import org.elasticsearch.core.Nullable; +import org.elasticsearch.index.fielddata.IndexFieldData.XFieldComparatorSource.Nested; +import org.elasticsearch.index.fielddata.IndexPointFieldData; +import org.elasticsearch.index.fielddata.MultiPointValues; +import org.elasticsearch.script.field.ToScriptFieldFactory; +import org.elasticsearch.search.DocValueFormat; +import org.elasticsearch.search.MultiValueMode; +import org.elasticsearch.search.aggregations.support.ValuesSourceType; +import org.elasticsearch.search.sort.BucketedSort; +import org.elasticsearch.search.sort.SortOrder; + +abstract class AbstractPointIndexFieldData> implements IndexPointFieldData { + + protected final String fieldName; + protected final ValuesSourceType valuesSourceType; + protected final ToScriptFieldFactory toScriptFieldFactory; + + protected AbstractPointIndexFieldData( + String fieldName, + ValuesSourceType valuesSourceType, + ToScriptFieldFactory toScriptFieldFactory + ) { + this.fieldName = fieldName; + this.valuesSourceType = valuesSourceType; + this.toScriptFieldFactory = toScriptFieldFactory; + } + + @Override + public final String getFieldName() { + return fieldName; + } + + @Override + public ValuesSourceType getValuesSourceType() { + return valuesSourceType; + } + + @Override + public SortField sortField( + @Nullable Object missingValue, + MultiValueMode sortMode, + XFieldComparatorSource.Nested nested, + boolean reverse + ) { + throw new IllegalArgumentException( + "can't sort on geo_point or point field without using specific sorting feature, like geo_distance" + ); + } + + @Override + public BucketedSort newBucketedSort( + BigArrays bigArrays, + Object missingValue, + MultiValueMode sortMode, + Nested nested, + SortOrder sortOrder, + DocValueFormat format, + int bucketSize, + BucketedSort.ExtraData extra + ) { + throw new IllegalArgumentException( + "can't sort on geo_point or point field without using specific sorting feature, like geo_distance" + ); + } +} diff --git a/server/src/main/java/org/elasticsearch/index/fielddata/plain/LatLonPointDVLeafFieldData.java b/server/src/main/java/org/elasticsearch/index/fielddata/plain/LatLonPointDVLeafFieldData.java index 0c7c41027eb2e..3b94293d67902 100644 --- a/server/src/main/java/org/elasticsearch/index/fielddata/plain/LatLonPointDVLeafFieldData.java +++ b/server/src/main/java/org/elasticsearch/index/fielddata/plain/LatLonPointDVLeafFieldData.java @@ -18,7 +18,7 @@ import java.util.Collection; import java.util.Collections; -final class LatLonPointDVLeafFieldData extends AbstractLeafGeoPointFieldData { +final class LatLonPointDVLeafFieldData extends LeafGeoPointFieldData { private final LeafReader reader; private final String fieldName; diff --git a/server/src/main/java/org/elasticsearch/index/fielddata/plain/LatLonPointIndexFieldData.java b/server/src/main/java/org/elasticsearch/index/fielddata/plain/LatLonPointIndexFieldData.java new file mode 100644 index 0000000000000..4134363bfd667 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/fielddata/plain/LatLonPointIndexFieldData.java @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.index.fielddata.plain; + +import org.apache.lucene.document.LatLonDocValuesField; +import org.apache.lucene.index.DocValuesType; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.LeafReader; +import org.apache.lucene.index.LeafReaderContext; +import org.elasticsearch.index.fielddata.IndexFieldData; +import org.elasticsearch.index.fielddata.IndexFieldDataCache; +import org.elasticsearch.index.fielddata.IndexGeoPointFieldData; +import org.elasticsearch.index.fielddata.LeafPointFieldData; +import org.elasticsearch.index.fielddata.MultiGeoPointValues; +import org.elasticsearch.indices.breaker.CircuitBreakerService; +import org.elasticsearch.script.field.ToScriptFieldFactory; +import org.elasticsearch.search.aggregations.support.ValuesSourceType; + +public final class LatLonPointIndexFieldData extends AbstractPointIndexFieldData implements IndexGeoPointFieldData { + public LatLonPointIndexFieldData( + String fieldName, + ValuesSourceType valuesSourceType, + ToScriptFieldFactory toScriptFieldFactory + ) { + super(fieldName, valuesSourceType, toScriptFieldFactory); + } + + @Override + public LeafPointFieldData load(LeafReaderContext context) { + LeafReader reader = context.reader(); + FieldInfo info = reader.getFieldInfos().fieldInfo(fieldName); + if (info != null) { + checkCompatible(info); + } + return new LatLonPointDVLeafFieldData(reader, fieldName, toScriptFieldFactory); + } + + @Override + public LeafPointFieldData loadDirect(LeafReaderContext context) throws Exception { + return load(context); + } + + /** helper: checks a fieldinfo and throws exception if its definitely not a LatLonDocValuesField */ + static void checkCompatible(FieldInfo fieldInfo) { + // dv properties could be "unset", if you e.g. used only StoredField with this same name in the segment. + if (fieldInfo.getDocValuesType() != DocValuesType.NONE + && fieldInfo.getDocValuesType() != LatLonDocValuesField.TYPE.docValuesType()) { + throw new IllegalArgumentException( + "field=\"" + + fieldInfo.name + + "\" was indexed with docValuesType=" + + fieldInfo.getDocValuesType() + + " but this type has docValuesType=" + + LatLonDocValuesField.TYPE.docValuesType() + + ", is the field really a LatLonDocValuesField?" + ); + } + } + + public static class Builder implements IndexFieldData.Builder { + private final String name; + private final ValuesSourceType valuesSourceType; + private final ToScriptFieldFactory toScriptFieldFactory; + + public Builder(String name, ValuesSourceType valuesSourceType, ToScriptFieldFactory toScriptFieldFactory) { + this.name = name; + this.valuesSourceType = valuesSourceType; + this.toScriptFieldFactory = toScriptFieldFactory; + } + + @Override + public IndexFieldData build(IndexFieldDataCache cache, CircuitBreakerService breakerService) { + // ignore breaker + return new LatLonPointIndexFieldData(name, valuesSourceType, toScriptFieldFactory); + } + } +} diff --git a/server/src/main/java/org/elasticsearch/index/fielddata/plain/AbstractLeafGeoPointFieldData.java b/server/src/main/java/org/elasticsearch/index/fielddata/plain/LeafGeoPointFieldData.java similarity index 65% rename from server/src/main/java/org/elasticsearch/index/fielddata/plain/AbstractLeafGeoPointFieldData.java rename to server/src/main/java/org/elasticsearch/index/fielddata/plain/LeafGeoPointFieldData.java index 63ba8a1a99637..0630c477fbfdd 100644 --- a/server/src/main/java/org/elasticsearch/index/fielddata/plain/AbstractLeafGeoPointFieldData.java +++ b/server/src/main/java/org/elasticsearch/index/fielddata/plain/LeafGeoPointFieldData.java @@ -8,27 +8,32 @@ package org.elasticsearch.index.fielddata.plain; import org.elasticsearch.index.fielddata.FieldData; -import org.elasticsearch.index.fielddata.LeafGeoPointFieldData; +import org.elasticsearch.index.fielddata.LeafPointFieldData; import org.elasticsearch.index.fielddata.MultiGeoPointValues; import org.elasticsearch.index.fielddata.SortedBinaryDocValues; import org.elasticsearch.script.field.DocValuesScriptFieldFactory; import org.elasticsearch.script.field.ToScriptFieldFactory; -public abstract class AbstractLeafGeoPointFieldData extends LeafGeoPointFieldData { +public abstract class LeafGeoPointFieldData extends LeafPointFieldData { protected final ToScriptFieldFactory toScriptFieldFactory; - public AbstractLeafGeoPointFieldData(ToScriptFieldFactory toScriptFieldFactory) { + public LeafGeoPointFieldData(ToScriptFieldFactory toScriptFieldFactory) { this.toScriptFieldFactory = toScriptFieldFactory; } + @Override + public final MultiGeoPointValues getPointValues() { + return new MultiGeoPointValues(getSortedNumericDocValues()); + } + @Override public final SortedBinaryDocValues getBytesValues() { - return FieldData.toString(getGeoPointValues()); + return FieldData.toString(getPointValues()); } @Override public DocValuesScriptFieldFactory getScriptFieldFactory(String name) { - return toScriptFieldFactory.getScriptFieldFactory(getGeoPointValues(), name); + return toScriptFieldFactory.getScriptFieldFactory(getPointValues(), name); } } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/GeoPointFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/GeoPointFieldMapper.java index 0143bf952e9dd..418360c615427 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/GeoPointFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/GeoPointFieldMapper.java @@ -31,7 +31,7 @@ import org.elasticsearch.index.fielddata.FieldDataContext; import org.elasticsearch.index.fielddata.IndexFieldData; import org.elasticsearch.index.fielddata.SourceValueFetcherMultiGeoPointIndexFieldData; -import org.elasticsearch.index.fielddata.plain.AbstractLatLonPointIndexFieldData; +import org.elasticsearch.index.fielddata.plain.LatLonPointIndexFieldData; import org.elasticsearch.index.query.SearchExecutionContext; import org.elasticsearch.script.GeoPointFieldScript; import org.elasticsearch.script.Script; @@ -378,7 +378,7 @@ public IndexFieldData.Builder fielddataBuilder(FieldDataContext fieldDataContext } if ((operation == FielddataOperation.SEARCH || operation == FielddataOperation.SCRIPT) && hasDocValues()) { - return new AbstractLatLonPointIndexFieldData.Builder(name(), CoreValuesSourceType.GEOPOINT, GeoPointDocValuesField::new); + return new LatLonPointIndexFieldData.Builder(name(), CoreValuesSourceType.GEOPOINT, GeoPointDocValuesField::new); } if (operation == FielddataOperation.SCRIPT) { diff --git a/server/src/main/java/org/elasticsearch/index/query/GeoBoundingBoxQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/GeoBoundingBoxQueryBuilder.java index 730bae7685b6b..3b88d00b9769b 100644 --- a/server/src/main/java/org/elasticsearch/index/query/GeoBoundingBoxQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/GeoBoundingBoxQueryBuilder.java @@ -307,7 +307,7 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep builder.startObject(NAME); builder.startObject(fieldName); - geoBoundingBox.toXContentFragment(builder, false); + geoBoundingBox.toXContentFragmentWithArray(builder); builder.endObject(); builder.field(VALIDATION_METHOD_FIELD.getPreferredName(), validationMethod); builder.field(IGNORE_UNMAPPED_FIELD.getPreferredName(), ignoreUnmapped); diff --git a/server/src/main/java/org/elasticsearch/index/query/functionscore/DecayFunctionBuilder.java b/server/src/main/java/org/elasticsearch/index/query/functionscore/DecayFunctionBuilder.java index efc6f6729f822..e358314bade8d 100644 --- a/server/src/main/java/org/elasticsearch/index/query/functionscore/DecayFunctionBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/functionscore/DecayFunctionBuilder.java @@ -382,7 +382,7 @@ public boolean needsScores() { @Override protected NumericDoubleValues distance(LeafReaderContext context) { - final MultiGeoPointValues geoPointValues = fieldData.load(context).getGeoPointValues(); + final MultiGeoPointValues geoPointValues = fieldData.load(context).getPointValues(); return FieldData.replaceMissing(mode.select(new SortingNumericDoubleValues() { @Override public boolean advanceExact(int docId) throws IOException { @@ -413,7 +413,7 @@ public boolean advanceExact(int docId) throws IOException { protected String getDistanceString(LeafReaderContext ctx, int docId) throws IOException { StringBuilder values = new StringBuilder(mode.name()); values.append(" of: ["); - final MultiGeoPointValues geoPointValues = fieldData.load(ctx).getGeoPointValues(); + final MultiGeoPointValues geoPointValues = fieldData.load(ctx).getPointValues(); if (geoPointValues.advanceExact(docId)) { final int num = geoPointValues.docValueCount(); for (int i = 0; i < num; i++) { diff --git a/server/src/main/java/org/elasticsearch/script/field/GeoPointDocValuesField.java b/server/src/main/java/org/elasticsearch/script/field/GeoPointDocValuesField.java index d497630fd34d8..be2e09d63401d 100644 --- a/server/src/main/java/org/elasticsearch/script/field/GeoPointDocValuesField.java +++ b/server/src/main/java/org/elasticsearch/script/field/GeoPointDocValuesField.java @@ -8,182 +8,70 @@ package org.elasticsearch.script.field; -import org.apache.lucene.util.ArrayUtil; import org.elasticsearch.common.geo.GeoBoundingBox; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoUtils; import org.elasticsearch.index.fielddata.MultiGeoPointValues; import org.elasticsearch.index.fielddata.ScriptDocValues; -import java.io.IOException; -import java.util.Iterator; -import java.util.NoSuchElementException; - -public class GeoPointDocValuesField extends AbstractScriptFieldFactory - implements - Field, - DocValuesScriptFieldFactory, - ScriptDocValues.GeometrySupplier { - - protected final MultiGeoPointValues input; - protected final String name; - - protected GeoPoint[] values = new GeoPoint[0]; - protected int count; - +public class GeoPointDocValuesField extends PointDocValuesField { // maintain bwc by making centroid and bounding box available to ScriptDocValues.GeoPoints private ScriptDocValues.GeoPoints geoPoints = null; - private final GeoPoint centroid = new GeoPoint(); - private final GeoBoundingBox boundingBox = new GeoBoundingBox(new GeoPoint(), new GeoPoint()); - private int labelIndex = 0; public GeoPointDocValuesField(MultiGeoPointValues input, String name) { - this.input = input; - this.name = name; + super(input, name, GeoPoint::new, new GeoBoundingBox(new GeoPoint(), new GeoPoint()), new GeoPoint[0]); } @Override - public void setNextDocId(int docId) throws IOException { - if (input.advanceExact(docId)) { - resize(input.docValueCount()); - if (count == 1) { - setSingleValue(); - } else { - setMultiValue(); - } - } else { - resize(0); - } - } - - private void resize(int newSize) { - count = newSize; - if (newSize > values.length) { - int oldLength = values.length; - values = ArrayUtil.grow(values, count); - for (int i = oldLength; i < values.length; ++i) { - values[i] = new GeoPoint(); - } - } - } - - private void setSingleValue() throws IOException { - GeoPoint point = input.nextValue(); - values[0].reset(point.lat(), point.lon()); - centroid.reset(point.lat(), point.lon()); - boundingBox.topLeft().reset(point.lat(), point.lon()); - boundingBox.bottomRight().reset(point.lat(), point.lon()); - labelIndex = 0; - } - - private void setMultiValue() throws IOException { - double centroidLat = 0; - double centroidLon = 0; - labelIndex = 0; - double maxLon = Double.NEGATIVE_INFINITY; - double minLon = Double.POSITIVE_INFINITY; - double maxLat = Double.NEGATIVE_INFINITY; - double minLat = Double.POSITIVE_INFINITY; - for (int i = 0; i < count; i++) { - GeoPoint point = input.nextValue(); - values[i].reset(point.lat(), point.lon()); - centroidLat += point.getLat(); - centroidLon += point.getLon(); - maxLon = Math.max(maxLon, values[i].getLon()); - minLon = Math.min(minLon, values[i].getLon()); - maxLat = Math.max(maxLat, values[i].getLat()); - minLat = Math.min(minLat, values[i].getLat()); - labelIndex = closestPoint(labelIndex, i, (minLat + maxLat) / 2, (minLon + maxLon) / 2); - } - centroid.reset(centroidLat / count, centroidLon / count); - boundingBox.topLeft().reset(maxLat, minLon); - boundingBox.bottomRight().reset(minLat, maxLon); - } - - private int closestPoint(int a, int b, double lat, double lon) { - if (a == b) { - return a; - } - double distA = GeoUtils.planeDistance(lat, lon, values[a].lat(), values[a].lon()); - double distB = GeoUtils.planeDistance(lat, lon, values[b].lat(), values[b].lon()); - return distA < distB ? a : b; + protected void resetPointAt(int i, GeoPoint point) { + values[i].reset(point.lat(), point.lon()); } @Override - public ScriptDocValues toScriptDocValues() { - if (geoPoints == null) { - geoPoints = new ScriptDocValues.GeoPoints(this); - } - - return geoPoints; - } - - @Override - public GeoPoint getInternal(int index) { - return values[index]; + protected void resetCentroidAndBounds(GeoPoint point, GeoPoint topLeft, GeoPoint bottomRight) { + centroid.reset(point.lat() / count, point.lon() / count); + boundingBox.topLeft().reset(topLeft.lat(), topLeft.lon()); + boundingBox.bottomRight().reset(bottomRight.lat(), bottomRight.lon()); } - // maintain bwc by making centroid available to ScriptDocValues.GeoPoints @Override - public GeoPoint getInternalCentroid() { - return centroid; + protected double getXFrom(GeoPoint point) { + return point.lon(); } - // maintain bwc by making bounding box available to ScriptDocValues.GeoPoints @Override - public GeoBoundingBox getInternalBoundingBox() { - return boundingBox; + protected double getYFrom(GeoPoint point) { + return point.lat(); } @Override - public GeoPoint getInternalLabelPosition() { - return values[labelIndex]; + protected GeoPoint pointOf(double x, double y) { + return new GeoPoint(y, x); } @Override - public String getName() { - return name; + protected double planeDistance(double x1, double y1, GeoPoint point) { + return GeoUtils.planeDistance(y1, x1, point.lat(), point.lon()); } @Override - public boolean isEmpty() { - return count == 0; - } - - @Override - public int size() { - return count; - } - public GeoPoint get(GeoPoint defaultValue) { - return get(0, defaultValue); + // While this method seems redundant, it is needed for painless scripting method lookups which cannot handle generics + return super.get(defaultValue); } + @Override public GeoPoint get(int index, GeoPoint defaultValue) { - if (isEmpty() || index < 0 || index >= count) { - return defaultValue; - } - - return values[index]; + // While this method seems redundant, it is needed for painless scripting method lookups which cannot handle generics + return super.get(index, defaultValue); } @Override - public Iterator iterator() { - return new Iterator() { - private int index = 0; - - @Override - public boolean hasNext() { - return index < count; - } + public ScriptDocValues toScriptDocValues() { + if (geoPoints == null) { + geoPoints = new ScriptDocValues.GeoPoints(this); + } - @Override - public GeoPoint next() { - if (hasNext() == false) { - throw new NoSuchElementException(); - } - return values[index++]; - } - }; + return geoPoints; } } diff --git a/server/src/main/java/org/elasticsearch/script/field/PointDocValuesField.java b/server/src/main/java/org/elasticsearch/script/field/PointDocValuesField.java new file mode 100644 index 0000000000000..e863dfa67aacc --- /dev/null +++ b/server/src/main/java/org/elasticsearch/script/field/PointDocValuesField.java @@ -0,0 +1,191 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.script.field; + +import org.apache.lucene.util.ArrayUtil; +import org.elasticsearch.common.geo.BoundingBox; +import org.elasticsearch.common.geo.SpatialPoint; +import org.elasticsearch.index.fielddata.MultiPointValues; +import org.elasticsearch.index.fielddata.ScriptDocValues; + +import java.io.IOException; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.function.Supplier; + +public abstract class PointDocValuesField extends AbstractScriptFieldFactory + implements + Field, + DocValuesScriptFieldFactory, + ScriptDocValues.GeometrySupplier { + + protected final MultiPointValues input; + protected final String name; + + protected T[] values; + protected int count; + + private final Supplier pointMaker; + protected final T centroid; + protected final BoundingBox boundingBox; + private int labelIndex = 0; + + public PointDocValuesField(MultiPointValues input, String name, Supplier pointMaker, BoundingBox boundingBox, T[] values) { + this.input = input; + this.name = name; + this.pointMaker = pointMaker; + this.centroid = pointMaker.get(); + this.boundingBox = boundingBox; + this.values = values; + } + + @Override + public void setNextDocId(int docId) throws IOException { + if (input.advanceExact(docId)) { + resize(input.docValueCount()); + if (count == 1) { + setSingleValue(); + } else { + setMultiValue(); + } + } else { + resize(0); + } + } + + private void resize(int newSize) { + count = newSize; + if (newSize > values.length) { + int oldLength = values.length; + values = ArrayUtil.grow(values, count); + for (int i = oldLength; i < values.length; ++i) { + values[i] = pointMaker.get(); + } + } + } + + protected abstract void resetPointAt(int i, T point); + + protected abstract void resetCentroidAndBounds(T centroid, T topLeft, T bottomRight); + + protected abstract double getXFrom(T point); + + protected abstract double getYFrom(T point); + + protected abstract T pointOf(double x, double y); + + protected abstract double planeDistance(double x1, double y1, T point); + + private void setSingleValue() throws IOException { + T point = input.nextValue(); + resetPointAt(0, point); + resetCentroidAndBounds(point, point, point); + labelIndex = 0; + } + + private void setMultiValue() throws IOException { + double centroidY = 0; + double centroidX = 0; + labelIndex = 0; + double maxX = Double.NEGATIVE_INFINITY; + double minX = Double.POSITIVE_INFINITY; + double maxY = Double.NEGATIVE_INFINITY; + double minY = Double.POSITIVE_INFINITY; + for (int i = 0; i < count; i++) { + T point = input.nextValue(); + resetPointAt(i, point); + centroidX += getXFrom(point); + centroidY += getYFrom(point); + maxX = Math.max(maxX, getXFrom(values[i])); + minX = Math.min(minX, getXFrom(values[i])); + maxY = Math.max(maxY, getYFrom(values[i])); + minY = Math.min(minY, getYFrom(values[i])); + labelIndex = closestPoint(labelIndex, i, (minX + maxX) / 2, (minY + maxY) / 2); + } + resetCentroidAndBounds(pointOf(centroidX, centroidY), pointOf(minX, maxY), pointOf(maxX, minY)); + } + + private int closestPoint(int a, int b, double x, double y) { + if (a == b) { + return a; + } + double distA = planeDistance(x, y, values[a]); + double distB = planeDistance(x, y, values[b]); + return distA < distB ? a : b; + } + + @Override + public T getInternal(int index) { + return values[index]; + } + + // maintain bwc by making centroid available to ScriptDocValues.GeoPoints + @Override + public T getInternalCentroid() { + return centroid; + } + + // maintain bwc by making bounding box available to ScriptDocValues.GeoPoints + @Override + public BoundingBox getInternalBoundingBox() { + return boundingBox; + } + + @Override + public T getInternalLabelPosition() { + return values[labelIndex]; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isEmpty() { + return count == 0; + } + + @Override + public int size() { + return count; + } + + public T get(T defaultValue) { + return get(0, defaultValue); + } + + public T get(int index, T defaultValue) { + if (isEmpty() || index < 0 || index >= count) { + return defaultValue; + } + + return values[index]; + } + + @Override + public Iterator iterator() { + return new Iterator<>() { + private int index = 0; + + @Override + public boolean hasNext() { + return index < count; + } + + @Override + public T next() { + if (hasNext() == false) { + throw new NoSuchElementException(); + } + return values[index++]; + } + }; + } +} diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/composite/GeoTileGridValuesSourceBuilder.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/composite/GeoTileGridValuesSourceBuilder.java index b799cc0475b02..55a9a1f9ecbea 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/composite/GeoTileGridValuesSourceBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/composite/GeoTileGridValuesSourceBuilder.java @@ -158,7 +158,7 @@ protected void doXContentBody(XContentBuilder builder, Params params) throws IOE builder.field("precision", precision); if (geoBoundingBox.isUnbounded() == false) { builder.startObject(GeoBoundingBox.BOUNDS_FIELD.getPreferredName()); - geoBoundingBox.toXContentFragment(builder, true); + geoBoundingBox.toXContentFragment(builder); builder.endObject(); } } diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/CellIdSource.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/CellIdSource.java index 5bd3904953306..54a6a7989f02d 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/CellIdSource.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/CellIdSource.java @@ -169,7 +169,7 @@ protected CellSingleValue(GeoPointValues geoValues, int precision) { @Override public boolean advanceExact(int docId) throws IOException { - return geoValues.advanceExact(docId) && advance(geoValues.geoPointValue()); + return geoValues.advanceExact(docId) && advance(geoValues.pointValue()); } @Override diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoGridAggregationBuilder.java b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoGridAggregationBuilder.java index e97f5b85a65cb..0d5f708d38752 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoGridAggregationBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoGridAggregationBuilder.java @@ -231,7 +231,7 @@ protected XContentBuilder doXContentBody(XContentBuilder builder, Params params) } if (geoBoundingBox.isUnbounded() == false) { builder.startObject(GeoBoundingBox.BOUNDS_FIELD.getPreferredName()); - geoBoundingBox.toXContentFragment(builder, true); + geoBoundingBox.toXContentFragment(builder); builder.endObject(); } return builder; diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/CentroidAggregation.java b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/CentroidAggregation.java new file mode 100644 index 0000000000000..8fcece69b781a --- /dev/null +++ b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/CentroidAggregation.java @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.search.aggregations.metrics; + +import org.elasticsearch.common.geo.SpatialPoint; +import org.elasticsearch.search.aggregations.Aggregation; + +/** + * Generic interface for both geographic and cartesian centroid aggregations. + */ +public interface CentroidAggregation extends Aggregation { + SpatialPoint centroid(); + + long count(); +} diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/GeoCentroid.java b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/GeoCentroid.java index b5b5d2f186462..5a403702545a8 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/GeoCentroid.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/GeoCentroid.java @@ -8,14 +8,7 @@ package org.elasticsearch.search.aggregations.metrics; -import org.elasticsearch.common.geo.GeoPoint; -import org.elasticsearch.search.aggregations.Aggregation; - /** * Interface for {@link GeoCentroidAggregator} */ -public interface GeoCentroid extends Aggregation { - GeoPoint centroid(); - - long count(); -} +public interface GeoCentroid extends CentroidAggregation {} diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalCentroid.java b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalCentroid.java new file mode 100644 index 0000000000000..3179d9064b1b8 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalCentroid.java @@ -0,0 +1,193 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.search.aggregations.metrics; + +import org.elasticsearch.common.geo.SpatialPoint; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.search.aggregations.AggregationReduceContext; +import org.elasticsearch.search.aggregations.InternalAggregation; +import org.elasticsearch.search.aggregations.support.SamplingContext; +import org.elasticsearch.xcontent.ParseField; +import org.elasticsearch.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +/** + * Serialization and merge logic for {@link GeoCentroidAggregator}. + */ +public abstract class InternalCentroid extends InternalAggregation implements CentroidAggregation { + protected final SpatialPoint centroid; + protected final long count; + private final FieldExtractor firstField; + private final FieldExtractor secondField; + + public InternalCentroid( + String name, + SpatialPoint centroid, + long count, + Map metadata, + FieldExtractor firstField, + FieldExtractor secondField + ) { + super(name, metadata); + assert (centroid == null) == (count == 0); + this.centroid = centroid; + assert count >= 0; + this.count = count; + this.firstField = firstField; + this.secondField = secondField; + } + + protected abstract SpatialPoint centroidFromStream(StreamInput in) throws IOException; + + protected abstract void centroidToStream(StreamOutput out) throws IOException; + + /** + * Read from a stream. + */ + protected InternalCentroid(StreamInput in, FieldExtractor firstField, FieldExtractor secondField) throws IOException { + super(in); + count = in.readVLong(); + if (in.readBoolean()) { + centroid = centroidFromStream(in); + } else { + centroid = null; + } + this.firstField = firstField; + this.secondField = secondField; + } + + @Override + protected void doWriteTo(StreamOutput out) throws IOException { + out.writeVLong(count); + if (centroid != null) { + out.writeBoolean(true); + centroidToStream(out); + } else { + out.writeBoolean(false); + } + } + + @Override + public SpatialPoint centroid() { + return centroid; + } + + @Override + public long count() { + return count; + } + + protected abstract InternalCentroid copyWith(SpatialPoint result, long count); + + /** Create a new centroid with by reducing from the sums and total count */ + protected abstract InternalCentroid copyWith(double firstSum, double secondSum, long totalCount); + + @Override + public InternalCentroid reduce(List aggregations, AggregationReduceContext reduceContext) { + double firstSum = Double.NaN; + double secondSum = Double.NaN; + long totalCount = 0; + for (InternalAggregation aggregation : aggregations) { + InternalCentroid centroidAgg = (InternalCentroid) aggregation; + if (centroidAgg.count > 0) { + totalCount += centroidAgg.count; + if (Double.isNaN(firstSum)) { + firstSum = centroidAgg.count * firstField.extractor.apply(centroidAgg.centroid); + secondSum = centroidAgg.count * secondField.extractor.apply(centroidAgg.centroid); + } else { + firstSum += centroidAgg.count * firstField.extractor.apply(centroidAgg.centroid); + secondSum += centroidAgg.count * secondField.extractor.apply(centroidAgg.centroid); + } + } + } + return copyWith(firstSum, secondSum, totalCount); + } + + @Override + public InternalAggregation finalizeSampling(SamplingContext samplingContext) { + return copyWith(centroid, samplingContext.scaleUp(count)); + } + + @Override + protected boolean mustReduceOnSingleInternalAgg() { + return false; + } + + protected static class FieldExtractor { + private final String name; + private final Function extractor; + + public FieldExtractor(String name, Function extractor) { + this.name = name; + this.extractor = extractor; + } + } + + protected abstract double extractDouble(String name); + + @Override + public Object getProperty(List path) { + if (path.isEmpty()) { + return this; + } else if (path.size() == 1) { + String coordinate = path.get(0); + return switch (coordinate) { + case "value" -> centroid; + case "count" -> count; + default -> extractDouble(coordinate); + }; + } else { + throw new IllegalArgumentException("path not supported for [" + getName() + "]: " + path); + } + } + + protected static class Fields { + static final ParseField CENTROID = new ParseField("location"); + static final ParseField COUNT = new ParseField("count"); + } + + @Override + public XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException { + if (centroid != null) { + builder.startObject(Fields.CENTROID.getPreferredName()); + { + builder.field(firstField.name, firstField.extractor.apply(centroid)); + builder.field(secondField.name, secondField.extractor.apply(centroid)); + } + builder.endObject(); + } + builder.field(Fields.COUNT.getPreferredName(), count); + return builder; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + if (super.equals(obj) == false) return false; + InternalCentroid that = (InternalCentroid) obj; + return count == that.count && Objects.equals(centroid, that.centroid); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), centroid, count); + } + + @Override + public String toString() { + return "InternalCentroid{" + "centroid=" + centroid + ", count=" + count + '}'; + } +} diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalGeoBounds.java b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalGeoBounds.java index 113ac7a562c0c..77cfa6ecc00e3 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalGeoBounds.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalGeoBounds.java @@ -165,7 +165,7 @@ public XContentBuilder doXContentBody(XContentBuilder builder, Params params) th GeoBoundingBox bbox = resolveGeoBoundingBox(); if (bbox != null) { builder.startObject(GeoBoundingBox.BOUNDS_FIELD.getPreferredName()); - bbox.toXContentFragment(builder, true); + bbox.toXContentFragment(builder); builder.endObject(); } return builder; diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalGeoCentroid.java b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalGeoCentroid.java index 3d0607521f3a2..be441c215b4fd 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalGeoCentroid.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalGeoCentroid.java @@ -11,80 +11,70 @@ import org.apache.lucene.geo.GeoEncodingUtils; import org.elasticsearch.Version; import org.elasticsearch.common.geo.GeoPoint; +import org.elasticsearch.common.geo.SpatialPoint; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.search.aggregations.AggregationReduceContext; import org.elasticsearch.search.aggregations.InternalAggregation; import org.elasticsearch.search.aggregations.support.SamplingContext; import org.elasticsearch.xcontent.ParseField; -import org.elasticsearch.xcontent.XContentBuilder; import java.io.IOException; -import java.util.List; import java.util.Map; -import java.util.Objects; /** * Serialization and merge logic for {@link GeoCentroidAggregator}. */ -public class InternalGeoCentroid extends InternalAggregation implements GeoCentroid { - private final GeoPoint centroid; - private final long count; +public class InternalGeoCentroid extends InternalCentroid implements GeoCentroid { - public static long encodeLatLon(double lat, double lon) { + private static long encodeLatLon(double lat, double lon) { return (Integer.toUnsignedLong(GeoEncodingUtils.encodeLatitude(lat)) << 32) | Integer.toUnsignedLong( GeoEncodingUtils.encodeLongitude(lon) ); } - public static double decodeLatitude(long encodedLatLon) { + private static double decodeLatitude(long encodedLatLon) { return GeoEncodingUtils.decodeLatitude((int) (encodedLatLon >>> 32)); } - public static double decodeLongitude(long encodedLatLon) { + private static double decodeLongitude(long encodedLatLon) { return GeoEncodingUtils.decodeLongitude((int) (encodedLatLon & 0xFFFFFFFFL)); } - public InternalGeoCentroid(String name, GeoPoint centroid, long count, Map metadata) { - super(name, metadata); - assert (centroid == null) == (count == 0); - this.centroid = centroid; - assert count >= 0; - this.count = count; + public InternalGeoCentroid(String name, SpatialPoint centroid, long count, Map metadata) { + super( + name, + centroid, + count, + metadata, + new FieldExtractor("lat", SpatialPoint::getY), + new FieldExtractor("lon", SpatialPoint::getX) + ); } /** * Read from a stream. */ public InternalGeoCentroid(StreamInput in) throws IOException { - super(in); - count = in.readVLong(); - if (in.readBoolean()) { - if (in.getVersion().onOrAfter(Version.V_7_2_0)) { - centroid = new GeoPoint(in.readDouble(), in.readDouble()); - } else { - final long hash = in.readLong(); - centroid = new GeoPoint(decodeLatitude(hash), decodeLongitude(hash)); - } + super(in, new FieldExtractor("lat", SpatialPoint::getY), new FieldExtractor("lon", SpatialPoint::getX)); + } + @Override + protected GeoPoint centroidFromStream(StreamInput in) throws IOException { + if (in.getVersion().onOrAfter(Version.V_7_2_0)) { + return new GeoPoint(in.readDouble(), in.readDouble()); } else { - centroid = null; + final long hash = in.readLong(); + return new GeoPoint(decodeLatitude(hash), decodeLongitude(hash)); } } @Override - protected void doWriteTo(StreamOutput out) throws IOException { - out.writeVLong(count); - if (centroid != null) { - out.writeBoolean(true); - if (out.getVersion().onOrAfter(Version.V_7_2_0)) { - out.writeDouble(centroid.lat()); - out.writeDouble(centroid.lon()); - } else { - out.writeLong(encodeLatLon(centroid.lat(), centroid.lon())); - } + protected void centroidToStream(StreamOutput out) throws IOException { + if (out.getVersion().onOrAfter(Version.V_7_2_0)) { + out.writeDouble(centroid.getY()); + out.writeDouble(centroid.getX()); } else { - out.writeBoolean(false); + out.writeLong(encodeLatLon(centroid.getY(), centroid.getX())); } } @@ -94,35 +84,23 @@ public String getWriteableName() { } @Override - public GeoPoint centroid() { - return centroid; + protected double extractDouble(String name) { + return switch (name) { + case "lat" -> centroid.getY(); + case "lon" -> centroid.getX(); + default -> throw new IllegalArgumentException("Found unknown path element [" + name + "] in [" + getName() + "]"); + }; } @Override - public long count() { - return count; + protected InternalGeoCentroid copyWith(SpatialPoint result, long count) { + return new InternalGeoCentroid(name, result, count, getMetadata()); } @Override - public InternalGeoCentroid reduce(List aggregations, AggregationReduceContext reduceContext) { - double lonSum = Double.NaN; - double latSum = Double.NaN; - long totalCount = 0; - for (InternalAggregation aggregation : aggregations) { - InternalGeoCentroid centroidAgg = (InternalGeoCentroid) aggregation; - if (centroidAgg.count > 0) { - totalCount += centroidAgg.count; - if (Double.isNaN(lonSum)) { - lonSum = centroidAgg.count * centroidAgg.centroid.getLon(); - latSum = centroidAgg.count * centroidAgg.centroid.getLat(); - } else { - lonSum += (centroidAgg.count * centroidAgg.centroid.getLon()); - latSum += (centroidAgg.count * centroidAgg.centroid.getLat()); - } - } - } - final GeoPoint result = (Double.isNaN(lonSum)) ? null : new GeoPoint(latSum / totalCount, lonSum / totalCount); - return new InternalGeoCentroid(name, result, totalCount, getMetadata()); + protected InternalGeoCentroid copyWith(double firstSum, double secondSum, long totalCount) { + final GeoPoint result = (Double.isNaN(firstSum)) ? null : new GeoPoint(firstSum / totalCount, secondSum / totalCount); + return copyWith(result, totalCount); } @Override @@ -130,66 +108,8 @@ public InternalAggregation finalizeSampling(SamplingContext samplingContext) { return new InternalGeoCentroid(name, centroid, samplingContext.scaleUp(count), getMetadata()); } - @Override - protected boolean mustReduceOnSingleInternalAgg() { - return false; - } - - @Override - public Object getProperty(List path) { - if (path.isEmpty()) { - return this; - } else if (path.size() == 1) { - String coordinate = path.get(0); - return switch (coordinate) { - case "value" -> centroid; - case "lat" -> centroid.lat(); - case "lon" -> centroid.lon(); - case "count" -> count; - default -> throw new IllegalArgumentException("Found unknown path element [" + coordinate + "] in [" + getName() + "]"); - }; - } else { - throw new IllegalArgumentException("path not supported for [" + getName() + "]: " + path); - } - } - static class Fields { - static final ParseField CENTROID = new ParseField("location"); - static final ParseField COUNT = new ParseField("count"); static final ParseField CENTROID_LAT = new ParseField("lat"); static final ParseField CENTROID_LON = new ParseField("lon"); } - - @Override - public XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException { - if (centroid != null) { - builder.startObject(Fields.CENTROID.getPreferredName()); - { - builder.field(Fields.CENTROID_LAT.getPreferredName(), centroid.lat()); - builder.field(Fields.CENTROID_LON.getPreferredName(), centroid.lon()); - } - builder.endObject(); - } - builder.field(Fields.COUNT.getPreferredName(), count); - return builder; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null || getClass() != obj.getClass()) return false; - if (super.equals(obj) == false) return false; - InternalGeoCentroid that = (InternalGeoCentroid) obj; - return count == that.count && Objects.equals(centroid, that.centroid); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), centroid, count); - } - - @Override - public String toString() { - return "InternalGeoCentroid{" + "centroid=" + centroid + ", count=" + count + '}'; - } } diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/ParsedGeoBounds.java b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/ParsedGeoBounds.java index 676f1a34ebb08..fed48ec7640e3 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/ParsedGeoBounds.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/ParsedGeoBounds.java @@ -42,7 +42,7 @@ public String getType() { public XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException { if (geoBoundingBox != null) { builder.startObject(GeoBoundingBox.BOUNDS_FIELD.getPreferredName()); - geoBoundingBox.toXContentFragment(builder, true); + geoBoundingBox.toXContentFragment(builder); builder.endObject(); } return builder; diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/ParsedGeoCentroid.java b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/ParsedGeoCentroid.java index 03563d408b6ff..31a44959becd3 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/ParsedGeoCentroid.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/ParsedGeoCentroid.java @@ -42,14 +42,14 @@ public String getType() { @Override public XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException { if (centroid != null) { - builder.startObject(Fields.CENTROID.getPreferredName()); + builder.startObject(InternalCentroid.Fields.CENTROID.getPreferredName()); { builder.field(Fields.CENTROID_LAT.getPreferredName(), centroid.lat()); builder.field(Fields.CENTROID_LON.getPreferredName(), centroid.lon()); } builder.endObject(); } - builder.field(Fields.COUNT.getPreferredName(), count); + builder.field(InternalCentroid.Fields.COUNT.getPreferredName(), count); return builder; } @@ -67,8 +67,8 @@ public XContentBuilder doXContentBody(XContentBuilder builder, Params params) th static { declareAggregationFields(PARSER); - PARSER.declareObject((agg, centroid) -> agg.centroid = centroid, GEO_POINT_PARSER, Fields.CENTROID); - PARSER.declareLong((agg, count) -> agg.count = count, Fields.COUNT); + PARSER.declareObject((agg, centroid) -> agg.centroid = centroid, GEO_POINT_PARSER, InternalCentroid.Fields.CENTROID); + PARSER.declareLong((agg, count) -> agg.count = count, InternalCentroid.Fields.COUNT); GEO_POINT_PARSER.declareDouble(GeoPoint::resetLat, Fields.CENTROID_LAT); GEO_POINT_PARSER.declareDouble(GeoPoint::resetLon, Fields.CENTROID_LON); diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/support/AggregationInspectionHelper.java b/server/src/main/java/org/elasticsearch/search/aggregations/support/AggregationInspectionHelper.java index e1bb0914cdcd3..49cae8e0a72c2 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/support/AggregationInspectionHelper.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/support/AggregationInspectionHelper.java @@ -29,9 +29,9 @@ import org.elasticsearch.search.aggregations.bucket.terms.UnmappedTerms; import org.elasticsearch.search.aggregations.metrics.InternalAvg; import org.elasticsearch.search.aggregations.metrics.InternalCardinality; +import org.elasticsearch.search.aggregations.metrics.InternalCentroid; import org.elasticsearch.search.aggregations.metrics.InternalExtendedStats; import org.elasticsearch.search.aggregations.metrics.InternalGeoBounds; -import org.elasticsearch.search.aggregations.metrics.InternalGeoCentroid; import org.elasticsearch.search.aggregations.metrics.InternalHDRPercentileRanks; import org.elasticsearch.search.aggregations.metrics.InternalHDRPercentiles; import org.elasticsearch.search.aggregations.metrics.InternalMedianAbsoluteDeviation; @@ -167,7 +167,7 @@ public static boolean hasValue(InternalGeoBounds agg) { return (agg.topLeft() == null && agg.bottomRight() == null) == false; } - public static boolean hasValue(InternalGeoCentroid agg) { + public static boolean hasValue(InternalCentroid agg) { return agg.centroid() != null && agg.count() > 0; } diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/support/CoreValuesSourceType.java b/server/src/main/java/org/elasticsearch/search/aggregations/support/CoreValuesSourceType.java index e41e0d29691e8..29d80339798dc 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/support/CoreValuesSourceType.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/support/CoreValuesSourceType.java @@ -162,13 +162,12 @@ public ValuesSource getScript(AggregationScript.LeafFactory script, ValueType sc @Override public ValuesSource getField(FieldContext fieldContext, AggregationScript.LeafFactory script, AggregationContext context) { - if ((fieldContext.indexFieldData() instanceof IndexGeoPointFieldData) == false) { - throw new IllegalArgumentException( - "Expected geo_point type on field [" + fieldContext.field() + "], but got [" + fieldContext.fieldType().typeName() + "]" - ); + if (fieldContext.indexFieldData()instanceof IndexGeoPointFieldData pointFieldData) { + return new ValuesSource.GeoPoint.Fielddata(pointFieldData); } - - return new ValuesSource.GeoPoint.Fielddata((IndexGeoPointFieldData) fieldContext.indexFieldData()); + throw new IllegalArgumentException( + "Expected geo_point type on field [" + fieldContext.field() + "], but got [" + fieldContext.fieldType().typeName() + "]" + ); } @Override diff --git a/server/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortBuilder.java b/server/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortBuilder.java index 4d842da11163d..f58e24c686624 100644 --- a/server/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/sort/GeoDistanceSortBuilder.java @@ -36,7 +36,7 @@ import org.elasticsearch.index.fielddata.MultiGeoPointValues; import org.elasticsearch.index.fielddata.NumericDoubleValues; import org.elasticsearch.index.fielddata.SortedNumericDoubleValues; -import org.elasticsearch.index.fielddata.plain.AbstractLatLonPointIndexFieldData.LatLonPointIndexFieldData; +import org.elasticsearch.index.fielddata.plain.LatLonPointIndexFieldData; import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.query.GeoValidationMethod; import org.elasticsearch.index.query.QueryBuilder; @@ -642,7 +642,7 @@ public SortField.Type reducedType() { } private NumericDoubleValues getNumericDoubleValues(LeafReaderContext context) throws IOException { - final MultiGeoPointValues geoPointValues = geoIndexFieldData.load(context).getGeoPointValues(); + final MultiGeoPointValues geoPointValues = geoIndexFieldData.load(context).getPointValues(); final SortedNumericDoubleValues distanceValues = GeoUtils.distanceValues(geoDistance, unit, geoPointValues, localPoints); if (nested == null) { return FieldData.replaceMissing(sortMode.select(distanceValues), Double.POSITIVE_INFINITY); diff --git a/server/src/test/java/org/elasticsearch/index/fielddata/GeoFieldDataTests.java b/server/src/test/java/org/elasticsearch/index/fielddata/GeoFieldDataTests.java index ebf3cb91d88a0..9200aa0c236d9 100644 --- a/server/src/test/java/org/elasticsearch/index/fielddata/GeoFieldDataTests.java +++ b/server/src/test/java/org/elasticsearch/index/fielddata/GeoFieldDataTests.java @@ -12,7 +12,7 @@ import org.apache.lucene.document.StringField; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.index.Term; -import org.elasticsearch.index.fielddata.plain.AbstractLeafGeoPointFieldData; +import org.elasticsearch.index.fielddata.plain.LeafGeoPointFieldData; import java.util.List; @@ -149,7 +149,7 @@ public void testSingleValueAllSet() throws Exception { LeafFieldData fieldData = indexFieldData.load(readerContext); assertThat(fieldData.ramBytesUsed(), greaterThanOrEqualTo(minRamBytesUsed())); - MultiGeoPointValues fieldValues = ((AbstractLeafGeoPointFieldData) fieldData).getGeoPointValues(); + MultiGeoPointValues fieldValues = ((LeafGeoPointFieldData) fieldData).getPointValues(); assertValues(fieldValues, 0); assertValues(fieldValues, 1); assertValues(fieldValues, 2); @@ -165,7 +165,7 @@ public void testSingleValueWithMissing() throws Exception { LeafFieldData fieldData = indexFieldData.load(readerContext); assertThat(fieldData.ramBytesUsed(), greaterThanOrEqualTo(minRamBytesUsed())); - MultiGeoPointValues fieldValues = ((AbstractLeafGeoPointFieldData) fieldData).getGeoPointValues(); + MultiGeoPointValues fieldValues = ((LeafGeoPointFieldData) fieldData).getPointValues(); assertValues(fieldValues, 0); assertMissing(fieldValues, 1); assertValues(fieldValues, 2); @@ -181,7 +181,7 @@ public void testMultiValueAllSet() throws Exception { LeafFieldData fieldData = indexFieldData.load(readerContext); assertThat(fieldData.ramBytesUsed(), greaterThanOrEqualTo(minRamBytesUsed())); - MultiGeoPointValues fieldValues = ((AbstractLeafGeoPointFieldData) fieldData).getGeoPointValues(); + MultiGeoPointValues fieldValues = ((LeafGeoPointFieldData) fieldData).getPointValues(); assertValues(fieldValues, 0); assertValues(fieldValues, 1); assertValues(fieldValues, 2); @@ -197,7 +197,7 @@ public void testMultiValueWithMissing() throws Exception { LeafFieldData fieldData = indexFieldData.load(readerContext); assertThat(fieldData.ramBytesUsed(), greaterThanOrEqualTo(minRamBytesUsed())); - MultiGeoPointValues fieldValues = ((AbstractLeafGeoPointFieldData) fieldData).getGeoPointValues(); + MultiGeoPointValues fieldValues = ((LeafGeoPointFieldData) fieldData).getPointValues(); assertValues(fieldValues, 0); assertMissing(fieldValues, 1); diff --git a/server/src/test/java/org/elasticsearch/index/mapper/GeoPointScriptFieldTypeTests.java b/server/src/test/java/org/elasticsearch/index/mapper/GeoPointScriptFieldTypeTests.java index 6537e67cbbdec..97f7d3163a2b4 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/GeoPointScriptFieldTypeTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/GeoPointScriptFieldTypeTests.java @@ -74,7 +74,7 @@ public ScoreMode scoreMode() { @Override public LeafCollector getLeafCollector(LeafReaderContext context) { - MultiGeoPointValues dv = ifd.load(context).getGeoPointValues(); + MultiGeoPointValues dv = ifd.load(context).getPointValues(); return new LeafCollector() { @Override public void setScorer(Scorable scorer) {} diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/AbstractGeoTestCase.java b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/AbstractGeoTestCase.java index 6db70d83cf483..02572eb461fb5 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/AbstractGeoTestCase.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/AbstractGeoTestCase.java @@ -13,6 +13,7 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.common.geo.GeoPoint; +import org.elasticsearch.common.geo.SpatialPoint; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.geometry.utils.Geohash; import org.elasticsearch.search.SearchHit; @@ -32,6 +33,7 @@ import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse; import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder; +import static org.hamcrest.Matchers.closeTo; import static org.hamcrest.Matchers.equalTo; @ESIntegTestCase.SuiteScopeTestCase @@ -285,4 +287,18 @@ private void updateBoundsTopLeft(GeoPoint geoPoint, GeoPoint currentBound) { currentBound.resetLon(geoPoint.lon()); } } + + protected void assertSameCentroid(SpatialPoint centroid, SpatialPoint expectedCentroid) { + String[] names = centroid.getClass() == GeoPoint.class ? new String[] { "longitude", "latitude" } : new String[] { "x", "y" }; + assertThat( + "Mismatching value for '" + names[0] + "' field of centroid", + centroid.getX(), + closeTo(expectedCentroid.getX(), GEOHASH_TOLERANCE) + ); + assertThat( + "Mismatching value for '" + names[1] + "' field of centroid", + centroid.getY(), + closeTo(expectedCentroid.getY(), GEOHASH_TOLERANCE) + ); + } } diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/GeoCentroidAggregatorTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/GeoCentroidAggregatorTests.java index 3aeca19bb5f85..a10310e13a20e 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/GeoCentroidAggregatorTests.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/GeoCentroidAggregatorTests.java @@ -16,6 +16,7 @@ import org.apache.lucene.store.Directory; import org.apache.lucene.tests.index.RandomIndexWriter; import org.elasticsearch.common.geo.GeoPoint; +import org.elasticsearch.common.geo.SpatialPoint; import org.elasticsearch.index.mapper.GeoPointFieldMapper; import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.search.aggregations.AggregationBuilder; @@ -152,10 +153,10 @@ private void assertCentroid(RandomIndexWriter w, GeoPoint expectedCentroid) thro InternalGeoCentroid result = searchAndReduce(searcher, new MatchAllDocsQuery(), aggBuilder, fieldType); assertEquals("my_agg", result.getName()); - GeoPoint centroid = result.centroid(); + SpatialPoint centroid = result.centroid(); assertNotNull(centroid); - assertEquals(expectedCentroid.getLat(), centroid.getLat(), GEOHASH_TOLERANCE); - assertEquals(expectedCentroid.getLon(), centroid.getLon(), GEOHASH_TOLERANCE); + assertEquals(expectedCentroid.getX(), centroid.getX(), GEOHASH_TOLERANCE); + assertEquals(expectedCentroid.getY(), centroid.getY(), GEOHASH_TOLERANCE); assertTrue(AggregationInspectionHelper.hasValue(result)); } } diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/InternalGeoCentroidTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/InternalGeoCentroidTests.java index 212a2c0127e5b..7cc7209ca0ea8 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/InternalGeoCentroidTests.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/InternalGeoCentroidTests.java @@ -9,6 +9,7 @@ import org.apache.lucene.geo.GeoEncodingUtils; import org.elasticsearch.common.geo.GeoPoint; +import org.elasticsearch.common.geo.SpatialPoint; import org.elasticsearch.common.util.Maps; import org.elasticsearch.search.aggregations.ParsedAggregation; import org.elasticsearch.search.aggregations.support.SamplingContext; @@ -48,14 +49,14 @@ protected void assertReduced(InternalGeoCentroid reduced, List 0) { - lonSum += (input.count() * input.centroid().getLon()); - latSum += (input.count() * input.centroid().getLat()); + lonSum += (input.count() * input.centroid().getX()); + latSum += (input.count() * input.centroid().getY()); } totalCount += input.count(); } if (totalCount > 0) { - assertEquals(latSum / totalCount, reduced.centroid().getLat(), 1E-5D); - assertEquals(lonSum / totalCount, reduced.centroid().getLon(), 1E-5D); + assertEquals(latSum / totalCount, reduced.centroid().getY(), 1E-5D); + assertEquals(lonSum / totalCount, reduced.centroid().getX(), 1E-5D); } assertEquals(totalCount, reduced.count()); } @@ -67,8 +68,8 @@ protected boolean supportsSampling() { @Override protected void assertSampled(InternalGeoCentroid sampled, InternalGeoCentroid reduced, SamplingContext samplingContext) { - assertEquals(sampled.centroid().getLat(), reduced.centroid().getLat(), 1e-12); - assertEquals(sampled.centroid().getLon(), reduced.centroid().getLon(), 1e-12); + assertEquals(sampled.centroid().getY(), reduced.centroid().getY(), 1e-12); + assertEquals(sampled.centroid().getX(), reduced.centroid().getX(), 1e-12); assertEquals(sampled.count(), samplingContext.scaleUp(reduced.count()), 0); } @@ -79,7 +80,7 @@ public void testReduceMaxCount() { Long.MAX_VALUE, Collections.emptyMap() ); - InternalGeoCentroid reducedGeoCentroid = maxValueGeoCentroid.reduce(Collections.singletonList(maxValueGeoCentroid), null); + InternalCentroid reducedGeoCentroid = maxValueGeoCentroid.reduce(Collections.singletonList(maxValueGeoCentroid), null); assertThat(reducedGeoCentroid.count(), equalTo(Long.MAX_VALUE)); } @@ -95,7 +96,7 @@ protected void assertFromXContent(InternalGeoCentroid aggregation, ParsedAggrega @Override protected InternalGeoCentroid mutateInstance(InternalGeoCentroid instance) { String name = instance.getName(); - GeoPoint centroid = instance.centroid(); + SpatialPoint centroid = instance.centroid(); long count = instance.count(); Map metadata = instance.getMetadata(); switch (between(0, 3)) { @@ -115,9 +116,9 @@ protected InternalGeoCentroid mutateInstance(InternalGeoCentroid instance) { } else { GeoPoint newCentroid = new GeoPoint(centroid); if (randomBoolean()) { - newCentroid.resetLat(centroid.getLat() / 2.0); + newCentroid.resetLat(centroid.getY() / 2.0); } else { - newCentroid.resetLon(centroid.getLon() / 2.0); + newCentroid.resetLon(centroid.getX() / 2.0); } centroid = newCentroid; } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/transforms/pivot/GeoTileGroupSource.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/transforms/pivot/GeoTileGroupSource.java index ad488e48485ae..68109f429f461 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/transforms/pivot/GeoTileGroupSource.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/transforms/pivot/GeoTileGroupSource.java @@ -106,7 +106,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws } if (geoBoundingBox != null) { builder.startObject(GeoBoundingBox.BOUNDS_FIELD.getPreferredName()); - geoBoundingBox.toXContentFragment(builder, true); + geoBoundingBox.toXContentFragment(builder); builder.endObject(); } builder.endObject(); diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/datafeed/extractor/aggregation/AggregationToJsonProcessor.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/datafeed/extractor/aggregation/AggregationToJsonProcessor.java index 170ca97ce7731..35e5ac4df0de1 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/datafeed/extractor/aggregation/AggregationToJsonProcessor.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/datafeed/extractor/aggregation/AggregationToJsonProcessor.java @@ -202,7 +202,7 @@ private void processAggs(long docCount, List aggregations) throws I queueDocToWrite(keyValuePairs, docCount); } - addedLeafKeys.forEach(k -> keyValuePairs.remove(k)); + addedLeafKeys.forEach(keyValuePairs::remove); } private void processDateHistogram(Histogram agg) throws IOException { @@ -400,7 +400,7 @@ private boolean addMetricIfFinite(String key, double value) { private boolean processGeoCentroid(GeoCentroid agg) { if (agg.count() > 0) { - keyValuePairs.put(agg.getName(), agg.centroid().getLat() + "," + agg.centroid().getLon()); + keyValuePairs.put(agg.getName(), agg.centroid().getY() + "," + agg.centroid().getX()); return true; } return false; diff --git a/x-pack/plugin/spatial/src/internalClusterTest/java/org/elasticsearch/xpack/spatial/search/GeoShapeScriptDocValuesIT.java b/x-pack/plugin/spatial/src/internalClusterTest/java/org/elasticsearch/xpack/spatial/search/GeoShapeScriptDocValuesIT.java index 9ae7726e32a44..f41f78587cbf8 100644 --- a/x-pack/plugin/spatial/src/internalClusterTest/java/org/elasticsearch/xpack/spatial/search/GeoShapeScriptDocValuesIT.java +++ b/x-pack/plugin/spatial/src/internalClusterTest/java/org/elasticsearch/xpack/spatial/search/GeoShapeScriptDocValuesIT.java @@ -9,7 +9,8 @@ import org.apache.lucene.geo.Circle; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.common.document.DocumentField; -import org.elasticsearch.common.geo.GeoBoundingBox; +import org.elasticsearch.common.geo.BoundingBox; +import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.Orientation; import org.elasticsearch.geo.GeometryTestUtils; import org.elasticsearch.geometry.Geometry; @@ -20,7 +21,6 @@ import org.elasticsearch.geometry.Polygon; import org.elasticsearch.geometry.utils.GeographyValidator; import org.elasticsearch.geometry.utils.WellKnownText; -import org.elasticsearch.index.fielddata.ScriptDocValues; import org.elasticsearch.index.mapper.GeoShapeIndexer; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.script.MockScriptPlugin; @@ -34,6 +34,8 @@ import org.elasticsearch.xpack.spatial.index.fielddata.DimensionalShapeType; import org.elasticsearch.xpack.spatial.index.fielddata.GeoRelation; import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues; +import org.elasticsearch.xpack.spatial.index.fielddata.LeafShapeFieldData; +import org.elasticsearch.xpack.spatial.index.fielddata.plain.AbstractAtomicGeoShapeShapeFieldData; import org.elasticsearch.xpack.spatial.util.GeoTestUtils; import org.hamcrest.Matchers; import org.junit.Before; @@ -79,52 +81,53 @@ protected Map, Object>> pluginScripts() { private double scriptHeight(Map vars) { Map doc = (Map) vars.get("doc"); - ScriptDocValues.Geometry geometry = assertGeometry(doc); + LeafShapeFieldData.ShapeScriptValues geometry = assertGeometry(doc); if (geometry.size() == 0) { return Double.NaN; } else { - GeoBoundingBox boundingBox = geometry.getBoundingBox(); + BoundingBox boundingBox = geometry.getBoundingBox(); return boundingBox.topLeft().lat() - boundingBox.bottomRight().lat(); } } private double scriptWidth(Map vars) { Map doc = (Map) vars.get("doc"); - ScriptDocValues.Geometry geometry = assertGeometry(doc); + LeafShapeFieldData.ShapeScriptValues geometry = assertGeometry(doc); if (geometry.size() == 0) { return Double.NaN; } else { - GeoBoundingBox boundingBox = geometry.getBoundingBox(); + BoundingBox boundingBox = geometry.getBoundingBox(); return boundingBox.bottomRight().lon() - boundingBox.topLeft().lon(); } } private double scriptLat(Map vars) { Map doc = (Map) vars.get("doc"); - ScriptDocValues.Geometry geometry = assertGeometry(doc); + LeafShapeFieldData.ShapeScriptValues geometry = assertGeometry(doc); return geometry.size() == 0 ? Double.NaN : geometry.getCentroid().lat(); } private double scriptLon(Map vars) { Map doc = (Map) vars.get("doc"); - ScriptDocValues.Geometry geometry = assertGeometry(doc); + LeafShapeFieldData.ShapeScriptValues geometry = assertGeometry(doc); return geometry.size() == 0 ? Double.NaN : geometry.getCentroid().lon(); } private double scriptLabelLat(Map vars) { Map doc = (Map) vars.get("doc"); - ScriptDocValues.Geometry geometry = assertGeometry(doc); + LeafShapeFieldData.ShapeScriptValues geometry = assertGeometry(doc); return geometry.size() == 0 ? Double.NaN : geometry.getLabelPosition().lat(); } private double scriptLabelLon(Map vars) { Map doc = (Map) vars.get("doc"); - ScriptDocValues.Geometry geometry = assertGeometry(doc); + LeafShapeFieldData.ShapeScriptValues geometry = assertGeometry(doc); return geometry.size() == 0 ? Double.NaN : geometry.getLabelPosition().lon(); } - private ScriptDocValues.Geometry assertGeometry(Map doc) { - ScriptDocValues.Geometry geometry = (ScriptDocValues.Geometry) doc.get("location"); + private LeafShapeFieldData.ShapeScriptValues assertGeometry(Map doc) { + AbstractAtomicGeoShapeShapeFieldData.GeoShapeScriptValues geometry = + (AbstractAtomicGeoShapeShapeFieldData.GeoShapeScriptValues) doc.get("location"); if (geometry.size() == 0) { assertThat(geometry.getBoundingBox(), Matchers.nullValue()); assertThat(geometry.getCentroid(), Matchers.nullValue()); @@ -267,8 +270,8 @@ private void doTestGeometry(Geometry geometry, GeoShapeValues.GeoShapeValue expe .get(); assertSearchResponse(searchResponse); Map fields = searchResponse.getHits().getHits()[0].getFields(); - assertThat(fields.get("lat").getValue(), equalTo(value.lat())); - assertThat(fields.get("lon").getValue(), equalTo(value.lon())); + assertThat(fields.get("lat").getValue(), equalTo(value.getY())); + assertThat(fields.get("lon").getValue(), equalTo(value.getX())); assertThat(fields.get("height").getValue(), equalTo(value.boundingBox().maxY() - value.boundingBox().minY())); assertThat(fields.get("width").getValue(), equalTo(value.boundingBox().maxX() - value.boundingBox().minX())); @@ -285,16 +288,21 @@ private void doTestGeometry(Geometry geometry, GeoShapeValues.GeoShapeValue expe doTestLabelPosition(fields, expectedLabelPosition); } else if (fallbackToCentroid && value.dimensionalShapeType() == DimensionalShapeType.POLYGON) { // Use the centroid for all polygons, unless overwritten for specific cases - doTestLabelPosition(fields, GeoTestUtils.geoShapeValue(new Point(value.lon(), value.lat()))); + doTestLabelPosition(fields, GeoTestUtils.geoShapeValue(new Point(value.getX(), value.getY()))); } } private void doTestLabelPosition(Map fields, GeoShapeValues.GeoShapeValue expectedLabelPosition) throws IOException { - assertEquals("Unexpected latitude for label position,", expectedLabelPosition.lat(), fields.get("label_lat").getValue(), 0.0000001); + assertEquals( + "Unexpected latitude for label position,", + expectedLabelPosition.getY(), + fields.get("label_lat").getValue(), + 0.0000001 + ); assertEquals( "Unexpected longitude for label position,", - expectedLabelPosition.lon(), + expectedLabelPosition.getX(), fields.get("label_lon").getValue(), 0.0000001 ); diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/GeoShapeValues.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/GeoShapeValues.java index 77a3c0a3335b8..d247ae53a9dcc 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/GeoShapeValues.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/GeoShapeValues.java @@ -7,24 +7,16 @@ package org.elasticsearch.xpack.spatial.index.fielddata; -import org.apache.lucene.document.ShapeField; +import org.apache.lucene.geo.Component2D; import org.apache.lucene.geo.LatLonGeometry; import org.apache.lucene.geo.Point; -import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.Orientation; -import org.elasticsearch.geometry.Geometry; -import org.elasticsearch.geometry.utils.GeographyValidator; -import org.elasticsearch.geometry.utils.WellKnownText; import org.elasticsearch.index.mapper.GeoShapeIndexer; import org.elasticsearch.search.aggregations.support.ValuesSourceType; -import org.elasticsearch.xcontent.ToXContentFragment; -import org.elasticsearch.xcontent.XContentBuilder; -import org.elasticsearch.xpack.spatial.index.mapper.BinaryShapeDocValuesField; import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSourceType; import java.io.IOException; -import java.text.ParseException; /** * A stateful lightweight per document geo values. @@ -41,7 +33,7 @@ * * There is just one value for one document. */ -public abstract class GeoShapeValues { +public abstract class GeoShapeValues extends ShapeValues { public static GeoShapeValues EMPTY = new GeoShapeValues() { private final GeoShapeValuesSourceType DEFAULT_VALUES_SOURCE_TYPE = GeoShapeValuesSourceType.instance(); @@ -65,194 +57,20 @@ public GeoShapeValue value() { /** * Creates a new {@link GeoShapeValues} instance */ - protected GeoShapeValues() {} - - /** - * Advance this instance to the given document id - * @return true if there is a value for this document - */ - public abstract boolean advanceExact(int doc) throws IOException; - - public abstract ValuesSourceType valuesSourceType(); - - /** - * Return the value associated with the current document. - * - * Note: the returned {@link GeoShapeValue} might be shared across invocations. - * - * @return the value for the current docID set to {@link #advanceExact(int)}. - */ - public abstract GeoShapeValue value() throws IOException; + protected GeoShapeValues() { + super(CoordinateEncoder.GEO, GeoShapeValues.GeoShapeValue::new, new GeoShapeIndexer(Orientation.CCW, "missing")); + } /** thin wrapper around a {@link GeometryDocValueReader} which encodes / decodes values using * the Geo decoder */ - public static class GeoShapeValue implements ToXContentFragment { - private static final GeoShapeIndexer MISSING_GEOSHAPE_INDEXER = new GeoShapeIndexer(Orientation.CCW, "missing"); - private final GeometryDocValueReader reader; - private final BoundingBox boundingBox; - private final Tile2DVisitor tile2DVisitor; - private final LatLonGeometryRelationVisitor component2DRelationVisitor; - + public static class GeoShapeValue extends ShapeValues.ShapeValue { public GeoShapeValue() { - this.reader = new GeometryDocValueReader(); - this.boundingBox = new BoundingBox(); - this.tile2DVisitor = new Tile2DVisitor(); - this.component2DRelationVisitor = new LatLonGeometryRelationVisitor(CoordinateEncoder.GEO); - } - - /** - * reset the geometry. - */ - public void reset(BytesRef bytesRef) throws IOException { - this.reader.reset(bytesRef); - this.boundingBox.reset(reader.getExtent(), CoordinateEncoder.GEO); - } - - public BoundingBox boundingBox() { - return boundingBox; - } - - /** - * Select a label position that is within the shape. - */ - public GeoPoint labelPosition() throws IOException { - // For polygons we prefer to use the centroid, as long as it is within the polygon - if (reader.getDimensionalShapeType() == DimensionalShapeType.POLYGON) { - Component2DVisitor visitor = Component2DVisitor.getVisitor( - LatLonGeometry.create(new Point(lat(), lon())), - ShapeField.QueryRelation.INTERSECTS, - CoordinateEncoder.GEO - ); - reader.visit(visitor); - if (visitor.matches()) { - return new GeoPoint(lat(), lon()); - } - } - // For all other cases, use the first triangle (or line or point) in the tree which will always intersect the shape - LabelPositionVisitor visitor = new LabelPositionVisitor<>(CoordinateEncoder.GEO, (x, y) -> new GeoPoint(y, x)); - reader.visit(visitor); - return visitor.labelPosition(); - } - - /** - * Determine the {@link GeoRelation} between the current shape and a bounding box provided in - * the encoded space. - */ - public GeoRelation relate(int minX, int maxX, int minY, int maxY) throws IOException { - tile2DVisitor.reset(minX, minY, maxX, maxY); - reader.visit(tile2DVisitor); - return tile2DVisitor.relation(); - } - - /** - * Determine the {@link GeoRelation} between the current shape and a {@link LatLonGeometry}. It only supports - * simple geometries, therefore it will fail if the LatLonGeometry is a {@link org.apache.lucene.geo.Rectangle} - * that crosses the dateline. - */ - public GeoRelation relate(LatLonGeometry latLonGeometry) throws IOException { - component2DRelationVisitor.reset(latLonGeometry); - reader.visit(component2DRelationVisitor); - return component2DRelationVisitor.relation(); - } - - public DimensionalShapeType dimensionalShapeType() { - return reader.getDimensionalShapeType(); - } - - public double weight() throws IOException { - return reader.getSumCentroidWeight(); - } - - /** - * @return the latitude of the centroid of the shape - */ - public double lat() throws IOException { - return CoordinateEncoder.GEO.decodeY(reader.getCentroidY()); - } - - /** - * @return the longitude of the centroid of the shape - */ - public double lon() throws IOException { - return CoordinateEncoder.GEO.decodeX(reader.getCentroidX()); - } - - public static GeoShapeValue missing(String missing) { - try { - final Geometry geometry = WellKnownText.fromWKT(GeographyValidator.instance(true), true, missing); - final BinaryShapeDocValuesField field = new BinaryShapeDocValuesField("missing", CoordinateEncoder.GEO); - field.add(MISSING_GEOSHAPE_INDEXER.indexShape(geometry), geometry); - final GeoShapeValue value = new GeoShapeValue(); - value.reset(field.binaryValue()); - return value; - } catch (IOException | ParseException e) { - throw new IllegalArgumentException("Can't apply missing value [" + missing + "]", e); - } + super(CoordinateEncoder.GEO, (x, y) -> new GeoPoint(y, x)); } @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - throw new IllegalArgumentException("cannot write xcontent for geo_shape doc value"); - } - } - - public static class BoundingBox { - public double top; - public double bottom; - public double negLeft; - public double negRight; - public double posLeft; - public double posRight; - - private BoundingBox() {} - - private void reset(Extent extent, CoordinateEncoder coordinateEncoder) { - this.top = coordinateEncoder.decodeY(extent.top); - this.bottom = coordinateEncoder.decodeY(extent.bottom); - - if (extent.negLeft == Integer.MAX_VALUE && extent.negRight == Integer.MIN_VALUE) { - this.negLeft = Double.POSITIVE_INFINITY; - this.negRight = Double.NEGATIVE_INFINITY; - } else { - this.negLeft = coordinateEncoder.decodeX(extent.negLeft); - this.negRight = coordinateEncoder.decodeX(extent.negRight); - } - - if (extent.posLeft == Integer.MAX_VALUE && extent.posRight == Integer.MIN_VALUE) { - this.posLeft = Double.POSITIVE_INFINITY; - this.posRight = Double.NEGATIVE_INFINITY; - } else { - this.posLeft = coordinateEncoder.decodeX(extent.posLeft); - this.posRight = coordinateEncoder.decodeX(extent.posRight); - } - } - - /** - * @return the minimum y-coordinate of the extent - */ - public double minY() { - return bottom; - } - - /** - * @return the maximum y-coordinate of the extent - */ - public double maxY() { - return top; - } - - /** - * @return the absolute minimum x-coordinate of the extent, whether it is positive or negative. - */ - public double minX() { - return Math.min(negLeft, posLeft); - } - - /** - * @return the absolute maximum x-coordinate of the extent, whether it is positive or negative. - */ - public double maxX() { - return Math.max(negRight, posRight); + protected Component2D centroidAsComponent2D() throws IOException { + return LatLonGeometry.create(new Point(getY(), getX())); } } } diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/IndexGeoShapeFieldData.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/IndexShapeFieldData.java similarity index 71% rename from x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/IndexGeoShapeFieldData.java rename to x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/IndexShapeFieldData.java index 2752fc2710203..302dd53ac2527 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/IndexGeoShapeFieldData.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/IndexShapeFieldData.java @@ -10,6 +10,6 @@ import org.elasticsearch.index.fielddata.IndexFieldData; /** - * Specialization of {@link IndexFieldData} for geo shapes. + * Specialization of {@link IndexFieldData} for geo shapes and shapes. */ -public interface IndexGeoShapeFieldData extends IndexFieldData {} +public interface IndexShapeFieldData extends IndexFieldData {} diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/LabelPositionVisitor.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/LabelPositionVisitor.java index e7a6aed4d6c9d..16939c183cf79 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/LabelPositionVisitor.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/LabelPositionVisitor.java @@ -7,6 +7,8 @@ package org.elasticsearch.xpack.spatial.index.fielddata; +import org.elasticsearch.common.geo.SpatialPoint; + import java.util.function.BiFunction; /** @@ -15,12 +17,12 @@ * * TODO: We could instead choose the point closer to the centroid which improves unbalanced trees */ -public class LabelPositionVisitor extends TriangleTreeReader.DecodedVisitor { +public class LabelPositionVisitor extends TriangleTreeReader.DecodedVisitor { - private T labelPosition; - private final BiFunction pointMaker; + private SpatialPoint labelPosition; + private final BiFunction pointMaker; - public LabelPositionVisitor(CoordinateEncoder encoder, BiFunction pointMaker) { + public LabelPositionVisitor(CoordinateEncoder encoder, BiFunction pointMaker) { super(encoder); this.pointMaker = pointMaker; } @@ -75,7 +77,7 @@ boolean pushDecoded(double minX, double minY, double maxX, double maxY) { return labelPosition == null; } - public T labelPosition() { + public SpatialPoint labelPosition() { return labelPosition; } } diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/LeafGeoShapeFieldData.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/LeafGeoShapeFieldData.java deleted file mode 100644 index d7ba54741e816..0000000000000 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/LeafGeoShapeFieldData.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -package org.elasticsearch.xpack.spatial.index.fielddata; - -import org.elasticsearch.index.fielddata.LeafFieldData; - -/** - * {@link LeafFieldData} specialization for geo shapes. - */ -public interface LeafGeoShapeFieldData extends LeafFieldData { - /** - * Return geo shape values. - */ - GeoShapeValues getGeoShapeValues(); - -} diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/LeafShapeFieldData.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/LeafShapeFieldData.java new file mode 100644 index 0000000000000..7b67d614a00e0 --- /dev/null +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/LeafShapeFieldData.java @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.spatial.index.fielddata; + +import org.apache.lucene.util.Accountable; +import org.elasticsearch.common.geo.BoundingBox; +import org.elasticsearch.common.geo.SpatialPoint; +import org.elasticsearch.index.fielddata.LeafFieldData; +import org.elasticsearch.index.fielddata.ScriptDocValues; +import org.elasticsearch.index.fielddata.SortedBinaryDocValues; +import org.elasticsearch.script.field.DocValuesScriptFieldFactory; +import org.elasticsearch.script.field.ToScriptFieldFactory; + +import java.util.Collection; +import java.util.Collections; + +/** + * {@link LeafFieldData} specialization for geo-shapes and shapes. + */ +public abstract class LeafShapeFieldData implements LeafFieldData { + protected final ToScriptFieldFactory toScriptFieldFactory; + + public LeafShapeFieldData(ToScriptFieldFactory toScriptFieldFactory) { + this.toScriptFieldFactory = toScriptFieldFactory; + } + + public static class Empty extends LeafShapeFieldData { + private final ShapeValues emptyValues; + + public Empty(ToScriptFieldFactory toScriptFieldFactory, ShapeValues emptyValues) { + super(toScriptFieldFactory); + this.emptyValues = emptyValues; + } + + @Override + public long ramBytesUsed() { + return 0; + } + + @Override + public Collection getChildResources() { + return Collections.emptyList(); + } + + @Override + public void close() {} + + @Override + public ShapeValues getShapeValues() { + return emptyValues; + } + } + + /** + * Return geo-shape or shape values. + */ + public abstract ShapeValues getShapeValues(); + + @Override + public final SortedBinaryDocValues getBytesValues() { + throw new UnsupportedOperationException("scripts and term aggs are not supported by geo_shape or shape doc values"); + } + + @Override + public final DocValuesScriptFieldFactory getScriptFieldFactory(String name) { + return toScriptFieldFactory.getScriptFieldFactory(getShapeValues(), name); + } + + public static class ShapeScriptValues extends ScriptDocValues.BaseGeometry { + + private final GeometrySupplier gsSupplier; + + protected ShapeScriptValues(GeometrySupplier supplier) { + super(supplier); + this.gsSupplier = supplier; + } + + @Override + public int getDimensionalType() { + return gsSupplier.getInternal(0) == null ? -1 : gsSupplier.getInternal(0).dimensionalShapeType().ordinal(); + } + + @Override + public T getCentroid() { + return gsSupplier.getInternal(0) == null ? null : gsSupplier.getInternalCentroid(); + } + + @Override + public BoundingBox getBoundingBox() { + return gsSupplier.getInternal(0) == null ? null : gsSupplier.getInternalBoundingBox(); + } + + @Override + public T getLabelPosition() { + return gsSupplier.getInternal(0) == null ? null : gsSupplier.getInternalLabelPosition(); + } + + @Override + public ShapeValues.ShapeValue get(int index) { + return gsSupplier.getInternal(0); + } + + public ShapeValues.ShapeValue getValue() { + return gsSupplier.getInternal(0); + } + + @Override + public int size() { + return supplier.size(); + } + } +} diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/ShapeValues.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/ShapeValues.java new file mode 100644 index 0000000000000..b5c5f6a2d9693 --- /dev/null +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/ShapeValues.java @@ -0,0 +1,253 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.spatial.index.fielddata; + +import org.apache.lucene.document.ShapeField; +import org.apache.lucene.geo.Component2D; +import org.apache.lucene.geo.LatLonGeometry; +import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.geo.SpatialPoint; +import org.elasticsearch.geometry.Geometry; +import org.elasticsearch.geometry.utils.GeographyValidator; +import org.elasticsearch.geometry.utils.WellKnownText; +import org.elasticsearch.index.mapper.ShapeIndexer; +import org.elasticsearch.search.aggregations.support.ValuesSourceType; +import org.elasticsearch.xcontent.ToXContentFragment; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xpack.spatial.index.mapper.BinaryShapeDocValuesField; + +import java.io.IOException; +import java.text.ParseException; +import java.util.function.BiFunction; +import java.util.function.Supplier; + +/** + * A stateful lightweight per document geo values. + * To iterate over values in a document use the following pattern: + *
+ *   MultiGeoValues values = ..;
+ *   // for each docID
+ *   if (values.advanceExact(docId)) {
+ *     GeoValue value = values.value()
+ *     final int numValues = values.count();
+ *     // process value
+ *   }
+ * 
+ * + * There is just one value for one document. + */ +public abstract class ShapeValues { + protected final CoordinateEncoder encoder; + protected final Supplier supplier; + protected final ShapeIndexer missingShapeIndexer; + + /** + * Creates a new {@link ShapeValues} instance + */ + protected ShapeValues(CoordinateEncoder encoder, Supplier supplier, ShapeIndexer missingShapeIndexer) { + this.encoder = encoder; + this.supplier = supplier; + this.missingShapeIndexer = missingShapeIndexer; + } + + /** + * Advance this instance to the given document id + * @return true if there is a value for this document + */ + public abstract boolean advanceExact(int doc) throws IOException; + + public abstract ValuesSourceType valuesSourceType(); + + /** + * Return the value associated with the current document. + * + * Note: the returned {@link ShapeValue} might be shared across invocations. + * + * @return the value for the current docID set to {@link #advanceExact(int)}. + */ + public abstract ShapeValue value() throws IOException; + + public ShapeValue missing(String missing) { + try { + final Geometry geometry = WellKnownText.fromWKT(GeographyValidator.instance(true), true, missing); + final BinaryShapeDocValuesField field = new BinaryShapeDocValuesField("missing", encoder); + field.add(missingShapeIndexer.indexShape(geometry), geometry); + final ShapeValue value = supplier.get(); + value.reset(field.binaryValue()); + return value; + } catch (IOException | ParseException e) { + throw new IllegalArgumentException("Can't apply missing value [" + missing + "]", e); + } + } + + /** thin wrapper around a {@link GeometryDocValueReader} which encodes / decodes values using + * the Geo decoder */ + public abstract static class ShapeValue implements ToXContentFragment { + protected final GeometryDocValueReader reader; + private final BoundingBox boundingBox; + private final Tile2DVisitor tile2DVisitor; + protected final CoordinateEncoder encoder; + private final BiFunction pointMaker; + private final LatLonGeometryRelationVisitor component2DRelationVisitor; + + public ShapeValue(CoordinateEncoder encoder, BiFunction pointMaker) { + this.reader = new GeometryDocValueReader(); + this.boundingBox = new BoundingBox(); + this.tile2DVisitor = new Tile2DVisitor(); + this.encoder = encoder; + this.pointMaker = pointMaker; + this.component2DRelationVisitor = new LatLonGeometryRelationVisitor(encoder); + } + + /** + * reset the geometry. + */ + public void reset(BytesRef bytesRef) throws IOException { + this.reader.reset(bytesRef); + this.boundingBox.reset(reader.getExtent(), encoder); + } + + public BoundingBox boundingBox() { + return boundingBox; + } + + protected abstract Component2D centroidAsComponent2D() throws IOException; + + private boolean centroidWithinShape() throws IOException { + Component2DVisitor visitor = Component2DVisitor.getVisitor( + centroidAsComponent2D(), + ShapeField.QueryRelation.INTERSECTS, + CoordinateEncoder.GEO + ); + reader.visit(visitor); + return visitor.matches(); + } + + /** + * Select a label position that is within the shape. + */ + public SpatialPoint labelPosition() throws IOException { + // For polygons we prefer to use the centroid, as long as it is within the polygon + if (reader.getDimensionalShapeType() == DimensionalShapeType.POLYGON && centroidWithinShape()) { + return pointMaker.apply(getX(), getY()); + } + // For all other cases, use the first triangle (or line or point) in the tree which will always intersect the shape + LabelPositionVisitor visitor = new LabelPositionVisitor(CoordinateEncoder.GEO, pointMaker); + reader.visit(visitor); + return visitor.labelPosition(); + } + + /** + * Determine the {@link GeoRelation} between the current shape and a bounding box provided in + * the encoded space. + */ + public GeoRelation relate(int minX, int maxX, int minY, int maxY) throws IOException { + tile2DVisitor.reset(minX, minY, maxX, maxY); + reader.visit(tile2DVisitor); + return tile2DVisitor.relation(); + } + + /** + * Determine the {@link GeoRelation} between the current shape and a {@link LatLonGeometry}. It only supports + * simple geometries, therefore it will fail if the LatLonGeometry is a {@link org.apache.lucene.geo.Rectangle} + * that crosses the dateline. + */ + public GeoRelation relate(LatLonGeometry latLonGeometry) throws IOException { + component2DRelationVisitor.reset(latLonGeometry); + reader.visit(component2DRelationVisitor); + return component2DRelationVisitor.relation(); + } + + public DimensionalShapeType dimensionalShapeType() { + return reader.getDimensionalShapeType(); + } + + public double weight() throws IOException { + return reader.getSumCentroidWeight(); + } + + /** + * @return the Y-value (or latitude) of the centroid of the shape + */ + public double getY() throws IOException { + return encoder.decodeY(reader.getCentroidY()); + } + + /** + * @return the X-value (or longitude) of the centroid of the shape + */ + public double getX() throws IOException { + return encoder.decodeX(reader.getCentroidX()); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + throw new IllegalArgumentException("cannot write xcontent for geo_shape doc value"); + } + } + + public static class BoundingBox { + public double top; + public double bottom; + public double negLeft; + public double negRight; + public double posLeft; + public double posRight; + + public BoundingBox() {} + + private void reset(Extent extent, CoordinateEncoder coordinateEncoder) { + this.top = coordinateEncoder.decodeY(extent.top); + this.bottom = coordinateEncoder.decodeY(extent.bottom); + + if (extent.negLeft == Integer.MAX_VALUE && extent.negRight == Integer.MIN_VALUE) { + this.negLeft = Double.POSITIVE_INFINITY; + this.negRight = Double.NEGATIVE_INFINITY; + } else { + this.negLeft = coordinateEncoder.decodeX(extent.negLeft); + this.negRight = coordinateEncoder.decodeX(extent.negRight); + } + + if (extent.posLeft == Integer.MAX_VALUE && extent.posRight == Integer.MIN_VALUE) { + this.posLeft = Double.POSITIVE_INFINITY; + this.posRight = Double.NEGATIVE_INFINITY; + } else { + this.posLeft = coordinateEncoder.decodeX(extent.posLeft); + this.posRight = coordinateEncoder.decodeX(extent.posRight); + } + } + + /** + * @return the minimum y-coordinate of the extent + */ + public double minY() { + return bottom; + } + + /** + * @return the maximum y-coordinate of the extent + */ + public double maxY() { + return top; + } + + /** + * @return the absolute minimum x-coordinate of the extent, whether it is positive or negative. + */ + public double minX() { + return Math.min(negLeft, posLeft); + } + + /** + * @return the absolute maximum x-coordinate of the extent, whether it is positive or negative. + */ + public double maxX() { + return Math.max(negRight, posRight); + } + } +} diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/plain/AbstractAtomicGeoShapeShapeFieldData.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/plain/AbstractAtomicGeoShapeShapeFieldData.java index 9e2e4ed8669d7..93f237141a8ec 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/plain/AbstractAtomicGeoShapeShapeFieldData.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/plain/AbstractAtomicGeoShapeShapeFieldData.java @@ -7,115 +7,58 @@ package org.elasticsearch.xpack.spatial.index.fielddata.plain; -import org.apache.lucene.util.Accountable; import org.elasticsearch.common.geo.GeoBoundingBox; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.index.fielddata.ScriptDocValues; -import org.elasticsearch.index.fielddata.SortedBinaryDocValues; -import org.elasticsearch.script.field.DocValuesScriptFieldFactory; import org.elasticsearch.script.field.ToScriptFieldFactory; import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues; -import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues.GeoShapeValue; -import org.elasticsearch.xpack.spatial.index.fielddata.LeafGeoShapeFieldData; - -import java.util.Collection; -import java.util.Collections; +import org.elasticsearch.xpack.spatial.index.fielddata.LeafShapeFieldData; +import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues; import static org.elasticsearch.common.geo.SphericalMercatorUtils.latToSphericalMercator; import static org.elasticsearch.common.geo.SphericalMercatorUtils.lonToSphericalMercator; -public abstract class AbstractAtomicGeoShapeShapeFieldData implements LeafGeoShapeFieldData { - - private final ToScriptFieldFactory toScriptFieldFactory; +public abstract class AbstractAtomicGeoShapeShapeFieldData extends LeafShapeFieldData { - public AbstractAtomicGeoShapeShapeFieldData(ToScriptFieldFactory toScriptFieldFactory) { - this.toScriptFieldFactory = toScriptFieldFactory; + public AbstractAtomicGeoShapeShapeFieldData(ToScriptFieldFactory toScriptFieldFactory) { + super(toScriptFieldFactory); } - @Override - public final SortedBinaryDocValues getBytesValues() { - throw new UnsupportedOperationException("scripts and term aggs are not supported by geo_shape doc values"); + public static LeafShapeFieldData empty(final int maxDoc, ToScriptFieldFactory toScriptFieldFactory) { + return new LeafShapeFieldData.Empty<>(toScriptFieldFactory, GeoShapeValues.EMPTY); } - @Override - public final DocValuesScriptFieldFactory getScriptFieldFactory(String name) { - return toScriptFieldFactory.getScriptFieldFactory(getGeoShapeValues(), name); - } - - public static LeafGeoShapeFieldData empty(final int maxDoc, ToScriptFieldFactory toScriptFieldFactory) { - return new AbstractAtomicGeoShapeShapeFieldData(toScriptFieldFactory) { - - @Override - public long ramBytesUsed() { - return 0; - } - - @Override - public Collection getChildResources() { - return Collections.emptyList(); - } - - @Override - public void close() {} - - @Override - public GeoShapeValues getGeoShapeValues() { - return GeoShapeValues.EMPTY; - } - }; - } + public static final class GeoShapeScriptValues extends LeafShapeFieldData.ShapeScriptValues + implements + ScriptDocValues.Geometry { - public static final class GeoShapeScriptValues extends ScriptDocValues.Geometry { - - private final GeometrySupplier gsSupplier; - - public GeoShapeScriptValues(GeometrySupplier supplier) { + public GeoShapeScriptValues(GeometrySupplier supplier) { super(supplier); - this.gsSupplier = supplier; - } - - @Override - public int getDimensionalType() { - return gsSupplier.getInternal(0) == null ? -1 : gsSupplier.getInternal(0).dimensionalShapeType().ordinal(); - } - - @Override - public GeoPoint getCentroid() { - return gsSupplier.getInternal(0) == null ? null : gsSupplier.getInternalCentroid(); } @Override - public double getMercatorWidth() { - return lonToSphericalMercator(getBoundingBox().right()) - lonToSphericalMercator(getBoundingBox().left()); + public GeoShapeValues.GeoShapeValue get(int index) { + return (GeoShapeValues.GeoShapeValue) super.get(index); } @Override - public double getMercatorHeight() { - return latToSphericalMercator(getBoundingBox().top()) - latToSphericalMercator(getBoundingBox().bottom()); + public GeoShapeValues.GeoShapeValue getValue() { + return (GeoShapeValues.GeoShapeValue) super.getValue(); } @Override public GeoBoundingBox getBoundingBox() { - return gsSupplier.getInternal(0) == null ? null : gsSupplier.getInternalBoundingBox(); - } - - @Override - public GeoPoint getLabelPosition() { - return gsSupplier.getInternal(0) == null ? null : gsSupplier.getInternalLabelPosition(); + return (GeoBoundingBox) super.getBoundingBox(); } @Override - public GeoShapeValues.GeoShapeValue get(int index) { - return gsSupplier.getInternal(0); - } - - public GeoShapeValues.GeoShapeValue getValue() { - return gsSupplier.getInternal(0); + public double getMercatorWidth() { + return lonToSphericalMercator(getBoundingBox().right()) - lonToSphericalMercator(getBoundingBox().left()); } @Override - public int size() { - return supplier.size(); + public double getMercatorHeight() { + return latToSphericalMercator(getBoundingBox().top()) - latToSphericalMercator(getBoundingBox().bottom()); } } } diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/plain/AbstractLatLonShapeIndexFieldData.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/plain/AbstractLatLonShapeIndexFieldData.java deleted file mode 100644 index ffdcbc17f89c6..0000000000000 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/plain/AbstractLatLonShapeIndexFieldData.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -package org.elasticsearch.xpack.spatial.index.fielddata.plain; - -import org.apache.lucene.index.DocValuesType; -import org.apache.lucene.index.FieldInfo; -import org.apache.lucene.index.LeafReader; -import org.apache.lucene.index.LeafReaderContext; -import org.apache.lucene.search.SortField; -import org.elasticsearch.common.util.BigArrays; -import org.elasticsearch.core.Nullable; -import org.elasticsearch.index.fielddata.IndexFieldData; -import org.elasticsearch.index.fielddata.IndexFieldDataCache; -import org.elasticsearch.indices.breaker.CircuitBreakerService; -import org.elasticsearch.script.field.ToScriptFieldFactory; -import org.elasticsearch.search.DocValueFormat; -import org.elasticsearch.search.MultiValueMode; -import org.elasticsearch.search.aggregations.support.ValuesSourceType; -import org.elasticsearch.search.sort.BucketedSort; -import org.elasticsearch.search.sort.SortOrder; -import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues; -import org.elasticsearch.xpack.spatial.index.fielddata.IndexGeoShapeFieldData; -import org.elasticsearch.xpack.spatial.index.fielddata.LeafGeoShapeFieldData; - -public abstract class AbstractLatLonShapeIndexFieldData implements IndexGeoShapeFieldData { - protected final String fieldName; - protected final ValuesSourceType valuesSourceType; - protected final ToScriptFieldFactory toScriptFieldFactory; - - AbstractLatLonShapeIndexFieldData( - String fieldName, - ValuesSourceType valuesSourceType, - ToScriptFieldFactory toScriptFieldFactory - ) { - this.fieldName = fieldName; - this.valuesSourceType = valuesSourceType; - this.toScriptFieldFactory = toScriptFieldFactory; - } - - @Override - public final String getFieldName() { - return fieldName; - } - - @Override - public ValuesSourceType getValuesSourceType() { - return valuesSourceType; - } - - @Override - public SortField sortField( - @Nullable Object missingValue, - MultiValueMode sortMode, - XFieldComparatorSource.Nested nested, - boolean reverse - ) { - throw new IllegalArgumentException("can't sort on geo_shape field without using specific sorting feature, like geo_distance"); - } - - public static class LatLonShapeIndexFieldData extends AbstractLatLonShapeIndexFieldData { - public LatLonShapeIndexFieldData( - String fieldName, - ValuesSourceType valuesSourceType, - ToScriptFieldFactory toScriptFieldFactory - ) { - super(fieldName, valuesSourceType, toScriptFieldFactory); - } - - @Override - public LeafGeoShapeFieldData load(LeafReaderContext context) { - LeafReader reader = context.reader(); - FieldInfo info = reader.getFieldInfos().fieldInfo(fieldName); - if (info != null) { - checkCompatible(info); - } - return new LatLonShapeDVAtomicShapeFieldData(reader, fieldName, toScriptFieldFactory); - } - - @Override - public LeafGeoShapeFieldData loadDirect(LeafReaderContext context) throws Exception { - return load(context); - } - - @Override - public BucketedSort newBucketedSort( - BigArrays bigArrays, - Object missingValue, - MultiValueMode sortMode, - IndexFieldData.XFieldComparatorSource.Nested nested, - SortOrder sortOrder, - DocValueFormat format, - int bucketSize, - BucketedSort.ExtraData extra - ) { - throw new IllegalArgumentException("can't sort on geo_shape field without using specific sorting feature, like geo_distance"); - } - - /** helper: checks a fieldinfo and throws exception if its definitely not a LatLonDocValuesField */ - static void checkCompatible(FieldInfo fieldInfo) { - // dv properties could be "unset", if you e.g. used only StoredField with this same name in the segment. - if (fieldInfo.getDocValuesType() != DocValuesType.NONE && fieldInfo.getDocValuesType() != DocValuesType.BINARY) { - throw new IllegalArgumentException( - "field=\"" - + fieldInfo.name - + "\" was indexed with docValuesType=" - + fieldInfo.getDocValuesType() - + " but this type has docValuesType=" - + DocValuesType.BINARY - + ", is the field really a geo-shape field?" - ); - } - } - } - - public static class Builder implements IndexFieldData.Builder { - private final String name; - private final ValuesSourceType valuesSourceType; - private final ToScriptFieldFactory toScriptFieldFactory; - - public Builder(String name, ValuesSourceType valuesSourceType, ToScriptFieldFactory toScriptFieldFactory) { - this.name = name; - this.valuesSourceType = valuesSourceType; - this.toScriptFieldFactory = toScriptFieldFactory; - } - - @Override - public IndexFieldData build(IndexFieldDataCache cache, CircuitBreakerService breakerService) { - // ignore breaker - return new LatLonShapeIndexFieldData(name, valuesSourceType, toScriptFieldFactory); - } - } -} diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/plain/AbstractShapeIndexFieldData.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/plain/AbstractShapeIndexFieldData.java new file mode 100644 index 0000000000000..61239429da6ee --- /dev/null +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/plain/AbstractShapeIndexFieldData.java @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.spatial.index.fielddata.plain; + +import org.apache.lucene.index.DocValuesType; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.search.SortField; +import org.elasticsearch.common.util.BigArrays; +import org.elasticsearch.core.Nullable; +import org.elasticsearch.script.field.ToScriptFieldFactory; +import org.elasticsearch.search.DocValueFormat; +import org.elasticsearch.search.MultiValueMode; +import org.elasticsearch.search.aggregations.support.ValuesSourceType; +import org.elasticsearch.search.sort.BucketedSort; +import org.elasticsearch.search.sort.SortOrder; +import org.elasticsearch.xpack.spatial.index.fielddata.IndexShapeFieldData; +import org.elasticsearch.xpack.spatial.index.fielddata.LeafShapeFieldData; +import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues; + +/** + * This class needs to be extended with a type specific implementation. + * In particular the `load(context)` method needs to return an instance of `LeadShapeFieldData` that is specific to the type of + * `ValueSourceType` that this is constructed with. For example, the `LatLonShapeIndexFieldData.load(context)` method will + * create an instance of `LatLonShapeDVAtomicShapeFieldData`. + */ +public abstract class AbstractShapeIndexFieldData implements IndexShapeFieldData { + protected final String fieldName; + protected final ValuesSourceType valuesSourceType; + protected final ToScriptFieldFactory toScriptFieldFactory; + + AbstractShapeIndexFieldData( + String fieldName, + ValuesSourceType valuesSourceType, + ToScriptFieldFactory toScriptFieldFactory + ) { + this.fieldName = fieldName; + this.valuesSourceType = valuesSourceType; + this.toScriptFieldFactory = toScriptFieldFactory; + } + + @Override + public final String getFieldName() { + return fieldName; + } + + @Override + public ValuesSourceType getValuesSourceType() { + return valuesSourceType; + } + + @Override + public LeafShapeFieldData loadDirect(LeafReaderContext context) { + return load(context); + } + + @Override + public BucketedSort newBucketedSort( + BigArrays bigArrays, + Object missingValue, + MultiValueMode sortMode, + XFieldComparatorSource.Nested nested, + SortOrder sortOrder, + DocValueFormat format, + int bucketSize, + BucketedSort.ExtraData extra + ) { + throw sortException(); + } + + @Override + public SortField sortField( + @Nullable Object missingValue, + MultiValueMode sortMode, + XFieldComparatorSource.Nested nested, + boolean reverse + ) { + throw sortException(); + } + + protected abstract IllegalArgumentException sortException(); + + /** helper: checks a fieldinfo and throws exception if it's definitely not a LatLonDocValuesField */ + protected static void checkCompatible(FieldInfo fieldInfo, String fieldTypeName) { + // dv properties could be "unset", if you e.g. used only StoredField with this same name in the segment. + if (fieldInfo.getDocValuesType() != DocValuesType.NONE && fieldInfo.getDocValuesType() != DocValuesType.BINARY) { + throw new IllegalArgumentException( + "field=\"" + + fieldInfo.name + + "\" was indexed with docValuesType=" + + fieldInfo.getDocValuesType() + + " but this type has docValuesType=" + + DocValuesType.BINARY + + ", is the field really a " + + fieldTypeName + + " field?" + ); + } + } +} diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/plain/LatLonShapeDVAtomicShapeFieldData.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/plain/LatLonShapeDVAtomicShapeFieldData.java index 00696ffee9b13..75935ad0d3a1c 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/plain/LatLonShapeDVAtomicShapeFieldData.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/plain/LatLonShapeDVAtomicShapeFieldData.java @@ -14,17 +14,19 @@ import org.elasticsearch.script.field.ToScriptFieldFactory; import org.elasticsearch.search.aggregations.support.ValuesSourceType; import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues; +import org.elasticsearch.xpack.spatial.index.fielddata.LeafShapeFieldData; +import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues; import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSourceType; import java.io.IOException; import java.util.Collection; import java.util.Collections; -final class LatLonShapeDVAtomicShapeFieldData extends AbstractAtomicGeoShapeShapeFieldData { +final class LatLonShapeDVAtomicShapeFieldData extends LeafShapeFieldData { private final LeafReader reader; private final String fieldName; - LatLonShapeDVAtomicShapeFieldData(LeafReader reader, String fieldName, ToScriptFieldFactory toScriptFieldFactory) { + LatLonShapeDVAtomicShapeFieldData(LeafReader reader, String fieldName, ToScriptFieldFactory toScriptFieldFactory) { super(toScriptFieldFactory); this.reader = reader; this.fieldName = fieldName; @@ -46,7 +48,7 @@ public void close() { } @Override - public GeoShapeValues getGeoShapeValues() { + public ShapeValues getShapeValues() { try { final BinaryDocValues binaryValues = DocValues.getBinary(reader, fieldName); final GeoShapeValues.GeoShapeValue geoShapeValue = new GeoShapeValues.GeoShapeValue(); diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/plain/LatLonShapeIndexFieldData.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/plain/LatLonShapeIndexFieldData.java new file mode 100644 index 0000000000000..eb5013da3f7c1 --- /dev/null +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/fielddata/plain/LatLonShapeIndexFieldData.java @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.spatial.index.fielddata.plain; + +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.LeafReader; +import org.apache.lucene.index.LeafReaderContext; +import org.elasticsearch.script.field.ToScriptFieldFactory; +import org.elasticsearch.search.aggregations.support.ValuesSourceType; +import org.elasticsearch.xpack.spatial.index.fielddata.LeafShapeFieldData; +import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues; + +public class LatLonShapeIndexFieldData extends AbstractShapeIndexFieldData { + public LatLonShapeIndexFieldData( + String fieldName, + ValuesSourceType valuesSourceType, + ToScriptFieldFactory toScriptFieldFactory + ) { + super(fieldName, valuesSourceType, toScriptFieldFactory); + } + + @Override + public LeafShapeFieldData load(LeafReaderContext context) { + LeafReader reader = context.reader(); + FieldInfo info = reader.getFieldInfos().fieldInfo(fieldName); + if (info != null) { + checkCompatible(info, "geo_shape"); + } + return new LatLonShapeDVAtomicShapeFieldData(reader, fieldName, toScriptFieldFactory); + } + + @Override + protected IllegalArgumentException sortException() { + return new IllegalArgumentException("can't sort on geo_shape field without using specific sorting feature, like geo_distance"); + } +} diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldMapper.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldMapper.java index e062df537aec1..6d7a39e135ebc 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldMapper.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldMapper.java @@ -46,8 +46,10 @@ import org.elasticsearch.script.field.Field; import org.elasticsearch.xpack.spatial.index.fielddata.CoordinateEncoder; import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues; +import org.elasticsearch.xpack.spatial.index.fielddata.LeafShapeFieldData; +import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues; import org.elasticsearch.xpack.spatial.index.fielddata.plain.AbstractAtomicGeoShapeShapeFieldData; -import org.elasticsearch.xpack.spatial.index.fielddata.plain.AbstractLatLonShapeIndexFieldData; +import org.elasticsearch.xpack.spatial.index.fielddata.plain.LatLonShapeIndexFieldData; import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSourceType; import java.io.IOException; @@ -183,7 +185,11 @@ public GeoShapeWithDocValuesFieldType( @Override public IndexFieldData.Builder fielddataBuilder(FieldDataContext fieldDataContext) { failIfNoDocValues(); - return new AbstractLatLonShapeIndexFieldData.Builder(name(), GeoShapeValuesSourceType.instance(), GeoShapeDocValuesField::new); + return (cache, breakerService) -> new LatLonShapeIndexFieldData( + name(), + GeoShapeValuesSourceType.instance(), + GeoShapeDocValuesField::new + ); } @Override @@ -340,13 +346,13 @@ protected void checkIncomingMergeType(FieldMapper mergeWith) { super.checkIncomingMergeType(mergeWith); } - public static class GeoShapeDocValuesField extends AbstractScriptFieldFactory + public static class GeoShapeDocValuesField extends AbstractScriptFieldFactory implements - Field, + Field, DocValuesScriptFieldFactory, - ScriptDocValues.GeometrySupplier { + ScriptDocValues.GeometrySupplier { - private final GeoShapeValues in; + private final ShapeValues in; protected final String name; private GeoShapeValues.GeoShapeValue value; @@ -354,9 +360,9 @@ public static class GeoShapeDocValuesField extends AbstractScriptFieldFactory geoShapeScriptValues; - public GeoShapeDocValuesField(GeoShapeValues in, String name) { + public GeoShapeDocValuesField(ShapeValues in, String name) { this.in = in; this.name = name; } @@ -364,8 +370,8 @@ public GeoShapeDocValuesField(GeoShapeValues in, String name) { @Override public void setNextDocId(int docId) throws IOException { if (in.advanceExact(docId)) { - value = in.value(); - centroid.reset(value.lat(), value.lon()); + value = (GeoShapeValues.GeoShapeValue) in.value(); + centroid.reset(value.getY(), value.getX()); boundingBox.topLeft().reset(value.boundingBox().maxY(), value.boundingBox().minX()); boundingBox.bottomRight().reset(value.boundingBox().minY(), value.boundingBox().maxX()); } else { @@ -374,7 +380,7 @@ public void setNextDocId(int docId) throws IOException { } @Override - public ScriptDocValues toScriptDocValues() { + public ScriptDocValues toScriptDocValues() { if (geoShapeScriptValues == null) { geoShapeScriptValues = new AbstractAtomicGeoShapeShapeFieldData.GeoShapeScriptValues(this); } @@ -383,7 +389,7 @@ public ScriptDocValues toScriptDocValues() { } @Override - public GeoShapeValues.GeoShapeValue getInternal(int index) { + public ShapeValues.ShapeValue getInternal(int index) { if (index != 0) { throw new UnsupportedOperationException(); } @@ -406,7 +412,7 @@ public GeoBoundingBox getInternalBoundingBox() { @Override public GeoPoint getInternalLabelPosition() { try { - return value.labelPosition(); + return new GeoPoint(value.labelPosition()); } catch (IOException e) { throw new UncheckedIOException("Failed to parse geo shape label position: " + e.getMessage(), e); } @@ -440,8 +446,8 @@ public GeoShapeValues.GeoShapeValue get(int index, GeoShapeValues.GeoShapeValue } @Override - public Iterator iterator() { - return new Iterator() { + public Iterator iterator() { + return new Iterator<>() { private int index = 0; @Override @@ -450,7 +456,7 @@ public boolean hasNext() { } @Override - public GeoShapeValues.GeoShapeValue next() { + public ShapeValues.ShapeValue next() { if (hasNext() == false) { throw new NoSuchElementException(); } diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/AbstractGeoHashGridTiler.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/AbstractGeoHashGridTiler.java index af944ece7dbc0..f41292d0325a0 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/AbstractGeoHashGridTiler.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/AbstractGeoHashGridTiler.java @@ -12,6 +12,7 @@ import org.elasticsearch.geometry.utils.Geohash; import org.elasticsearch.xpack.spatial.index.fielddata.GeoRelation; import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues; +import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues; import java.io.IOException; @@ -33,7 +34,7 @@ public long encode(double x, double y) { } @Override - public int setValues(GeoShapeCellValues values, GeoShapeValues.GeoShapeValue geoValue) throws IOException { + public int setValues(GeoShapeCellValues values, ShapeValues.ShapeValue geoValue) throws IOException { if (precision == 0) { return 1; @@ -51,11 +52,8 @@ public int setValues(GeoShapeCellValues values, GeoShapeValues.GeoShapeValue geo return setValuesByRasterization("", values, 0, geoValue); } - protected int setValuesByBruteForceScan( - GeoShapeCellValues values, - GeoShapeValues.GeoShapeValue geoValue, - GeoShapeValues.BoundingBox bounds - ) throws IOException { + protected int setValuesByBruteForceScan(GeoShapeCellValues values, ShapeValues.ShapeValue geoValue, GeoShapeValues.BoundingBox bounds) + throws IOException { // TODO: This way to discover cells inside of a bounding box seems not to work as expected. I can // see that eventually we will be visiting twice the same cell which should not happen. int idx = 0; @@ -80,9 +78,9 @@ protected int setValuesByBruteForceScan( } /** - * Sets a singular doc-value for the {@link GeoShapeValues.GeoShapeValue}. + * Sets a singular doc-value for the {@link ShapeValues.ShapeValue}. */ - protected int setValue(GeoShapeCellValues docValues, GeoShapeValues.GeoShapeValue geoValue, GeoShapeValues.BoundingBox bounds) + protected int setValue(GeoShapeCellValues docValues, ShapeValues.ShapeValue geoValue, GeoShapeValues.BoundingBox bounds) throws IOException { String hash = Geohash.stringEncode(bounds.minX(), bounds.minY(), precision); if (relateTile(geoValue, hash) != GeoRelation.QUERY_DISJOINT) { @@ -93,7 +91,7 @@ protected int setValue(GeoShapeCellValues docValues, GeoShapeValues.GeoShapeValu return 0; } - private GeoRelation relateTile(GeoShapeValues.GeoShapeValue geoValue, String hash) throws IOException { + private GeoRelation relateTile(ShapeValues.ShapeValue geoValue, String hash) throws IOException { if (validHash(hash)) { final Rectangle rectangle = Geohash.toBoundingBox(hash); int minX = GeoEncodingUtils.encodeLongitude(rectangle.getMinLon()); @@ -105,26 +103,26 @@ private GeoRelation relateTile(GeoShapeValues.GeoShapeValue geoValue, String has return GeoRelation.QUERY_DISJOINT; } - protected int setValuesByRasterization(String hash, GeoShapeCellValues values, int valuesIndex, GeoShapeValues.GeoShapeValue geoValue) + protected int setValuesByRasterization(String hash, GeoShapeCellValues values, int valuesIndex, ShapeValues.ShapeValue geoValue) throws IOException { String[] hashes = Geohash.getSubGeohashes(hash); - for (int i = 0; i < hashes.length; i++) { - GeoRelation relation = relateTile(geoValue, hashes[i]); + for (String s : hashes) { + GeoRelation relation = relateTile(geoValue, s); if (relation == GeoRelation.QUERY_CROSSES) { - if (hashes[i].length() == precision) { + if (s.length() == precision) { values.resizeCell(valuesIndex + 1); - values.add(valuesIndex++, Geohash.longEncode(hashes[i])); + values.add(valuesIndex++, Geohash.longEncode(s)); } else { - valuesIndex = setValuesByRasterization(hashes[i], values, valuesIndex, geoValue); + valuesIndex = setValuesByRasterization(s, values, valuesIndex, geoValue); } } else if (relation == GeoRelation.QUERY_INSIDE) { - if (hashes[i].length() == precision) { + if (s.length() == precision) { values.resizeCell(valuesIndex + 1); - values.add(valuesIndex++, Geohash.longEncode(hashes[i])); + values.add(valuesIndex++, Geohash.longEncode(s)); } else { int numTilesAtPrecision = getNumTilesAtPrecision(precision, hash.length()); values.resizeCell(getNewSize(valuesIndex, numTilesAtPrecision + 1)); - valuesIndex = setValuesForFullyContainedTile(hashes[i], values, valuesIndex, precision); + valuesIndex = setValuesForFullyContainedTile(s, values, valuesIndex, precision); } } } @@ -149,12 +147,12 @@ private int getNumTilesAtPrecision(int finalPrecision, int currentPrecision) { protected int setValuesForFullyContainedTile(String hash, GeoShapeCellValues values, int valuesIndex, int targetPrecision) { String[] hashes = Geohash.getSubGeohashes(hash); - for (int i = 0; i < hashes.length; i++) { - if (validHash(hashes[i])) { - if (hashes[i].length() == targetPrecision) { - values.add(valuesIndex++, Geohash.longEncode(hashes[i])); + for (String s : hashes) { + if (validHash(s)) { + if (s.length() == targetPrecision) { + values.add(valuesIndex++, Geohash.longEncode(s)); } else { - valuesIndex = setValuesForFullyContainedTile(hashes[i], values, valuesIndex, targetPrecision); + valuesIndex = setValuesForFullyContainedTile(s, values, valuesIndex, targetPrecision); } } } diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/AbstractGeoTileGridTiler.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/AbstractGeoTileGridTiler.java index 68717dd8b68a4..e7d3bc4f91b2c 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/AbstractGeoTileGridTiler.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/AbstractGeoTileGridTiler.java @@ -11,6 +11,7 @@ import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils; import org.elasticsearch.xpack.spatial.index.fielddata.GeoRelation; import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues; +import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues; import java.io.IOException; @@ -47,7 +48,7 @@ public long encode(double x, double y) { * @return the number of tiles set by the shape */ @Override - public int setValues(GeoShapeCellValues values, GeoShapeValues.GeoShapeValue geoValue) throws IOException { + public int setValues(GeoShapeCellValues values, ShapeValues.ShapeValue geoValue) throws IOException { GeoShapeValues.BoundingBox bounds = geoValue.boundingBox(); assert bounds.minX() <= bounds.maxX(); @@ -75,7 +76,7 @@ public int setValues(GeoShapeCellValues values, GeoShapeValues.GeoShapeValue geo } } - private GeoRelation relateTile(GeoShapeValues.GeoShapeValue geoValue, int xTile, int yTile, int precision) throws IOException { + private GeoRelation relateTile(ShapeValues.ShapeValue geoValue, int xTile, int yTile, int precision) throws IOException { if (validTile(xTile, yTile, precision)) { final double tiles = 1 << precision; final int minX = GeoEncodingUtils.encodeLongitude(GeoTileUtils.tileToLon(xTile, tiles)); @@ -112,7 +113,7 @@ protected int setValue(GeoShapeCellValues docValues, int xTile, int yTile) { */ protected int setValuesByBruteForceScan( GeoShapeCellValues values, - GeoShapeValues.GeoShapeValue geoValue, + ShapeValues.ShapeValue geoValue, int minXTile, int minYTile, int maxXTile, @@ -137,7 +138,7 @@ protected int setValuesByRasterization( int zTile, GeoShapeCellValues values, int valuesIndex, - GeoShapeValues.GeoShapeValue geoValue + ShapeValues.ShapeValue geoValue ) throws IOException { zTile++; for (int i = 0; i < 2; i++) { diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoGridTiler.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoGridTiler.java index 97150fe53d671..a85607ccf7f0c 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoGridTiler.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoGridTiler.java @@ -7,7 +7,7 @@ package org.elasticsearch.xpack.spatial.search.aggregations.bucket.geogrid; -import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues; +import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues; import java.io.IOException; @@ -44,7 +44,7 @@ public int precision() { * * @return the number of cells the geoValue intersects */ - public abstract int setValues(GeoShapeCellValues docValues, GeoShapeValues.GeoShapeValue geoValue) throws IOException; + public abstract int setValues(GeoShapeCellValues docValues, ShapeValues.ShapeValue geoValue) throws IOException; /** Maximum number of cells that can be created by this tiler */ protected abstract long getMaxCells(); diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoShapeCellIdSource.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoShapeCellIdSource.java index cfd1bce97ab24..03b31e18986ef 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoShapeCellIdSource.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoShapeCellIdSource.java @@ -13,7 +13,7 @@ import org.elasticsearch.index.fielddata.SortedNumericDoubleValues; import org.elasticsearch.search.aggregations.support.ValuesSource; import org.elasticsearch.search.aggregations.support.ValuesSourceType; -import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues; +import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues; import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSource; import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSourceType; @@ -46,7 +46,7 @@ public boolean isFloatingPoint() { @Override public SortedNumericDocValues longValues(LeafReaderContext ctx) { - GeoShapeValues geoValues = valuesSource.geoShapeValues(ctx); + ShapeValues geoValues = valuesSource.shapeValues(ctx); ValuesSourceType vs = geoValues.valuesSourceType(); if (GeoShapeValuesSourceType.instance() == vs) { // docValues are geo shapes diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoShapeCellValues.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoShapeCellValues.java index ba8d2c11681be..9849216febce2 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoShapeCellValues.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoShapeCellValues.java @@ -7,17 +7,17 @@ package org.elasticsearch.xpack.spatial.search.aggregations.bucket.geogrid; -import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues; +import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues; import java.io.IOException; import java.util.function.LongConsumer; /** Sorted numeric doc values for geo shapes */ class GeoShapeCellValues extends ByteTrackingSortingNumericDocValues { - private final GeoShapeValues geoShapeValues; + private final ShapeValues geoShapeValues; protected final GeoGridTiler tiler; - protected GeoShapeCellValues(GeoShapeValues geoShapeValues, GeoGridTiler tiler, LongConsumer circuitBreakerConsumer) { + protected GeoShapeCellValues(ShapeValues geoShapeValues, GeoGridTiler tiler, LongConsumer circuitBreakerConsumer) { super(circuitBreakerConsumer); this.geoShapeValues = geoShapeValues; this.tiler = tiler; diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/metrics/GeoShapeBoundsAggregator.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/metrics/GeoShapeBoundsAggregator.java index bc09c266c3758..03c4058e2684b 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/metrics/GeoShapeBoundsAggregator.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/metrics/GeoShapeBoundsAggregator.java @@ -19,6 +19,7 @@ import org.elasticsearch.search.aggregations.support.AggregationContext; import org.elasticsearch.search.aggregations.support.ValuesSourceConfig; import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues; +import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues; import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSource; import java.io.IOException; @@ -66,13 +67,13 @@ public LeafBucketCollector getLeafCollector(AggregationExecutionContext aggCtx, if (valuesSource == null) { return LeafBucketCollector.NO_OP_COLLECTOR; } - final GeoShapeValues values = valuesSource.geoShapeValues(aggCtx.getLeafReaderContext()); + final ShapeValues values = valuesSource.shapeValues(aggCtx.getLeafReaderContext()); return new LeafBucketCollectorBase(sub, values) { @Override public void collect(int doc, long bucket) throws IOException { if (values.advanceExact(doc)) { maybeResize(bucket); - final GeoShapeValues.GeoShapeValue value = values.value(); + final GeoShapeValues.ShapeValue value = values.value(); final GeoShapeValues.BoundingBox bounds = value.boundingBox(); tops.set(bucket, Math.max(tops.get(bucket), bounds.top)); bottoms.set(bucket, Math.min(bottoms.get(bucket), bounds.bottom)); diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/metrics/GeoShapeCentroidAggregator.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/metrics/GeoShapeCentroidAggregator.java index 36840cd8e8cc6..2704f3c0a98b8 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/metrics/GeoShapeCentroidAggregator.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/metrics/GeoShapeCentroidAggregator.java @@ -23,7 +23,7 @@ import org.elasticsearch.search.aggregations.support.AggregationContext; import org.elasticsearch.search.aggregations.support.ValuesSourceConfig; import org.elasticsearch.xpack.spatial.index.fielddata.DimensionalShapeType; -import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues; +import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues; import org.elasticsearch.xpack.spatial.search.aggregations.support.GeoShapeValuesSource; import java.io.IOException; @@ -65,7 +65,7 @@ public LeafBucketCollector getLeafCollector(AggregationExecutionContext aggCtx, if (valuesSource == null) { return LeafBucketCollector.NO_OP_COLLECTOR; } - final GeoShapeValues values = valuesSource.geoShapeValues(aggCtx.getLeafReaderContext()); + final ShapeValues values = valuesSource.shapeValues(aggCtx.getLeafReaderContext()); final CompensatedSum compensatedSumLat = new CompensatedSum(0, 0); final CompensatedSum compensatedSumLon = new CompensatedSum(0, 0); final CompensatedSum compensatedSumWeight = new CompensatedSum(0, 0); @@ -80,14 +80,14 @@ public void collect(int doc, long bucket) throws IOException { // Compute the sum of double values with Kahan summation algorithm which is more // accurate than naive summation. final DimensionalShapeType shapeType = DimensionalShapeType.fromOrdinalByte(dimensionalShapeTypes.get(bucket)); - final GeoShapeValues.GeoShapeValue value = values.value(); + final ShapeValues.ShapeValue value = values.value(); final int compares = shapeType.compareTo(value.dimensionalShapeType()); // update the sum if (compares < 0) { // shape with higher dimensional value final double coordinateWeight = value.weight(); - compensatedSumLat.reset(coordinateWeight * value.lat(), 0.0); - compensatedSumLon.reset(coordinateWeight * value.lon(), 0.0); + compensatedSumLat.reset(coordinateWeight * value.getY(), 0.0); + compensatedSumLon.reset(coordinateWeight * value.getX(), 0.0); compensatedSumWeight.reset(coordinateWeight, 0.0); dimensionalShapeTypes.set(bucket, (byte) value.dimensionalShapeType().ordinal()); } else if (compares == 0) { @@ -96,8 +96,8 @@ public void collect(int doc, long bucket) throws IOException { compensatedSumLon.reset(lonSum.get(bucket), lonCompensations.get(bucket)); compensatedSumWeight.reset(weightSum.get(bucket), weightCompensations.get(bucket)); final double coordinateWeight = value.weight(); - compensatedSumLat.add(coordinateWeight * value.lat()); - compensatedSumLon.add(coordinateWeight * value.lon()); + compensatedSumLat.add(coordinateWeight * value.getY()); + compensatedSumLon.add(coordinateWeight * value.getX()); compensatedSumWeight.add(coordinateWeight); } else { // do not modify centroid calculation since shape is of lower dimension than the running dimension diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/support/GeoShapeValuesSource.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/support/GeoShapeValuesSource.java index 093cf8b7cf39e..7367ece5cefa2 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/support/GeoShapeValuesSource.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/support/GeoShapeValuesSource.java @@ -9,55 +9,34 @@ import org.apache.lucene.index.LeafReaderContext; import org.elasticsearch.common.Rounding; -import org.elasticsearch.index.fielddata.DocValueBits; -import org.elasticsearch.index.fielddata.FieldData; import org.elasticsearch.index.fielddata.SortedBinaryDocValues; import org.elasticsearch.search.aggregations.AggregationExecutionException; -import org.elasticsearch.search.aggregations.support.ValuesSource; import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues; -import org.elasticsearch.xpack.spatial.index.fielddata.IndexGeoShapeFieldData; +import org.elasticsearch.xpack.spatial.index.fielddata.IndexShapeFieldData; +import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues; import java.io.IOException; import java.util.function.Function; -public abstract class GeoShapeValuesSource extends ValuesSource { +public abstract class GeoShapeValuesSource extends ShapeValuesSource { public static final GeoShapeValuesSource EMPTY = new GeoShapeValuesSource() { @Override - public GeoShapeValues geoShapeValues(LeafReaderContext context) { + public GeoShapeValues shapeValues(LeafReaderContext context) { return GeoShapeValues.EMPTY; } - - @Override - public SortedBinaryDocValues bytesValues(LeafReaderContext context) throws IOException { - return FieldData.emptySortedBinary(); - } - }; - public abstract GeoShapeValues geoShapeValues(LeafReaderContext context); - @Override protected Function roundingPreparer() throws IOException { throw new AggregationExecutionException("can't round a [geo_shape]"); } - @Override - public DocValueBits docsWithValue(LeafReaderContext context) throws IOException { - GeoShapeValues values = geoShapeValues(context); - return new DocValueBits() { - @Override - public boolean advanceExact(int doc) throws IOException { - return values.advanceExact(doc); - } - }; - } - public static class Fielddata extends GeoShapeValuesSource { - protected final IndexGeoShapeFieldData indexFieldData; + protected final IndexShapeFieldData indexFieldData; - public Fielddata(IndexGeoShapeFieldData indexFieldData) { + public Fielddata(IndexShapeFieldData indexFieldData) { this.indexFieldData = indexFieldData; } @@ -66,8 +45,8 @@ public SortedBinaryDocValues bytesValues(LeafReaderContext context) { return indexFieldData.load(context).getBytesValues(); } - public GeoShapeValues geoShapeValues(LeafReaderContext context) { - return indexFieldData.load(context).getGeoShapeValues(); + public ShapeValues shapeValues(LeafReaderContext context) { + return indexFieldData.load(context).getShapeValues(); } } } diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/support/GeoShapeValuesSourceType.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/support/GeoShapeValuesSourceType.java index 139f462575349..49b4e9cdf2eda 100644 --- a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/support/GeoShapeValuesSourceType.java +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/support/GeoShapeValuesSourceType.java @@ -9,8 +9,6 @@ import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.util.BytesRef; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.index.fielddata.IndexGeoPointFieldData; import org.elasticsearch.index.fielddata.SortedBinaryDocValues; import org.elasticsearch.script.AggregationScript; @@ -18,15 +16,16 @@ import org.elasticsearch.search.aggregations.support.AggregationContext; import org.elasticsearch.search.aggregations.support.FieldContext; import org.elasticsearch.search.aggregations.support.MissingValues; -import org.elasticsearch.search.aggregations.support.ValueType; import org.elasticsearch.search.aggregations.support.ValuesSource; import org.elasticsearch.search.aggregations.support.ValuesSourceType; import org.elasticsearch.xpack.spatial.index.fielddata.GeoShapeValues; -import org.elasticsearch.xpack.spatial.index.fielddata.IndexGeoShapeFieldData; +import org.elasticsearch.xpack.spatial.index.fielddata.IndexShapeFieldData; +import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues; +import org.elasticsearch.xpack.spatial.index.fielddata.plain.LatLonShapeIndexFieldData; import java.io.IOException; -public class GeoShapeValuesSourceType implements Writeable, ValuesSourceType { +public class GeoShapeValuesSourceType extends ShapeValuesSourceType { static GeoShapeValuesSourceType INSTANCE = new GeoShapeValuesSourceType(); @@ -39,17 +38,11 @@ public ValuesSource getEmpty() { return GeoShapeValuesSource.EMPTY; } - @Override - public ValuesSource getScript(AggregationScript.LeafFactory script, ValueType scriptValueType) { - // TODO (support scripts) - throw new UnsupportedOperationException("geo_shape"); - } - @Override public ValuesSource getField(FieldContext fieldContext, AggregationScript.LeafFactory script, AggregationContext context) { - boolean isGeoPoint = fieldContext.indexFieldData() instanceof IndexGeoPointFieldData; - boolean isGeoShape = fieldContext.indexFieldData() instanceof IndexGeoShapeFieldData; - if (isGeoPoint == false && isGeoShape == false) { + boolean isPoint = fieldContext.indexFieldData() instanceof IndexGeoPointFieldData; + boolean isShape = fieldContext.indexFieldData() instanceof IndexShapeFieldData; + if (isPoint == false && isShape == false) { throw new IllegalArgumentException( "Expected geo_point or geo_shape type on field [" + fieldContext.field() @@ -58,10 +51,10 @@ public ValuesSource getField(FieldContext fieldContext, AggregationScript.LeafFa + "]" ); } - if (isGeoPoint) { + if (isPoint) { return new ValuesSource.GeoPoint.Fielddata((IndexGeoPointFieldData) fieldContext.indexFieldData()); } - return new GeoShapeValuesSource.Fielddata((IndexGeoShapeFieldData) fieldContext.indexFieldData()); + return new GeoShapeValuesSource.Fielddata((LatLonShapeIndexFieldData) fieldContext.indexFieldData()); } @Override @@ -71,12 +64,12 @@ public ValuesSource replaceMissing( DocValueFormat docValueFormat, AggregationContext context ) { - GeoShapeValuesSource geoShapeValuesSource = (GeoShapeValuesSource) valuesSource; - final GeoShapeValues.GeoShapeValue missing = GeoShapeValues.GeoShapeValue.missing(rawMissing.toString()); + GeoShapeValuesSource shapeValuesSource = (GeoShapeValuesSource) valuesSource; + final ShapeValues.ShapeValue missing = GeoShapeValues.EMPTY.missing(rawMissing.toString()); return new GeoShapeValuesSource() { @Override - public GeoShapeValues geoShapeValues(LeafReaderContext context) { - GeoShapeValues values = geoShapeValuesSource.geoShapeValues(context); + public GeoShapeValues shapeValues(LeafReaderContext context) { + ShapeValues values = shapeValuesSource.shapeValues(context); return new GeoShapeValues() { private boolean exists; @@ -95,20 +88,20 @@ public ValuesSourceType valuesSourceType() { } @Override - public GeoShapeValue value() throws IOException { + public ShapeValue value() throws IOException { return exists ? values.value() : missing; } @Override public String toString() { - return "anon MultiGeoShapeValues of [" + super.toString() + "]"; + return "anon MultiShapeValues of [" + super.toString() + "]"; } }; } @Override public SortedBinaryDocValues bytesValues(LeafReaderContext context) throws IOException { - return MissingValues.replaceMissing(geoShapeValuesSource.bytesValues(context), new BytesRef(missing.toString())); + return MissingValues.replaceMissing(valuesSource.bytesValues(context), new BytesRef(missing.toString())); } }; } @@ -117,9 +110,4 @@ public SortedBinaryDocValues bytesValues(LeafReaderContext context) throws IOExc public String typeName() { return "geoshape"; } - - @Override - public void writeTo(StreamOutput out) throws IOException { - - } } diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/support/ShapeValuesSource.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/support/ShapeValuesSource.java new file mode 100644 index 0000000000000..4c99845652f8e --- /dev/null +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/support/ShapeValuesSource.java @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.spatial.search.aggregations.support; + +import org.apache.lucene.index.LeafReaderContext; +import org.elasticsearch.index.fielddata.DocValueBits; +import org.elasticsearch.index.fielddata.FieldData; +import org.elasticsearch.index.fielddata.SortedBinaryDocValues; +import org.elasticsearch.search.aggregations.support.ValuesSource; +import org.elasticsearch.xpack.spatial.index.fielddata.ShapeValues; + +import java.io.IOException; + +public abstract class ShapeValuesSource extends ValuesSource { + public abstract ShapeValues shapeValues(LeafReaderContext context); + + @Override + public SortedBinaryDocValues bytesValues(LeafReaderContext context) throws IOException { + return FieldData.emptySortedBinary(); + } + + @Override + public DocValueBits docsWithValue(LeafReaderContext context) { + ShapeValues values = shapeValues(context); + return new DocValueBits() { + @Override + public boolean advanceExact(int doc) throws IOException { + return values.advanceExact(doc); + } + }; + } +} diff --git a/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/support/ShapeValuesSourceType.java b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/support/ShapeValuesSourceType.java new file mode 100644 index 0000000000000..2bcd714545153 --- /dev/null +++ b/x-pack/plugin/spatial/src/main/java/org/elasticsearch/xpack/spatial/search/aggregations/support/ShapeValuesSourceType.java @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.spatial.search.aggregations.support; + +import org.elasticsearch.script.AggregationScript; +import org.elasticsearch.search.aggregations.support.AggregationContext; +import org.elasticsearch.search.aggregations.support.FieldContext; +import org.elasticsearch.search.aggregations.support.ValueType; +import org.elasticsearch.search.aggregations.support.ValuesSource; +import org.elasticsearch.search.aggregations.support.ValuesSourceType; + +public abstract class ShapeValuesSourceType implements ValuesSourceType { + + @Override + public ValuesSource getScript(AggregationScript.LeafFactory script, ValueType scriptValueType) { + // TODO (support scripts) + throw new UnsupportedOperationException(typeName()); + } + + @Override + public abstract ValuesSource getField(FieldContext fieldContext, AggregationScript.LeafFactory script, AggregationContext context); +} diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/fielddata/CentroidCalculatorTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/fielddata/CentroidCalculatorTests.java index aa817db92c35a..534777d3339a0 100644 --- a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/fielddata/CentroidCalculatorTests.java +++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/fielddata/CentroidCalculatorTests.java @@ -148,25 +148,25 @@ public void testRoundingErrorAndNormalization() throws IOException { { Line line = new Line(new double[] { 180.0, 180.0 }, new double[] { latA, latB }); GeoShapeValues.GeoShapeValue value = GeoTestUtils.geoShapeValue(line); - assertThat(value.lon(), anyOf(equalTo(179.99999991618097), equalTo(-180.0))); + assertThat(value.getX(), anyOf(equalTo(179.99999991618097), equalTo(-180.0))); } { Line line = new Line(new double[] { -180.0, -180.0 }, new double[] { latA, latB }); GeoShapeValues.GeoShapeValue value = GeoTestUtils.geoShapeValue(line); - assertThat(value.lon(), anyOf(equalTo(179.99999991618097), equalTo(-180.0))); + assertThat(value.getX(), anyOf(equalTo(179.99999991618097), equalTo(-180.0))); } { Line line = new Line(new double[] { lonA, lonB }, new double[] { 90.0, 90.0 }); GeoShapeValues.GeoShapeValue value = GeoTestUtils.geoShapeValue(line); - assertThat(value.lat(), equalTo(89.99999995809048)); + assertThat(value.getY(), equalTo(89.99999995809048)); } { Line line = new Line(new double[] { lonA, lonB }, new double[] { -90.0, -90.0 }); GeoShapeValues.GeoShapeValue value = GeoTestUtils.geoShapeValue(line); - assertThat(value.lat(), equalTo(-90.0)); + assertThat(value.getY(), equalTo(-90.0)); } } diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/fielddata/GeometryDocValueTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/fielddata/GeometryDocValueTests.java index 7e21541518836..50e505b58cb33 100644 --- a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/fielddata/GeometryDocValueTests.java +++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/fielddata/GeometryDocValueTests.java @@ -11,6 +11,7 @@ import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeometryNormalizer; import org.elasticsearch.common.geo.Orientation; +import org.elasticsearch.common.geo.SpatialPoint; import org.elasticsearch.geometry.Geometry; import org.elasticsearch.geometry.GeometryCollection; import org.elasticsearch.geometry.LinearRing; @@ -133,11 +134,11 @@ public void testRectangleShape() throws IOException { // Label position is the centroid if within the polygon GeoShapeValues.GeoShapeValue shapeValue = GeoTestUtils.geoShapeValue(rectangle); - GeoPoint labelPosition = shapeValue.labelPosition(); - double labelLon = ((double) minX + maxX) / 2; - double labelLat = ((double) minY + maxY) / 2; - assertEquals(labelLon, labelPosition.lon(), 0.0000001); - assertEquals(labelLat, labelPosition.lat(), 0.0000001); + SpatialPoint labelPosition = shapeValue.labelPosition(); + double labelX = ((double) minX + maxX) / 2; + double labelY = ((double) minY + maxY) / 2; + assertEquals(labelX, labelPosition.getX(), 0.0000001); + assertEquals(labelY, labelPosition.getY(), 0.0000001); } } @@ -155,10 +156,10 @@ public void testNonCentroidPolygon() throws IOException { // Label position is calculated as the first triangle GeoShapeValues.GeoShapeValue shapeValue = GeoTestUtils.geoShapeValue(geometry); - GeoPoint labelPosition = shapeValue.labelPosition(); + SpatialPoint labelPosition = shapeValue.labelPosition(); assertThat( "Expect label position to match one of eight triangles in the two rectangles", - labelPosition, + new GeoPoint(labelPosition), isRectangleLabelPosition(r1, r2) ); } @@ -177,11 +178,11 @@ public void testAntarcticaLabelPosition() throws Exception { // Label position is the centroid if within the polygon GeoShapeValues.GeoShapeValue shapeValue = GeoTestUtils.geoShapeValue(geometry); - GeoPoint labelPosition = shapeValue.labelPosition(); + SpatialPoint labelPosition = shapeValue.labelPosition(); double centroidX = CoordinateEncoder.GEO.decodeX(reader.getCentroidX()); double centroidY = CoordinateEncoder.GEO.decodeY(reader.getCentroidY()); - assertEquals(centroidX, labelPosition.lon(), 0.0000001); - assertEquals(centroidY, labelPosition.lat(), 0.0000001); + assertEquals(centroidX, labelPosition.getX(), 0.0000001); + assertEquals(centroidY, labelPosition.getY(), 0.0000001); Circle tolerance = new Circle(centroidY, centroidX, 1); assertTrue("Expect label position to be within the geometry", shapeValue.relate(tolerance) != GeoRelation.QUERY_DISJOINT); } @@ -192,11 +193,11 @@ public void testFranceLabelPosition() throws Exception { // Label position is the centroid if within the polygon GeoShapeValues.GeoShapeValue shapeValue = GeoTestUtils.geoShapeValue(geometry); - GeoPoint labelPosition = shapeValue.labelPosition(); + SpatialPoint labelPosition = shapeValue.labelPosition(); double centroidX = CoordinateEncoder.GEO.decodeX(reader.getCentroidX()); double centroidY = CoordinateEncoder.GEO.decodeY(reader.getCentroidY()); - assertEquals(centroidX, labelPosition.lon(), 0.0000001); - assertEquals(centroidY, labelPosition.lat(), 0.0000001); + assertEquals(centroidX, labelPosition.getX(), 0.0000001); + assertEquals(centroidY, labelPosition.getY(), 0.0000001); Circle tolerance = new Circle(centroidY, centroidX, 1); assertTrue("Expect label position to be within the geometry", shapeValue.relate(tolerance) != GeoRelation.QUERY_DISJOINT); } diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/GeoFieldMapperTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/GeoFieldMapperTests.java new file mode 100644 index 0000000000000..7e526e32b0c22 --- /dev/null +++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/GeoFieldMapperTests.java @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.spatial.index.mapper; + +import org.elasticsearch.index.mapper.MappedFieldType; +import org.elasticsearch.index.mapper.MapperTestCase; +import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xpack.spatial.LocalStateSpatialPlugin; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; + +/** Base class for testing geo field mappers */ +public abstract class GeoFieldMapperTests extends MapperTestCase { + + static final String FIELD_NAME = "field"; + + @Override + protected Collection getPlugins() { + return Collections.singletonList(new LocalStateSpatialPlugin()); + } + + @Override + protected void assertSearchable(MappedFieldType fieldType) {} + + @Override + protected void minimalMapping(XContentBuilder b) throws IOException { + b.field("type", getFieldName()); + } + + @Override + protected Object getSampleValueForDocument() { + return "POINT (14.0 15.0)"; + } + + protected abstract String getFieldName(); +} diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldMapperTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldMapperTests.java index 07a42660003a8..07554b6a333aa 100644 --- a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldMapperTests.java +++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/GeoShapeWithDocValuesFieldMapperTests.java @@ -11,25 +11,23 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.geo.Orientation; +import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper; +import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper; +import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper.AbstractShapeGeometryFieldType; import org.elasticsearch.index.mapper.DocumentMapper; import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.Mapper; import org.elasticsearch.index.mapper.MapperParsingException; import org.elasticsearch.index.mapper.MapperService; -import org.elasticsearch.index.mapper.MapperTestCase; import org.elasticsearch.index.mapper.ParsedDocument; import org.elasticsearch.index.mapper.SourceToParse; -import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.VersionUtils; import org.elasticsearch.xcontent.ToXContent; -import org.elasticsearch.xcontent.XContentBuilder; import org.elasticsearch.xcontent.XContentFactory; import org.elasticsearch.xcontent.XContentType; -import org.elasticsearch.xpack.spatial.LocalStateSpatialPlugin; import org.junit.AssumptionViolatedException; import java.io.IOException; -import java.util.Collection; import java.util.Collections; import static org.hamcrest.Matchers.containsString; @@ -37,16 +35,11 @@ import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.instanceOf; -public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase { +public class GeoShapeWithDocValuesFieldMapperTests extends GeoFieldMapperTests { @Override - protected void minimalMapping(XContentBuilder b) throws IOException { - b.field("type", "geo_shape"); - } - - @Override - protected Object getSampleValueForDocument() { - return "POINT (14.0 15.0)"; + protected String getFieldName() { + return "geo_shape"; } @Override @@ -64,45 +57,47 @@ protected void registerParameters(ParameterChecker checker) throws IOException { checker.registerConflictCheck("doc_values", b -> b.field("doc_values", false)); checker.registerConflictCheck("index", b -> b.field("index", false)); checker.registerUpdateCheck(b -> b.field("orientation", "right"), m -> { - GeoShapeWithDocValuesFieldMapper gsfm = (GeoShapeWithDocValuesFieldMapper) m; + AbstractShapeGeometryFieldMapper gsfm = (AbstractShapeGeometryFieldMapper) m; assertEquals(Orientation.RIGHT, gsfm.orientation()); }); checker.registerUpdateCheck(b -> b.field("ignore_malformed", true), m -> { - GeoShapeWithDocValuesFieldMapper gpfm = (GeoShapeWithDocValuesFieldMapper) m; + AbstractShapeGeometryFieldMapper gpfm = (AbstractShapeGeometryFieldMapper) m; assertTrue(gpfm.ignoreMalformed()); }); checker.registerUpdateCheck(b -> b.field("ignore_z_value", false), m -> { - GeoShapeWithDocValuesFieldMapper gpfm = (GeoShapeWithDocValuesFieldMapper) m; + AbstractShapeGeometryFieldMapper gpfm = (AbstractShapeGeometryFieldMapper) m; assertFalse(gpfm.ignoreZValue()); }); checker.registerUpdateCheck(b -> b.field("coerce", true), m -> { - GeoShapeWithDocValuesFieldMapper gpfm = (GeoShapeWithDocValuesFieldMapper) m; + AbstractShapeGeometryFieldMapper gpfm = (AbstractShapeGeometryFieldMapper) m; assertTrue(gpfm.coerce()); }); } - @Override - protected Collection getPlugins() { - return Collections.singletonList(new LocalStateSpatialPlugin()); + protected AbstractShapeGeometryFieldType fieldType(Mapper fieldMapper) { + AbstractShapeGeometryFieldMapper shapeFieldMapper = (AbstractShapeGeometryFieldMapper) fieldMapper; + return (AbstractShapeGeometryFieldType) shapeFieldMapper.fieldType(); } - public void testDefaultConfiguration() throws IOException { + protected Class> fieldMapperClass() { + return GeoShapeWithDocValuesFieldMapper.class; + } + public void testDefaultConfiguration() throws IOException { DocumentMapper mapper = createDocumentMapper(fieldMapping(this::minimalMapping)); - Mapper fieldMapper = mapper.mappers().getMapper("field"); - assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); + Mapper fieldMapper = mapper.mappers().getMapper(FIELD_NAME); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - GeoShapeWithDocValuesFieldMapper geoShapeFieldMapper = (GeoShapeWithDocValuesFieldMapper) fieldMapper; - assertThat(geoShapeFieldMapper.fieldType().orientation(), equalTo(Orientation.RIGHT)); - assertTrue(geoShapeFieldMapper.fieldType().hasDocValues()); + AbstractShapeGeometryFieldType fieldType = fieldType(fieldMapper); + assertThat(fieldType.orientation(), equalTo(Orientation.RIGHT)); + assertTrue(fieldType.hasDocValues()); } public void testDefaultDocValueConfigurationOnPre7_8() throws IOException { - Version oldVersion = VersionUtils.randomVersionBetween(random(), Version.V_7_0_0, Version.V_7_7_0); DocumentMapper defaultMapper = createDocumentMapper(oldVersion, fieldMapping(this::minimalMapping)); - Mapper fieldMapper = defaultMapper.mappers().getMapper("field"); - assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); + Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); GeoShapeWithDocValuesFieldMapper geoShapeFieldMapper = (GeoShapeWithDocValuesFieldMapper) fieldMapper; assertFalse(geoShapeFieldMapper.fieldType().hasDocValues()); @@ -114,26 +109,28 @@ public void testDefaultDocValueConfigurationOnPre7_8() throws IOException { public void testOrientationParsing() throws IOException { DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(b -> { - b.field("type", "geo_shape"); + b.field("type", getFieldName()); b.field("orientation", "left"); })); - Mapper fieldMapper = defaultMapper.mappers().getMapper("field"); - assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); + Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - Orientation orientation = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).fieldType().orientation(); + AbstractShapeGeometryFieldType fieldType = fieldType(fieldMapper); + Orientation orientation = fieldType.orientation(); assertThat(orientation, equalTo(Orientation.CLOCKWISE)); assertThat(orientation, equalTo(Orientation.LEFT)); assertThat(orientation, equalTo(Orientation.CW)); // explicit right orientation test defaultMapper = createDocumentMapper(fieldMapping(b -> { - b.field("type", "geo_shape"); + b.field("type", getFieldName()); b.field("orientation", "right"); })); - fieldMapper = defaultMapper.mappers().getMapper("field"); - assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); + fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - orientation = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).fieldType().orientation(); + fieldType = fieldType(fieldMapper); + orientation = fieldType.orientation(); assertThat(orientation, equalTo(Orientation.COUNTER_CLOCKWISE)); assertThat(orientation, equalTo(Orientation.RIGHT)); assertThat(orientation, equalTo(Orientation.CCW)); @@ -145,23 +142,23 @@ public void testOrientationParsing() throws IOException { public void testCoerceParsing() throws IOException { DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(b -> { - b.field("type", "geo_shape"); + b.field("type", getFieldName()); b.field("coerce", true); })); - Mapper fieldMapper = defaultMapper.mappers().getMapper("field"); - assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); + Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - boolean coerce = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).coerce(); + boolean coerce = ((AbstractShapeGeometryFieldMapper) fieldMapper).coerce(); assertThat(coerce, equalTo(true)); defaultMapper = createDocumentMapper(fieldMapping(b -> { - b.field("type", "geo_shape"); + b.field("type", getFieldName()); b.field("coerce", false); })); - fieldMapper = defaultMapper.mappers().getMapper("field"); - assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); + fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - coerce = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).coerce(); + coerce = ((AbstractShapeGeometryFieldMapper) fieldMapper).coerce(); assertThat(coerce, equalTo(false)); } @@ -171,24 +168,24 @@ public void testCoerceParsing() throws IOException { */ public void testIgnoreZValue() throws IOException { DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(b -> { - b.field("type", "geo_shape"); + b.field("type", getFieldName()); b.field("ignore_z_value", true); })); - Mapper fieldMapper = defaultMapper.mappers().getMapper("field"); - assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); + Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - boolean ignoreZValue = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).ignoreZValue(); + boolean ignoreZValue = ((AbstractGeometryFieldMapper) fieldMapper).ignoreZValue(); assertThat(ignoreZValue, equalTo(true)); // explicit false accept_z_value test defaultMapper = createDocumentMapper(fieldMapping(b -> { - b.field("type", "geo_shape"); + b.field("type", getFieldName()); b.field("ignore_z_value", false); })); - fieldMapper = defaultMapper.mappers().getMapper("field"); - assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); + fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - ignoreZValue = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).ignoreZValue(); + ignoreZValue = ((AbstractGeometryFieldMapper) fieldMapper).ignoreZValue(); assertThat(ignoreZValue, equalTo(false)); } @@ -198,45 +195,45 @@ public void testIgnoreZValue() throws IOException { public void testIgnoreMalformedParsing() throws IOException { DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(b -> { - b.field("type", "geo_shape"); + b.field("type", getFieldName()); b.field("ignore_malformed", true); })); - Mapper fieldMapper = defaultMapper.mappers().getMapper("field"); - assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); + Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - boolean ignoreMalformed = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).ignoreMalformed(); + boolean ignoreMalformed = ((AbstractGeometryFieldMapper) fieldMapper).ignoreMalformed(); assertThat(ignoreMalformed, equalTo(true)); // explicit false ignore_malformed test defaultMapper = createDocumentMapper(fieldMapping(b -> { - b.field("type", "geo_shape"); + b.field("type", getFieldName()); b.field("ignore_malformed", false); })); - fieldMapper = defaultMapper.mappers().getMapper("field"); - assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); + fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - ignoreMalformed = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).ignoreMalformed(); + ignoreMalformed = ((AbstractGeometryFieldMapper) fieldMapper).ignoreMalformed(); assertThat(ignoreMalformed, equalTo(false)); } public void testIgnoreMalformedValues() throws IOException { DocumentMapper ignoreMapper = createDocumentMapper(fieldMapping(b -> { - b.field("type", "geo_shape"); + b.field("type", getFieldName()); b.field("ignore_malformed", true); })); DocumentMapper failMapper = createDocumentMapper(fieldMapping(b -> { - b.field("type", "geo_shape"); + b.field("type", getFieldName()); b.field("ignore_malformed", false); })); { BytesReference arrayedDoc = BytesReference.bytes( - XContentFactory.jsonBuilder().startObject().field("field", "Bad shape").endObject() + XContentFactory.jsonBuilder().startObject().field(FIELD_NAME, "Bad shape").endObject() ); SourceToParse sourceToParse = new SourceToParse("1", arrayedDoc, XContentType.JSON); ParsedDocument document = ignoreMapper.parse(sourceToParse); - assertThat(document.docs().get(0).getFields("field").length, equalTo(0)); + assertThat(document.docs().get(0).getFields(FIELD_NAME).length, equalTo(0)); MapperParsingException exception = expectThrows(MapperParsingException.class, () -> failMapper.parse(sourceToParse)); assertThat(exception.getCause().getMessage(), containsString("Unknown geometry type: bad")); } @@ -245,7 +242,7 @@ public void testIgnoreMalformedValues() throws IOException { XContentFactory.jsonBuilder() .startObject() .field( - "field", + FIELD_NAME, "POLYGON ((18.9401790919516 -33.9681188869036, 18.9401790919516 -33.9681188869036, 18.9401790919517 " + "-33.9681188869036, 18.9401790919517 -33.9681188869036, 18.9401790919516 -33.9681188869036))" ) @@ -253,7 +250,7 @@ public void testIgnoreMalformedValues() throws IOException { ); SourceToParse sourceToParse = new SourceToParse("1", arrayedDoc, XContentType.JSON); ParsedDocument document = ignoreMapper.parse(sourceToParse); - assertThat(document.docs().get(0).getFields("field").length, equalTo(0)); + assertThat(document.docs().get(0).getFields(FIELD_NAME).length, equalTo(0)); MapperParsingException exception = expectThrows(MapperParsingException.class, () -> failMapper.parse(sourceToParse)); assertThat(exception.getCause().getMessage(), containsString("at least three non-collinear points required")); } @@ -265,43 +262,43 @@ public void testIgnoreMalformedValues() throws IOException { public void testDocValues() throws IOException { DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(b -> { - b.field("type", "geo_shape"); + b.field("type", getFieldName()); b.field("doc_values", true); })); - Mapper fieldMapper = defaultMapper.mappers().getMapper("field"); - assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); + Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - boolean hasDocValues = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).fieldType().hasDocValues(); + boolean hasDocValues = ((AbstractGeometryFieldMapper) fieldMapper).fieldType().hasDocValues(); assertTrue(hasDocValues); // explicit false doc_values defaultMapper = createDocumentMapper(fieldMapping(b -> { - b.field("type", "geo_shape"); + b.field("type", getFieldName()); b.field("doc_values", false); })); - fieldMapper = defaultMapper.mappers().getMapper("field"); - assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); + fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - hasDocValues = ((GeoShapeWithDocValuesFieldMapper) fieldMapper).fieldType().hasDocValues(); + hasDocValues = ((AbstractGeometryFieldMapper) fieldMapper).fieldType().hasDocValues(); assertFalse(hasDocValues); } - public void testGeoShapeMapperMerge() throws Exception { + public void testShapeMapperMerge() throws Exception { MapperService mapperService = createMapperService(fieldMapping(b -> { - b.field("type", "geo_shape"); + b.field("type", getFieldName()); b.field("orientation", "ccw"); })); merge(mapperService, fieldMapping(b -> { - b.field("type", "geo_shape"); + b.field("type", getFieldName()); b.field("orientation", "cw"); })); - Mapper fieldMapper = mapperService.documentMapper().mappers().getMapper("field"); - assertThat(fieldMapper, instanceOf(GeoShapeWithDocValuesFieldMapper.class)); + Mapper fieldMapper = mapperService.documentMapper().mappers().getMapper(FIELD_NAME); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - GeoShapeWithDocValuesFieldMapper geoShapeFieldMapper = (GeoShapeWithDocValuesFieldMapper) fieldMapper; - assertThat(geoShapeFieldMapper.fieldType().orientation(), equalTo(Orientation.CW)); + AbstractShapeGeometryFieldType fieldType = fieldType(fieldMapper); + assertThat(fieldType.orientation(), equalTo(Orientation.CW)); } public void testInvalidCurrentVersion() { @@ -309,7 +306,7 @@ public void testInvalidCurrentVersion() { MapperParsingException.class, () -> super.createMapperService( Version.CURRENT, - fieldMapping((b) -> { b.field("type", "geo_shape").field("strategy", "recursive"); }) + fieldMapping((b) -> { b.field("type", getFieldName()).field("strategy", "recursive"); }) ) ); assertThat( @@ -320,17 +317,17 @@ public void testInvalidCurrentVersion() { public void testGeoShapeLegacyMerge() throws Exception { Version version = VersionUtils.randomPreviousCompatibleVersion(random(), Version.V_8_0_0); - MapperService m = createMapperService(version, fieldMapping(b -> b.field("type", "geo_shape"))); + MapperService m = createMapperService(version, fieldMapping(b -> b.field("type", getFieldName()))); Exception e = expectThrows( IllegalArgumentException.class, - () -> merge(m, fieldMapping(b -> b.field("type", "geo_shape").field("strategy", "recursive"))) + () -> merge(m, fieldMapping(b -> b.field("type", getFieldName()).field("strategy", "recursive"))) ); assertThat(e.getMessage(), containsString("mapper [field] of type [geo_shape] cannot change strategy from [BKD] to [recursive]")); assertFieldWarnings("strategy"); - MapperService lm = createMapperService(version, fieldMapping(b -> b.field("type", "geo_shape").field("strategy", "recursive"))); - e = expectThrows(IllegalArgumentException.class, () -> merge(lm, fieldMapping(b -> b.field("type", "geo_shape")))); + MapperService lm = createMapperService(version, fieldMapping(b -> b.field("type", getFieldName()).field("strategy", "recursive"))); + e = expectThrows(IllegalArgumentException.class, () -> merge(lm, fieldMapping(b -> b.field("type", getFieldName())))); assertThat(e.getMessage(), containsString("mapper [field] of type [geo_shape] cannot change strategy from [recursive] to [BKD]")); assertFieldWarnings("strategy"); } @@ -345,7 +342,7 @@ private void assertFieldWarnings(String... fieldNames) { public void testSerializeDefaults() throws Exception { DocumentMapper defaultMapper = createDocumentMapper(fieldMapping(this::minimalMapping)); - String serialized = toXContentString((GeoShapeWithDocValuesFieldMapper) defaultMapper.mappers().getMapper("field")); + String serialized = toXContentString((GeoShapeWithDocValuesFieldMapper) defaultMapper.mappers().getMapper(FIELD_NAME)); assertTrue(serialized, serialized.contains("\"orientation\":\"" + Orientation.RIGHT + "\"")); assertTrue(serialized, serialized.contains("\"doc_values\":true")); } @@ -353,22 +350,20 @@ public void testSerializeDefaults() throws Exception { public void testSerializeDocValues() throws IOException { boolean docValues = randomBoolean(); DocumentMapper mapper = createDocumentMapper(fieldMapping(b -> { - b.field("type", "geo_shape"); + b.field("type", getFieldName()); b.field("doc_values", docValues); })); - String serialized = toXContentString((GeoShapeWithDocValuesFieldMapper) mapper.mappers().getMapper("field")); + String serialized = toXContentString((GeoShapeWithDocValuesFieldMapper) mapper.mappers().getMapper(FIELD_NAME)); assertTrue(serialized, serialized.contains("\"orientation\":\"" + Orientation.RIGHT + "\"")); assertTrue(serialized, serialized.contains("\"doc_values\":" + docValues)); } - public void testGeoShapeArrayParsing() throws Exception { + public void testShapeArrayParsing() throws Exception { DocumentMapper mapper = createDocumentMapper(fieldMapping(this::minimalMapping)); - BytesReference arrayedDoc = BytesReference.bytes( - XContentFactory.jsonBuilder() - .startObject() - .startArray("shape") + SourceToParse sourceToParse = source(b -> { + b.startArray("shape") .startObject() .field("type", "Point") .startArray("coordinates") @@ -383,11 +378,9 @@ public void testGeoShapeArrayParsing() throws Exception { .value(-15.0) .endArray() .endObject() - .endArray() - .endObject() - ); + .endArray(); + }); - SourceToParse sourceToParse = new SourceToParse("1", arrayedDoc, XContentType.JSON); ParsedDocument document = mapper.parse(sourceToParse); assertThat(document.docs(), hasSize(1)); IndexableField[] fields = document.docs().get(0).getFields("shape.type"); @@ -401,7 +394,7 @@ public void testMultiFieldsDeprecationWarning() throws Exception { b.startObject("keyword").field("type", "keyword").endObject(); b.endObject(); })); - assertWarnings("Adding multifields to [geo_shape] mappers has no effect and will be forbidden in future"); + assertWarnings("Adding multifields to [" + getFieldName() + "] mappers has no effect and will be forbidden in future"); } public void testSelfIntersectPolygon() throws IOException { @@ -413,7 +406,7 @@ public void testSelfIntersectPolygon() throws IOException { assertThat(ex.getCause().getMessage(), containsString("Polygon self-intersection at lat=0.5 lon=0.5")); } - public String toXContentString(GeoShapeWithDocValuesFieldMapper mapper, boolean includeDefaults) { + public String toXContentString(AbstractShapeGeometryFieldMapper mapper, boolean includeDefaults) { if (includeDefaults) { ToXContent.Params params = new ToXContent.MapParams(Collections.singletonMap("include_defaults", "true")); return Strings.toString(mapper, params); @@ -422,7 +415,7 @@ public String toXContentString(GeoShapeWithDocValuesFieldMapper mapper, boolean } } - public String toXContentString(GeoShapeWithDocValuesFieldMapper mapper) throws IOException { + public String toXContentString(AbstractShapeGeometryFieldMapper mapper) { return toXContentString(mapper, true); } diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/ShapeFieldMapperTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/ShapeFieldMapperTests.java index c4098b852b74a..7530231f18dd4 100644 --- a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/ShapeFieldMapperTests.java +++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/index/mapper/ShapeFieldMapperTests.java @@ -12,6 +12,9 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.geo.Orientation; +import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper; +import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper; +import org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapper.AbstractShapeGeometryFieldType; import org.elasticsearch.index.mapper.DocumentMapper; import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.Mapper; @@ -68,40 +71,51 @@ protected boolean supportsStoredFields() { @Override protected void registerParameters(ParameterChecker checker) throws IOException { + checker.registerConflictCheck("doc_values", b -> b.field("doc_values", false)); checker.registerConflictCheck("index", b -> b.field("index", false)); checker.registerUpdateCheck(b -> b.field("orientation", "right"), m -> { - ShapeFieldMapper gsfm = (ShapeFieldMapper) m; + AbstractShapeGeometryFieldMapper gsfm = (AbstractShapeGeometryFieldMapper) m; assertEquals(Orientation.RIGHT, gsfm.orientation()); }); checker.registerUpdateCheck(b -> b.field("ignore_malformed", true), m -> { - ShapeFieldMapper gpfm = (ShapeFieldMapper) m; + AbstractShapeGeometryFieldMapper gpfm = (AbstractShapeGeometryFieldMapper) m; assertTrue(gpfm.ignoreMalformed()); }); checker.registerUpdateCheck(b -> b.field("ignore_z_value", false), m -> { - ShapeFieldMapper gpfm = (ShapeFieldMapper) m; + AbstractShapeGeometryFieldMapper gpfm = (AbstractShapeGeometryFieldMapper) m; assertFalse(gpfm.ignoreZValue()); }); checker.registerUpdateCheck(b -> b.field("coerce", true), m -> { - ShapeFieldMapper gpfm = (ShapeFieldMapper) m; + AbstractShapeGeometryFieldMapper gpfm = (AbstractShapeGeometryFieldMapper) m; assertTrue(gpfm.coerce()); }); } + protected AbstractShapeGeometryFieldType fieldType(Mapper fieldMapper) { + AbstractShapeGeometryFieldMapper shapeFieldMapper = (AbstractShapeGeometryFieldMapper) fieldMapper; + return (AbstractShapeGeometryFieldType) shapeFieldMapper.fieldType(); + } + + protected Class> fieldMapperClass() { + return ShapeFieldMapper.class; + } + public void testDefaultConfiguration() throws IOException { DocumentMapper mapper = createDocumentMapper(fieldMapping(this::minimalMapping)); Mapper fieldMapper = mapper.mappers().getMapper(FIELD_NAME); - assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class)); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - ShapeFieldMapper shapeFieldMapper = (ShapeFieldMapper) fieldMapper; - assertThat(shapeFieldMapper.fieldType().orientation(), equalTo(Orientation.RIGHT)); - assertTrue(shapeFieldMapper.fieldType().hasDocValues()); + AbstractShapeGeometryFieldType fieldType = fieldType(fieldMapper); + assertThat(fieldType.orientation(), equalTo(Orientation.RIGHT)); + assertTrue(fieldType.hasDocValues()); } public void testDefaultDocValueConfigurationOnPre8_4() throws IOException { + // TODO verify which version this test is actually valid for (when PR is actually merged) Version oldVersion = VersionUtils.randomVersionBetween(random(), Version.V_7_0_0, Version.V_8_3_0); DocumentMapper defaultMapper = createDocumentMapper(oldVersion, fieldMapping(this::minimalMapping)); Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); - assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class)); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); ShapeFieldMapper shapeFieldMapper = (ShapeFieldMapper) fieldMapper; assertFalse(shapeFieldMapper.fieldType().hasDocValues()); @@ -117,9 +131,10 @@ public void testOrientationParsing() throws IOException { b.field("orientation", "left"); })); Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); - assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class)); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - Orientation orientation = ((ShapeFieldMapper) fieldMapper).fieldType().orientation(); + AbstractShapeGeometryFieldType fieldType = fieldType(fieldMapper); + Orientation orientation = fieldType.orientation(); assertThat(orientation, equalTo(Orientation.CLOCKWISE)); assertThat(orientation, equalTo(Orientation.LEFT)); assertThat(orientation, equalTo(Orientation.CW)); @@ -130,9 +145,10 @@ public void testOrientationParsing() throws IOException { b.field("orientation", "right"); })); fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); - assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class)); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - orientation = ((ShapeFieldMapper) fieldMapper).fieldType().orientation(); + fieldType = fieldType(fieldMapper); + orientation = fieldType.orientation(); assertThat(orientation, equalTo(Orientation.COUNTER_CLOCKWISE)); assertThat(orientation, equalTo(Orientation.RIGHT)); assertThat(orientation, equalTo(Orientation.CCW)); @@ -148,9 +164,9 @@ public void testCoerceParsing() throws IOException { b.field("coerce", true); })); Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); - assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class)); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - boolean coerce = ((ShapeFieldMapper) fieldMapper).coerce(); + boolean coerce = ((AbstractShapeGeometryFieldMapper) fieldMapper).coerce(); assertThat(coerce, equalTo(true)); defaultMapper = createDocumentMapper(fieldMapping(b -> { @@ -158,9 +174,9 @@ public void testCoerceParsing() throws IOException { b.field("coerce", false); })); fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); - assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class)); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - coerce = ((ShapeFieldMapper) fieldMapper).coerce(); + coerce = ((AbstractShapeGeometryFieldMapper) fieldMapper).coerce(); assertThat(coerce, equalTo(false)); } @@ -174,9 +190,9 @@ public void testIgnoreZValue() throws IOException { b.field("ignore_z_value", true); })); Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); - assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class)); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - boolean ignoreZValue = ((ShapeFieldMapper) fieldMapper).ignoreZValue(); + boolean ignoreZValue = ((AbstractGeometryFieldMapper) fieldMapper).ignoreZValue(); assertThat(ignoreZValue, equalTo(true)); // explicit false accept_z_value test @@ -185,9 +201,9 @@ public void testIgnoreZValue() throws IOException { b.field("ignore_z_value", false); })); fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); - assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class)); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - ignoreZValue = ((ShapeFieldMapper) fieldMapper).ignoreZValue(); + ignoreZValue = ((AbstractGeometryFieldMapper) fieldMapper).ignoreZValue(); assertThat(ignoreZValue, equalTo(false)); } @@ -201,9 +217,9 @@ public void testIgnoreMalformedParsing() throws IOException { b.field("ignore_malformed", true); })); Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); - assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class)); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - boolean ignoreMalformed = ((ShapeFieldMapper) fieldMapper).ignoreMalformed(); + boolean ignoreMalformed = ((AbstractGeometryFieldMapper) fieldMapper).ignoreMalformed(); assertThat(ignoreMalformed, equalTo(true)); // explicit false ignore_malformed test @@ -212,9 +228,9 @@ public void testIgnoreMalformedParsing() throws IOException { b.field("ignore_malformed", false); })); fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); - assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class)); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - ignoreMalformed = ((ShapeFieldMapper) fieldMapper).ignoreMalformed(); + ignoreMalformed = ((AbstractGeometryFieldMapper) fieldMapper).ignoreMalformed(); assertThat(ignoreMalformed, equalTo(false)); } @@ -231,11 +247,11 @@ public void testIgnoreMalformedValues() throws IOException { { BytesReference arrayedDoc = BytesReference.bytes( - XContentFactory.jsonBuilder().startObject().field("field", "Bad shape").endObject() + XContentFactory.jsonBuilder().startObject().field(FIELD_NAME, "Bad shape").endObject() ); SourceToParse sourceToParse = new SourceToParse("1", arrayedDoc, XContentType.JSON); ParsedDocument document = ignoreMapper.parse(sourceToParse); - assertThat(document.docs().get(0).getFields("field").length, equalTo(0)); + assertThat(document.docs().get(0).getFields(FIELD_NAME).length, equalTo(0)); MapperParsingException exception = expectThrows(MapperParsingException.class, () -> failMapper.parse(sourceToParse)); assertThat(exception.getCause().getMessage(), containsString("Unknown geometry type: bad")); } @@ -244,7 +260,7 @@ public void testIgnoreMalformedValues() throws IOException { XContentFactory.jsonBuilder() .startObject() .field( - "field", + FIELD_NAME, "POLYGON ((18.9401790919516 -33.9681188869036, 18.9401790919516 -33.9681188869036, 18.9401790919517 " + "-33.9681188869036, 18.9401790919517 -33.9681188869036, 18.9401790919516 -33.9681188869036))" ) @@ -252,7 +268,7 @@ public void testIgnoreMalformedValues() throws IOException { ); SourceToParse sourceToParse = new SourceToParse("1", arrayedDoc, XContentType.JSON); ParsedDocument document = ignoreMapper.parse(sourceToParse); - assertThat(document.docs().get(0).getFields("field").length, equalTo(0)); + assertThat(document.docs().get(0).getFields(FIELD_NAME).length, equalTo(0)); MapperParsingException exception = expectThrows(MapperParsingException.class, () -> failMapper.parse(sourceToParse)); assertThat(exception.getCause().getMessage(), containsString("at least three non-collinear points required")); } @@ -267,10 +283,10 @@ public void testDocValues() throws IOException { b.field("type", getFieldName()); b.field("doc_values", true); })); - Mapper fieldMapper = defaultMapper.mappers().getMapper("field"); - assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class)); + Mapper fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - boolean hasDocValues = ((ShapeFieldMapper) fieldMapper).fieldType().hasDocValues(); + boolean hasDocValues = ((AbstractGeometryFieldMapper) fieldMapper).fieldType().hasDocValues(); assertTrue(hasDocValues); // explicit false doc_values @@ -278,10 +294,10 @@ public void testDocValues() throws IOException { b.field("type", getFieldName()); b.field("doc_values", false); })); - fieldMapper = defaultMapper.mappers().getMapper("field"); - assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class)); + fieldMapper = defaultMapper.mappers().getMapper(FIELD_NAME); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - hasDocValues = ((ShapeFieldMapper) fieldMapper).fieldType().hasDocValues(); + hasDocValues = ((AbstractGeometryFieldMapper) fieldMapper).fieldType().hasDocValues(); assertFalse(hasDocValues); } @@ -297,10 +313,10 @@ public void testShapeMapperMerge() throws Exception { })); Mapper fieldMapper = mapperService.documentMapper().mappers().getMapper(FIELD_NAME); - assertThat(fieldMapper, instanceOf(ShapeFieldMapper.class)); + assertThat(fieldMapper, instanceOf(fieldMapperClass())); - ShapeFieldMapper shapeFieldMapper = (ShapeFieldMapper) fieldMapper; - assertThat(shapeFieldMapper.fieldType().orientation(), equalTo(Orientation.CW)); + AbstractShapeGeometryFieldType fieldType = fieldType(fieldMapper); + assertThat(fieldType.orientation(), equalTo(Orientation.CW)); } public void testSerializeDefaults() throws Exception { @@ -316,7 +332,7 @@ public void testSerializeDocValues() throws IOException { b.field("type", getFieldName()); b.field("doc_values", docValues); })); - String serialized = toXContentString((ShapeFieldMapper) mapper.mappers().getMapper("field")); + String serialized = toXContentString((ShapeFieldMapper) mapper.mappers().getMapper(FIELD_NAME)); assertTrue(serialized, serialized.contains("\"orientation\":\"" + Orientation.RIGHT + "\"")); assertTrue(serialized, serialized.contains("\"doc_values\":" + docValues)); } @@ -357,7 +373,7 @@ public void testMultiFieldsDeprecationWarning() throws Exception { b.startObject("keyword").field("type", "keyword").endObject(); b.endObject(); })); - assertWarnings("Adding multifields to [shape] mappers has no effect and will be forbidden in future"); + assertWarnings("Adding multifields to [" + getFieldName() + "] mappers has no effect and will be forbidden in future"); } public void testSelfIntersectPolygon() throws IOException { @@ -369,7 +385,7 @@ public void testSelfIntersectPolygon() throws IOException { assertThat(ex.getCause().getMessage(), containsString("Polygon self-intersection at lat=0.5 lon=0.5")); } - public String toXContentString(ShapeFieldMapper mapper, boolean includeDefaults) { + public String toXContentString(AbstractShapeGeometryFieldMapper mapper, boolean includeDefaults) { if (includeDefaults) { ToXContent.Params params = new ToXContent.MapParams(Collections.singletonMap("include_defaults", "true")); return Strings.toString(mapper, params); @@ -378,7 +394,7 @@ public String toXContentString(ShapeFieldMapper mapper, boolean includeDefaults) } } - public String toXContentString(ShapeFieldMapper mapper) { + public String toXContentString(AbstractShapeGeometryFieldMapper mapper) { return toXContentString(mapper, true); } diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoHexAggregatorTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoHexAggregatorTests.java index 37f10782b17d7..1427da4daf7d3 100644 --- a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoHexAggregatorTests.java +++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/aggregations/bucket/geogrid/GeoHexAggregatorTests.java @@ -42,6 +42,7 @@ protected List getSearchPlugins() { @Override protected List getSupportedValuesSourceTypes() { + // TODO: why is shape here already, it is not supported yet return List.of(GeoShapeValuesSourceType.instance(), CoreValuesSourceType.GEOPOINT); } diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/aggregations/metrics/GeoShapeCentroidAggregatorTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/aggregations/metrics/GeoShapeCentroidAggregatorTests.java index 1efd37f802aec..e512276d0ae63 100644 --- a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/aggregations/metrics/GeoShapeCentroidAggregatorTests.java +++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/search/aggregations/metrics/GeoShapeCentroidAggregatorTests.java @@ -18,6 +18,7 @@ import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeometryNormalizer; import org.elasticsearch.common.geo.Orientation; +import org.elasticsearch.common.geo.SpatialPoint; import org.elasticsearch.geo.GeometryTestUtils; import org.elasticsearch.geometry.Geometry; import org.elasticsearch.index.mapper.MappedFieldType; @@ -210,10 +211,10 @@ private void assertCentroid(RandomIndexWriter w, GeoPoint expectedCentroid) thro InternalGeoCentroid result = searchAndReduce(searcher, new MatchAllDocsQuery(), aggBuilder, fieldType); assertEquals("my_agg", result.getName()); - GeoPoint centroid = result.centroid(); + SpatialPoint centroid = result.centroid(); assertNotNull(centroid); - assertEquals(expectedCentroid.getLat(), centroid.getLat(), GEOHASH_TOLERANCE); - assertEquals(expectedCentroid.getLon(), centroid.getLon(), GEOHASH_TOLERANCE); + assertEquals(expectedCentroid.getX(), centroid.getX(), GEOHASH_TOLERANCE); + assertEquals(expectedCentroid.getY(), centroid.getY(), GEOHASH_TOLERANCE); assertTrue(AggregationInspectionHelper.hasValue(result)); } } diff --git a/x-pack/plugin/vector-tile/src/main/java/org/elasticsearch/xpack/vectortile/rest/GridType.java b/x-pack/plugin/vector-tile/src/main/java/org/elasticsearch/xpack/vectortile/rest/GridType.java index ac8a4831153ab..aa5d5a75fe4c0 100644 --- a/x-pack/plugin/vector-tile/src/main/java/org/elasticsearch/xpack/vectortile/rest/GridType.java +++ b/x-pack/plugin/vector-tile/src/main/java/org/elasticsearch/xpack/vectortile/rest/GridType.java @@ -42,8 +42,8 @@ public byte[] toFeature(GridAggregation gridAggregation, InternalGeoGridBucket b throws IOException { final Rectangle r = gridAggregation.toRectangle(key); final InternalGeoCentroid centroid = bucket.getAggregations().get(RestVectorTileAction.CENTROID_AGG_NAME); - final double featureLon = Math.min(Math.max(centroid.centroid().lon(), r.getMinLon()), r.getMaxLon()); - final double featureLat = Math.min(Math.max(centroid.centroid().lat(), r.getMinLat()), r.getMaxLat()); + final double featureLon = Math.min(Math.max(centroid.centroid().getX(), r.getMinLon()), r.getMaxLon()); + final double featureLat = Math.min(Math.max(centroid.centroid().getY(), r.getMinLat()), r.getMaxLat()); return featureFactory.point(featureLon, featureLat); } };