Refactor GeoPoint and GeoShape with generics (#89388)

* Refactor GeoPoint and GeoShape with generics

In preparation for supporting CartesianPoint and CartesianShape
in aggregations, this PR adds a common interface between GeoPoint
and CartesianPoint, and then uses that to split out some key common
code that will be used in CartesianPoint and CartesianShape aggregations

* Simplify generics (by Ignacio)

Co-authored-by: Ignacio Vera <ivera@apache.org>

* Refactor ElasticPoint to SpatialPoint

* Rename ShapeValuesProvider to ShapeValuesSource

It extends ValuesSource, and is extended by GeoShapeValuesSource.
There is no reason for the suffix `Provider`.

* Code review, mostly AbstractShapeIndexFieldData

* Reverted trivial refactoring

* Removed unused Writable interface implementation

* Further generics refinements

Based on Ignacio's work in 050df953df,
we fix the BoundingBox generics, and also add a little more specificity
to the previous generics (replace <?> with <? extends SpatialPoint>).

* Removed some geo-specific code from BoundingBox

Co-authored-by: Ignacio Vera <ivera@apache.org>
This commit is contained in:
Craig Taverner 2022-09-07 11:23:34 +02:00 committed by GitHub
parent 97f0e98c3a
commit b68d62ba1e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
83 changed files with 2148 additions and 1496 deletions

View file

@ -11,8 +11,8 @@ package org.elasticsearch.script.expression;
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() {

View file

@ -11,8 +11,8 @@ package org.elasticsearch.script.expression;
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 {

View file

@ -11,8 +11,8 @@ package org.elasticsearch.script.expression;
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 @@ final class GeoLongitudeValueSource extends FieldDataBasedDoubleValuesSource {
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

View file

@ -218,7 +218,7 @@ public class MissingValueIT extends ESIntegTestCase {
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));
}
}

View file

@ -45,8 +45,7 @@ public class GeoCentroidIT extends AbstractGeoTestCase {
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 class GeoCentroidIT extends AbstractGeoTestCase {
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 class GeoCentroidIT extends AbstractGeoTestCase {
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 class GeoCentroidIT extends AbstractGeoTestCase {
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 class GeoCentroidIT extends AbstractGeoTestCase {
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 class GeoCentroidIT extends AbstractGeoTestCase {
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 class GeoCentroidIT extends AbstractGeoTestCase {
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);
}
}
}

View file

@ -11,7 +11,7 @@ package org.elasticsearch.search.geo;
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 @@ public class GeoPointScriptDocValuesIT extends ESSingleNodeTestCase {
private double scriptHeight(Map<String, Object> 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<GeoPoint> boundingBox = geometry.getBoundingBox();
return boundingBox.topLeft().lat() - boundingBox.bottomRight().lat();
}
}
private double scriptWidth(Map<String, Object> 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<GeoPoint> boundingBox = geometry.getBoundingBox();
return boundingBox.bottomRight().lon() - boundingBox.topLeft().lon();
}
}
private double scriptLat(Map<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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());

View file

@ -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<T extends SpatialPoint> 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<T extends BoundingBox<? extends SpatialPoint>> {
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() + ")";
}
}

View file

@ -10,108 +10,58 @@ package org.elasticsearch.common.geo;
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<GeoPoint> {
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 class GeoBoundingBox implements ToXContentFragment, Writeable {
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<GeoBoundingBox> {
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();
}
}

View file

@ -30,7 +30,7 @@ import java.io.IOException;
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 class GeoPoint implements ToXContentFragment {
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 class GeoPoint implements ToXContentFragment {
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);
}

View file

@ -537,7 +537,7 @@ public class GeoUtils {
@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);
}

View file

@ -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();
}

View file

@ -14,6 +14,7 @@ import org.apache.lucene.index.NumericDocValues;
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 enum FieldData {
* Returns a {@link DocValueBits} representing all documents from <code>pointValues</code> that have
* a value.
*/
public static DocValueBits docsWithValue(final MultiGeoPointValues pointValues) {
public static DocValueBits docsWithValue(final MultiPointValues<? extends SpatialPoint> pointValues) {
return new DocValueBits() {
@Override
public boolean advanceExact(int doc) throws IOException {
@ -217,7 +218,7 @@ public enum FieldData {
* if the wrapped {@link SortedNumericDocValues} is a singleton.
*/
public static GeoPointValues unwrapSingleton(MultiGeoPointValues values) {
return values.getGeoPointValues();
return values.getPointValues();
}
/**

View file

@ -12,7 +12,7 @@ import org.apache.lucene.index.LeafReaderContext;
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.aggregations.support.ValuesSourceType;
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 class GeoPointScriptFieldData implements IndexGeoPointFieldData {
}
@Override
public LeafGeoPointFieldData load(LeafReaderContext context) {
public LeafPointFieldData<MultiGeoPointValues> 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 class GeoPointScriptFieldData implements IndexGeoPointFieldData {
}
@Override
public LeafGeoPointFieldData loadDirect(LeafReaderContext context) {
public LeafPointFieldData<MultiGeoPointValues> loadDirect(LeafReaderContext context) {
return load(context);
}
}

View file

@ -16,28 +16,20 @@ import java.io.IOException;
/**
* Per-document geo-point values.
*/
public final class GeoPointValues {
public final class GeoPointValues extends PointValues<GeoPoint> {
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());
}
}

View file

@ -11,4 +11,4 @@ package org.elasticsearch.index.fielddata;
/**
* Specialization of {@link IndexFieldData} for geo points.
*/
public interface IndexGeoPointFieldData extends IndexFieldData<LeafGeoPointFieldData> {}
public interface IndexGeoPointFieldData extends IndexPointFieldData<MultiGeoPointValues> {}

View file

@ -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<T extends MultiPointValues<? extends SpatialPoint>> extends IndexFieldData<LeafPointFieldData<T>> {}

View file

@ -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<T extends MultiPointValues<? extends SpatialPoint>> 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)}
*/

View file

@ -18,62 +18,35 @@ import java.io.IOException;
* A stateful lightweight per document set of {@link GeoPoint} values.
* To iterate over values in a document use the following pattern:
* <pre>
* 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 &lt; numValues; i++) {
* GeoPoint value = values.valueAt(i);
* GeoPoint value = values.nextValue();
* // process value
* }
* </pre>
* 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<GeoPoint> {
private final GeoPoint point = new GeoPoint();
private final SortedNumericDocValues numericValues;
/**
* Creates a new {@link MultiGeoPointValues} instance
*/
public MultiGeoPointValues(SortedNumericDocValues numericValues) {
this.numericValues = numericValues;
super(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)}.
*/
@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;
}
}

View file

@ -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<T extends SpatialPoint> {
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<T> getPointValues();
}

View file

@ -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<T extends SpatialPoint> {
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;
}

View file

@ -11,9 +11,11 @@ package org.elasticsearch.index.fielddata;
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 abstract class ScriptDocValues<T> extends AbstractList<T> {
}
}
public abstract static class Geometry<T> extends ScriptDocValues<T> {
public abstract static class BaseGeometry<T extends SpatialPoint, V> extends ScriptDocValues<V> {
public Geometry(Supplier<T> supplier) {
public BaseGeometry(Supplier<V> supplier) {
super(supplier);
}
@ -230,35 +232,52 @@ public abstract class ScriptDocValues<T> extends AbstractList<T> {
public abstract int getDimensionalType();
/** Returns the bounding box of this geometry */
public abstract GeoBoundingBox getBoundingBox();
public abstract BoundingBox<T> 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<T> extends Supplier<T> {
public interface GeometrySupplier<T extends SpatialPoint, V> extends Supplier<V> {
GeoPoint getInternalCentroid();
T getInternalCentroid();
GeoBoundingBox getInternalBoundingBox();
BoundingBox<T> getInternalBoundingBox();
GeoPoint getInternalLabelPosition();
T getInternalLabelPosition();
}
public static class GeoPoints extends Geometry<GeoPoint> {
public static class GeoPoints extends BaseGeometry<GeoPoint, GeoPoint> implements Geometry {
private final GeometrySupplier<GeoPoint> geometrySupplier;
private final GeometrySupplier<GeoPoint, GeoPoint> geometrySupplier;
public GeoPoints(GeometrySupplier<GeoPoint> supplier) {
public GeoPoints(GeometrySupplier<GeoPoint, GeoPoint> supplier) {
super(supplier);
geometrySupplier = supplier;
}
@ -366,7 +385,7 @@ public abstract class ScriptDocValues<T> extends AbstractList<T> {
@Override
public GeoBoundingBox getBoundingBox() {
return size() == 0 ? null : geometrySupplier.getInternalBoundingBox();
return size() == 0 ? null : (GeoBoundingBox) geometrySupplier.getInternalBoundingBox();
}
@Override

View file

@ -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<MultiGeoPointValues> toScriptFieldFactory;
AbstractLatLonPointIndexFieldData(
String fieldName,
ValuesSourceType valuesSourceType,
ToScriptFieldFactory<MultiGeoPointValues> 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<MultiGeoPointValues> 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<MultiGeoPointValues> toScriptFieldFactory;
public Builder(String name, ValuesSourceType valuesSourceType, ToScriptFieldFactory<MultiGeoPointValues> 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);
}
}
}

View file

@ -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<T extends MultiPointValues<? extends SpatialPoint>> implements IndexPointFieldData<T> {
protected final String fieldName;
protected final ValuesSourceType valuesSourceType;
protected final ToScriptFieldFactory<T> toScriptFieldFactory;
protected AbstractPointIndexFieldData(
String fieldName,
ValuesSourceType valuesSourceType,
ToScriptFieldFactory<T> 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"
);
}
}

View file

@ -18,7 +18,7 @@ import java.io.IOException;
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;

View file

@ -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<MultiGeoPointValues> implements IndexGeoPointFieldData {
public LatLonPointIndexFieldData(
String fieldName,
ValuesSourceType valuesSourceType,
ToScriptFieldFactory<MultiGeoPointValues> toScriptFieldFactory
) {
super(fieldName, valuesSourceType, toScriptFieldFactory);
}
@Override
public LeafPointFieldData<MultiGeoPointValues> 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<MultiGeoPointValues> 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<MultiGeoPointValues> toScriptFieldFactory;
public Builder(String name, ValuesSourceType valuesSourceType, ToScriptFieldFactory<MultiGeoPointValues> 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);
}
}
}

View file

@ -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<MultiGeoPointValues> {
protected final ToScriptFieldFactory<MultiGeoPointValues> toScriptFieldFactory;
public AbstractLeafGeoPointFieldData(ToScriptFieldFactory<MultiGeoPointValues> toScriptFieldFactory) {
public LeafGeoPointFieldData(ToScriptFieldFactory<MultiGeoPointValues> 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);
}
}

View file

@ -31,7 +31,7 @@ import org.elasticsearch.geometry.Point;
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 class GeoPointFieldMapper extends AbstractPointGeometryFieldMapper<GeoPoi
}
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) {

View file

@ -307,7 +307,7 @@ public class GeoBoundingBoxQueryBuilder extends AbstractQueryBuilder<GeoBounding
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);

View file

@ -382,7 +382,7 @@ public abstract class DecayFunctionBuilder<DFB extends DecayFunctionBuilder<DFB>
@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 abstract class DecayFunctionBuilder<DFB extends DecayFunctionBuilder<DFB>
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++) {

View file

@ -8,105 +8,62 @@
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<GeoPoint>
implements
Field<GeoPoint>,
DocValuesScriptFieldFactory,
ScriptDocValues.GeometrySupplier<GeoPoint> {
protected final MultiGeoPointValues input;
protected final String name;
protected GeoPoint[] values = new GeoPoint[0];
protected int count;
public class GeoPointDocValuesField extends PointDocValuesField<GeoPoint> {
// 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);
}
protected void resetPointAt(int i, GeoPoint point) {
values[i].reset(point.lat(), point.lon());
}
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();
}
}
@Override
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());
}
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;
@Override
protected double getXFrom(GeoPoint point) {
return point.lon();
}
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);
@Override
protected double getYFrom(GeoPoint point) {
return point.lat();
}
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;
@Override
protected GeoPoint pointOf(double x, double y) {
return new GeoPoint(y, x);
}
@Override
protected double planeDistance(double x1, double y1, GeoPoint point) {
return GeoUtils.planeDistance(y1, x1, point.lat(), point.lon());
}
@Override
public GeoPoint get(GeoPoint 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) {
// While this method seems redundant, it is needed for painless scripting method lookups which cannot handle generics
return super.get(index, defaultValue);
}
@Override
@ -117,73 +74,4 @@ public class GeoPointDocValuesField extends AbstractScriptFieldFactory<GeoPoint>
return geoPoints;
}
@Override
public GeoPoint getInternal(int index) {
return values[index];
}
// maintain bwc by making centroid available to ScriptDocValues.GeoPoints
@Override
public GeoPoint getInternalCentroid() {
return centroid;
}
// maintain bwc by making bounding box available to ScriptDocValues.GeoPoints
@Override
public GeoBoundingBox getInternalBoundingBox() {
return boundingBox;
}
@Override
public GeoPoint getInternalLabelPosition() {
return values[labelIndex];
}
@Override
public String getName() {
return name;
}
@Override
public boolean isEmpty() {
return count == 0;
}
@Override
public int size() {
return count;
}
public GeoPoint get(GeoPoint defaultValue) {
return get(0, defaultValue);
}
public GeoPoint get(int index, GeoPoint defaultValue) {
if (isEmpty() || index < 0 || index >= count) {
return defaultValue;
}
return values[index];
}
@Override
public Iterator<GeoPoint> iterator() {
return new Iterator<GeoPoint>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < count;
}
@Override
public GeoPoint next() {
if (hasNext() == false) {
throw new NoSuchElementException();
}
return values[index++];
}
};
}
}

View file

@ -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<T extends SpatialPoint> extends AbstractScriptFieldFactory<T>
implements
Field<T>,
DocValuesScriptFieldFactory,
ScriptDocValues.GeometrySupplier<T, T> {
protected final MultiPointValues<T> input;
protected final String name;
protected T[] values;
protected int count;
private final Supplier<T> pointMaker;
protected final T centroid;
protected final BoundingBox<T> boundingBox;
private int labelIndex = 0;
public PointDocValuesField(MultiPointValues<T> input, String name, Supplier<T> pointMaker, BoundingBox<T> 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<T> 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<T> 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++];
}
};
}
}

View file

@ -158,7 +158,7 @@ public class GeoTileGridValuesSourceBuilder extends CompositeValuesSourceBuilder
builder.field("precision", precision);
if (geoBoundingBox.isUnbounded() == false) {
builder.startObject(GeoBoundingBox.BOUNDS_FIELD.getPreferredName());
geoBoundingBox.toXContentFragment(builder, true);
geoBoundingBox.toXContentFragment(builder);
builder.endObject();
}
}

View file

@ -169,7 +169,7 @@ public abstract class CellIdSource extends ValuesSource.Numeric {
@Override
public boolean advanceExact(int docId) throws IOException {
return geoValues.advanceExact(docId) && advance(geoValues.geoPointValue());
return geoValues.advanceExact(docId) && advance(geoValues.pointValue());
}
@Override

View file

@ -231,7 +231,7 @@ public abstract class GeoGridAggregationBuilder extends ValuesSourceAggregationB
}
if (geoBoundingBox.isUnbounded() == false) {
builder.startObject(GeoBoundingBox.BOUNDS_FIELD.getPreferredName());
geoBoundingBox.toXContentFragment(builder, true);
geoBoundingBox.toXContentFragment(builder);
builder.endObject();
}
return builder;

View file

@ -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();
}

View file

@ -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 {}

View file

@ -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<String, Object> 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<InternalAggregation> 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<SpatialPoint, Double> extractor;
public FieldExtractor(String name, Function<SpatialPoint, Double> extractor) {
this.name = name;
this.extractor = extractor;
}
}
protected abstract double extractDouble(String name);
@Override
public Object getProperty(List<String> 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 + '}';
}
}

View file

@ -165,7 +165,7 @@ public class InternalGeoBounds extends InternalAggregation implements GeoBounds
GeoBoundingBox bbox = resolveGeoBoundingBox();
if (bbox != null) {
builder.startObject(GeoBoundingBox.BOUNDS_FIELD.getPreferredName());
bbox.toXContentFragment(builder, true);
bbox.toXContentFragment(builder);
builder.endObject();
}
return builder;

View file

@ -11,80 +11,70 @@ package org.elasticsearch.search.aggregations.metrics;
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<String, Object> 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<String, Object> 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 class InternalGeoCentroid extends InternalAggregation implements GeoCentr
}
@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<InternalAggregation> 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 class InternalGeoCentroid extends InternalAggregation implements GeoCentr
return new InternalGeoCentroid(name, centroid, samplingContext.scaleUp(count), getMetadata());
}
@Override
protected boolean mustReduceOnSingleInternalAgg() {
return false;
}
@Override
public Object getProperty(List<String> 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 + '}';
}
}

View file

@ -42,7 +42,7 @@ public class ParsedGeoBounds extends ParsedAggregation implements GeoBounds {
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;

View file

@ -42,14 +42,14 @@ public class ParsedGeoCentroid extends ParsedAggregation implements GeoCentroid
@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 class ParsedGeoCentroid extends ParsedAggregation implements GeoCentroid
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);

View file

@ -29,9 +29,9 @@ import org.elasticsearch.search.aggregations.bucket.terms.UnmappedSignificantTer
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 class AggregationInspectionHelper {
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;
}

View file

@ -162,13 +162,12 @@ public enum CoreValuesSourceType implements ValuesSourceType {
@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

View file

@ -36,7 +36,7 @@ import org.elasticsearch.index.fielddata.IndexGeoPointFieldData;
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 class GeoDistanceSortBuilder extends SortBuilder<GeoDistanceSortBuilder>
}
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);

View file

@ -12,7 +12,7 @@ import org.apache.lucene.document.Field;
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 class GeoFieldDataTests extends AbstractGeoFieldDataTestCase {
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 class GeoFieldDataTests extends AbstractGeoFieldDataTestCase {
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 class GeoFieldDataTests extends AbstractGeoFieldDataTestCase {
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 class GeoFieldDataTests extends AbstractGeoFieldDataTestCase {
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);

View file

@ -74,7 +74,7 @@ public class GeoPointScriptFieldTypeTests extends AbstractNonTextScriptFieldType
@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) {}

View file

@ -13,6 +13,7 @@ import org.elasticsearch.action.search.SearchResponse;
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 java.util.Map;
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 @@ public abstract class AbstractGeoTestCase extends ESIntegTestCase {
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)
);
}
}

View file

@ -16,6 +16,7 @@ import org.apache.lucene.search.MatchAllDocsQuery;
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 @@ public class GeoCentroidAggregatorTests extends AggregatorTestCase {
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));
}
}

View file

@ -9,6 +9,7 @@ package org.elasticsearch.search.aggregations.metrics;
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 @@ public class InternalGeoCentroidTests extends InternalAggregationTestCase<Intern
long totalCount = 0;
for (InternalGeoCentroid input : inputs) {
if (input.count() > 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 @@ public class InternalGeoCentroidTests extends InternalAggregationTestCase<Intern
@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 class InternalGeoCentroidTests extends InternalAggregationTestCase<Intern
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 @@ public class InternalGeoCentroidTests extends InternalAggregationTestCase<Intern
@Override
protected InternalGeoCentroid mutateInstance(InternalGeoCentroid instance) {
String name = instance.getName();
GeoPoint centroid = instance.centroid();
SpatialPoint centroid = instance.centroid();
long count = instance.count();
Map<String, Object> metadata = instance.getMetadata();
switch (between(0, 3)) {
@ -115,9 +116,9 @@ public class InternalGeoCentroidTests extends InternalAggregationTestCase<Intern
} 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;
}

View file

@ -106,7 +106,7 @@ public class GeoTileGroupSource extends SingleGroupSource {
}
if (geoBoundingBox != null) {
builder.startObject(GeoBoundingBox.BOUNDS_FIELD.getPreferredName());
geoBoundingBox.toXContentFragment(builder, true);
geoBoundingBox.toXContentFragment(builder);
builder.endObject();
}
builder.endObject();

View file

@ -202,7 +202,7 @@ class AggregationToJsonProcessor {
queueDocToWrite(keyValuePairs, docCount);
}
addedLeafKeys.forEach(k -> keyValuePairs.remove(k));
addedLeafKeys.forEach(keyValuePairs::remove);
}
private void processDateHistogram(Histogram agg) throws IOException {
@ -400,7 +400,7 @@ class AggregationToJsonProcessor {
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;

View file

@ -9,7 +9,8 @@ package org.elasticsearch.xpack.spatial.search;
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.Point;
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.LocalStateSpatialPlugin;
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 @@ public class GeoShapeScriptDocValuesIT extends ESSingleNodeTestCase {
private double scriptHeight(Map<String, Object> vars) {
Map<?, ?> doc = (Map<?, ?>) vars.get("doc");
ScriptDocValues.Geometry<?> geometry = assertGeometry(doc);
LeafShapeFieldData.ShapeScriptValues<GeoPoint> geometry = assertGeometry(doc);
if (geometry.size() == 0) {
return Double.NaN;
} else {
GeoBoundingBox boundingBox = geometry.getBoundingBox();
BoundingBox<GeoPoint> boundingBox = geometry.getBoundingBox();
return boundingBox.topLeft().lat() - boundingBox.bottomRight().lat();
}
}
private double scriptWidth(Map<String, Object> vars) {
Map<?, ?> doc = (Map<?, ?>) vars.get("doc");
ScriptDocValues.Geometry<?> geometry = assertGeometry(doc);
LeafShapeFieldData.ShapeScriptValues<GeoPoint> geometry = assertGeometry(doc);
if (geometry.size() == 0) {
return Double.NaN;
} else {
GeoBoundingBox boundingBox = geometry.getBoundingBox();
BoundingBox<GeoPoint> boundingBox = geometry.getBoundingBox();
return boundingBox.bottomRight().lon() - boundingBox.topLeft().lon();
}
}
private double scriptLat(Map<String, Object> vars) {
Map<?, ?> doc = (Map<?, ?>) vars.get("doc");
ScriptDocValues.Geometry<?> geometry = assertGeometry(doc);
LeafShapeFieldData.ShapeScriptValues<GeoPoint> geometry = assertGeometry(doc);
return geometry.size() == 0 ? Double.NaN : geometry.getCentroid().lat();
}
private double scriptLon(Map<String, Object> vars) {
Map<?, ?> doc = (Map<?, ?>) vars.get("doc");
ScriptDocValues.Geometry<?> geometry = assertGeometry(doc);
LeafShapeFieldData.ShapeScriptValues<GeoPoint> geometry = assertGeometry(doc);
return geometry.size() == 0 ? Double.NaN : geometry.getCentroid().lon();
}
private double scriptLabelLat(Map<String, Object> vars) {
Map<?, ?> doc = (Map<?, ?>) vars.get("doc");
ScriptDocValues.Geometry<?> geometry = assertGeometry(doc);
LeafShapeFieldData.ShapeScriptValues<GeoPoint> geometry = assertGeometry(doc);
return geometry.size() == 0 ? Double.NaN : geometry.getLabelPosition().lat();
}
private double scriptLabelLon(Map<String, Object> vars) {
Map<?, ?> doc = (Map<?, ?>) vars.get("doc");
ScriptDocValues.Geometry<?> geometry = assertGeometry(doc);
LeafShapeFieldData.ShapeScriptValues<GeoPoint> 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<GeoPoint> 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 @@ public class GeoShapeScriptDocValuesIT extends ESSingleNodeTestCase {
.get();
assertSearchResponse(searchResponse);
Map<String, DocumentField> 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 @@ public class GeoShapeScriptDocValuesIT extends ESSingleNodeTestCase {
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<String, DocumentField> 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
);

View file

@ -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 @@ import java.text.ParseException;
*
* 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 abstract class GeoShapeValues {
/**
* 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<GeoPoint> 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()));
}
}
}

View file

@ -10,6 +10,6 @@ package org.elasticsearch.xpack.spatial.index.fielddata;
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<LeafGeoShapeFieldData> {}
public interface IndexShapeFieldData extends IndexFieldData<LeafShapeFieldData> {}

View file

@ -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 @@ import java.util.function.BiFunction;
*
* TODO: We could instead choose the point closer to the centroid which improves unbalanced trees
*/
public class LabelPositionVisitor<T> extends TriangleTreeReader.DecodedVisitor {
public class LabelPositionVisitor extends TriangleTreeReader.DecodedVisitor {
private T labelPosition;
private final BiFunction<Double, Double, T> pointMaker;
private SpatialPoint labelPosition;
private final BiFunction<Double, Double, SpatialPoint> pointMaker;
public LabelPositionVisitor(CoordinateEncoder encoder, BiFunction<Double, Double, T> pointMaker) {
public LabelPositionVisitor(CoordinateEncoder encoder, BiFunction<Double, Double, SpatialPoint> pointMaker) {
super(encoder);
this.pointMaker = pointMaker;
}
@ -75,7 +77,7 @@ public class LabelPositionVisitor<T> extends TriangleTreeReader.DecodedVisitor {
return labelPosition == null;
}
public T labelPosition() {
public SpatialPoint labelPosition() {
return labelPosition;
}
}

View file

@ -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();
}

View file

@ -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<ShapeValues> toScriptFieldFactory;
public LeafShapeFieldData(ToScriptFieldFactory<ShapeValues> toScriptFieldFactory) {
this.toScriptFieldFactory = toScriptFieldFactory;
}
public static class Empty<T extends SpatialPoint> extends LeafShapeFieldData {
private final ShapeValues emptyValues;
public Empty(ToScriptFieldFactory<ShapeValues> toScriptFieldFactory, ShapeValues emptyValues) {
super(toScriptFieldFactory);
this.emptyValues = emptyValues;
}
@Override
public long ramBytesUsed() {
return 0;
}
@Override
public Collection<Accountable> 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<T extends SpatialPoint> extends ScriptDocValues.BaseGeometry<T, ShapeValues.ShapeValue> {
private final GeometrySupplier<T, ShapeValues.ShapeValue> gsSupplier;
protected ShapeScriptValues(GeometrySupplier<T, ShapeValues.ShapeValue> 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<T> 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();
}
}
}

View file

@ -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:
* <pre>
* MultiGeoValues values = ..;
* // for each docID
* if (values.advanceExact(docId)) {
* GeoValue value = values.value()
* final int numValues = values.count();
* // process value
* }
* </pre>
*
* There is just one value for one document.
*/
public abstract class ShapeValues {
protected final CoordinateEncoder encoder;
protected final Supplier<ShapeValue> supplier;
protected final ShapeIndexer missingShapeIndexer;
/**
* Creates a new {@link ShapeValues} instance
*/
protected ShapeValues(CoordinateEncoder encoder, Supplier<ShapeValue> 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<Double, Double, SpatialPoint> pointMaker;
private final LatLonGeometryRelationVisitor component2DRelationVisitor;
public ShapeValue(CoordinateEncoder encoder, BiFunction<Double, Double, SpatialPoint> 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);
}
}
}

View file

@ -7,81 +7,48 @@
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 {
public abstract class AbstractAtomicGeoShapeShapeFieldData extends LeafShapeFieldData {
private final ToScriptFieldFactory<GeoShapeValues> toScriptFieldFactory;
public AbstractAtomicGeoShapeShapeFieldData(ToScriptFieldFactory<GeoShapeValues> toScriptFieldFactory) {
this.toScriptFieldFactory = toScriptFieldFactory;
public AbstractAtomicGeoShapeShapeFieldData(ToScriptFieldFactory<ShapeValues> 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<ShapeValues> toScriptFieldFactory) {
return new LeafShapeFieldData.Empty<>(toScriptFieldFactory, GeoShapeValues.EMPTY);
}
@Override
public final DocValuesScriptFieldFactory getScriptFieldFactory(String name) {
return toScriptFieldFactory.getScriptFieldFactory(getGeoShapeValues(), name);
}
public static final class GeoShapeScriptValues extends LeafShapeFieldData.ShapeScriptValues<GeoPoint>
implements
ScriptDocValues.Geometry {
public static LeafGeoShapeFieldData empty(final int maxDoc, ToScriptFieldFactory<GeoShapeValues> toScriptFieldFactory) {
return new AbstractAtomicGeoShapeShapeFieldData(toScriptFieldFactory) {
@Override
public long ramBytesUsed() {
return 0;
}
@Override
public Collection<Accountable> getChildResources() {
return Collections.emptyList();
}
@Override
public void close() {}
@Override
public GeoShapeValues getGeoShapeValues() {
return GeoShapeValues.EMPTY;
}
};
}
public static final class GeoShapeScriptValues extends ScriptDocValues.Geometry<GeoShapeValue> {
private final GeometrySupplier<GeoShapeValue> gsSupplier;
public GeoShapeScriptValues(GeometrySupplier<GeoShapeValue> supplier) {
public GeoShapeScriptValues(GeometrySupplier<GeoPoint, ShapeValues.ShapeValue> supplier) {
super(supplier);
this.gsSupplier = supplier;
}
@Override
public int getDimensionalType() {
return gsSupplier.getInternal(0) == null ? -1 : gsSupplier.getInternal(0).dimensionalShapeType().ordinal();
public GeoShapeValues.GeoShapeValue get(int index) {
return (GeoShapeValues.GeoShapeValue) super.get(index);
}
@Override
public GeoPoint getCentroid() {
return gsSupplier.getInternal(0) == null ? null : gsSupplier.getInternalCentroid();
public GeoShapeValues.GeoShapeValue getValue() {
return (GeoShapeValues.GeoShapeValue) super.getValue();
}
@Override
public GeoBoundingBox getBoundingBox() {
return (GeoBoundingBox) super.getBoundingBox();
}
@Override
@ -93,29 +60,5 @@ public abstract class AbstractAtomicGeoShapeShapeFieldData implements LeafGeoSha
public double getMercatorHeight() {
return latToSphericalMercator(getBoundingBox().top()) - latToSphericalMercator(getBoundingBox().bottom());
}
@Override
public GeoBoundingBox getBoundingBox() {
return gsSupplier.getInternal(0) == null ? null : gsSupplier.getInternalBoundingBox();
}
@Override
public GeoPoint getLabelPosition() {
return gsSupplier.getInternal(0) == null ? null : gsSupplier.getInternalLabelPosition();
}
@Override
public GeoShapeValues.GeoShapeValue get(int index) {
return gsSupplier.getInternal(0);
}
public GeoShapeValues.GeoShapeValue getValue() {
return gsSupplier.getInternal(0);
}
@Override
public int size() {
return supplier.size();
}
}
}

View file

@ -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<GeoShapeValues> toScriptFieldFactory;
AbstractLatLonShapeIndexFieldData(
String fieldName,
ValuesSourceType valuesSourceType,
ToScriptFieldFactory<GeoShapeValues> 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<GeoShapeValues> 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<GeoShapeValues> toScriptFieldFactory;
public Builder(String name, ValuesSourceType valuesSourceType, ToScriptFieldFactory<GeoShapeValues> 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);
}
}
}

View file

@ -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<ShapeValues> toScriptFieldFactory;
AbstractShapeIndexFieldData(
String fieldName,
ValuesSourceType valuesSourceType,
ToScriptFieldFactory<ShapeValues> 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?"
);
}
}
}

View file

@ -14,17 +14,19 @@ import org.apache.lucene.util.Accountable;
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<GeoShapeValues> toScriptFieldFactory) {
LatLonShapeDVAtomicShapeFieldData(LeafReader reader, String fieldName, ToScriptFieldFactory<ShapeValues> toScriptFieldFactory) {
super(toScriptFieldFactory);
this.reader = reader;
this.fieldName = fieldName;
@ -46,7 +48,7 @@ final class LatLonShapeDVAtomicShapeFieldData extends AbstractAtomicGeoShapeShap
}
@Override
public GeoShapeValues getGeoShapeValues() {
public ShapeValues getShapeValues() {
try {
final BinaryDocValues binaryValues = DocValues.getBinary(reader, fieldName);
final GeoShapeValues.GeoShapeValue geoShapeValue = new GeoShapeValues.GeoShapeValue();

View file

@ -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<ShapeValues> 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");
}
}

View file

@ -46,8 +46,10 @@ import org.elasticsearch.script.field.DocValuesScriptFieldFactory;
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 class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
@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 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
super.checkIncomingMergeType(mergeWith);
}
public static class GeoShapeDocValuesField extends AbstractScriptFieldFactory<GeoShapeValues.GeoShapeValue>
public static class GeoShapeDocValuesField extends AbstractScriptFieldFactory<ShapeValues.ShapeValue>
implements
Field<GeoShapeValues.GeoShapeValue>,
Field<ShapeValues.ShapeValue>,
DocValuesScriptFieldFactory,
ScriptDocValues.GeometrySupplier<GeoShapeValues.GeoShapeValue> {
ScriptDocValues.GeometrySupplier<GeoPoint, ShapeValues.ShapeValue> {
private final GeoShapeValues in;
private final ShapeValues in;
protected final String name;
private GeoShapeValues.GeoShapeValue value;
@ -354,9 +360,9 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
// maintain bwc by making bounding box and centroid available to GeoShapeValues (ScriptDocValues)
private final GeoPoint centroid = new GeoPoint();
private final GeoBoundingBox boundingBox = new GeoBoundingBox(new GeoPoint(), new GeoPoint());
private AbstractAtomicGeoShapeShapeFieldData.GeoShapeScriptValues geoShapeScriptValues;
private LeafShapeFieldData.ShapeScriptValues<GeoPoint> geoShapeScriptValues;
public GeoShapeDocValuesField(GeoShapeValues in, String name) {
public GeoShapeDocValuesField(ShapeValues in, String name) {
this.in = in;
this.name = name;
}
@ -364,8 +370,8 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
@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 class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
}
@Override
public ScriptDocValues<GeoShapeValues.GeoShapeValue> toScriptDocValues() {
public ScriptDocValues<ShapeValues.ShapeValue> toScriptDocValues() {
if (geoShapeScriptValues == null) {
geoShapeScriptValues = new AbstractAtomicGeoShapeShapeFieldData.GeoShapeScriptValues(this);
}
@ -383,7 +389,7 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
}
@Override
public GeoShapeValues.GeoShapeValue getInternal(int index) {
public ShapeValues.ShapeValue getInternal(int index) {
if (index != 0) {
throw new UnsupportedOperationException();
}
@ -406,7 +412,7 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
@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 class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
}
@Override
public Iterator<GeoShapeValues.GeoShapeValue> iterator() {
return new Iterator<GeoShapeValues.GeoShapeValue>() {
public Iterator<ShapeValues.ShapeValue> iterator() {
return new Iterator<>() {
private int index = 0;
@Override
@ -450,7 +456,7 @@ public class GeoShapeWithDocValuesFieldMapper extends AbstractShapeGeometryField
}
@Override
public GeoShapeValues.GeoShapeValue next() {
public ShapeValues.ShapeValue next() {
if (hasNext() == false) {
throw new NoSuchElementException();
}

View file

@ -12,6 +12,7 @@ import org.elasticsearch.geometry.Rectangle;
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 @@ abstract class AbstractGeoHashGridTiler extends GeoGridTiler {
}
@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 @@ abstract class AbstractGeoHashGridTiler extends GeoGridTiler {
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 @@ abstract class AbstractGeoHashGridTiler extends GeoGridTiler {
}
/**
* 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 @@ abstract class AbstractGeoHashGridTiler extends GeoGridTiler {
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 @@ abstract class AbstractGeoHashGridTiler extends GeoGridTiler {
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 @@ abstract class AbstractGeoHashGridTiler extends GeoGridTiler {
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);
}
}
}

View file

@ -11,6 +11,7 @@ import org.apache.lucene.geo.GeoEncodingUtils;
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 @@ abstract class AbstractGeoTileGridTiler extends GeoGridTiler {
* @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 @@ abstract class AbstractGeoTileGridTiler extends GeoGridTiler {
}
}
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 @@ abstract class AbstractGeoTileGridTiler extends GeoGridTiler {
*/
protected int setValuesByBruteForceScan(
GeoShapeCellValues values,
GeoShapeValues.GeoShapeValue geoValue,
ShapeValues.ShapeValue geoValue,
int minXTile,
int minYTile,
int maxXTile,
@ -137,7 +138,7 @@ abstract class AbstractGeoTileGridTiler extends GeoGridTiler {
int zTile,
GeoShapeCellValues values,
int valuesIndex,
GeoShapeValues.GeoShapeValue geoValue
ShapeValues.ShapeValue geoValue
) throws IOException {
zTile++;
for (int i = 0; i < 2; i++) {

View file

@ -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 abstract class GeoGridTiler {
*
* @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();

View file

@ -13,7 +13,7 @@ import org.elasticsearch.index.fielddata.SortedBinaryDocValues;
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 class GeoShapeCellIdSource extends ValuesSource.Numeric {
@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

View file

@ -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;

View file

@ -19,6 +19,7 @@ import org.elasticsearch.search.aggregations.metrics.MetricsAggregator;
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 final class GeoShapeBoundsAggregator extends MetricsAggregator {
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));

View file

@ -23,7 +23,7 @@ import org.elasticsearch.search.aggregations.metrics.MetricsAggregator;
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 final class GeoShapeCentroidAggregator extends MetricsAggregator {
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 final class GeoShapeCentroidAggregator extends MetricsAggregator {
// 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 final class GeoShapeCentroidAggregator extends MetricsAggregator {
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

View file

@ -9,55 +9,34 @@ package org.elasticsearch.xpack.spatial.search.aggregations.support;
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<Rounding, Rounding.Prepared> 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 abstract class GeoShapeValuesSource extends ValuesSource {
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();
}
}
}

View file

@ -9,8 +9,6 @@ package org.elasticsearch.xpack.spatial.search.aggregations.support;
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.DocValueFormat;
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 class GeoShapeValuesSourceType implements Writeable, ValuesSourceType {
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 class GeoShapeValuesSourceType implements Writeable, ValuesSourceType {
+ "]"
);
}
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 class GeoShapeValuesSourceType implements Writeable, ValuesSourceType {
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 class GeoShapeValuesSourceType implements Writeable, 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 class GeoShapeValuesSourceType implements Writeable, ValuesSourceType {
public String typeName() {
return "geoshape";
}
@Override
public void writeTo(StreamOutput out) throws IOException {
}
}

View file

@ -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);
}
};
}
}

View file

@ -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);
}

View file

@ -148,25 +148,25 @@ public class CentroidCalculatorTests extends ESTestCase {
{
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));
}
}

View file

@ -11,6 +11,7 @@ import org.apache.lucene.geo.Circle;
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 class GeometryDocValueTests extends ESTestCase {
// 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 class GeometryDocValueTests extends ESTestCase {
// 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 class GeometryDocValueTests extends ESTestCase {
// 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 class GeometryDocValueTests extends ESTestCase {
// 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);
}

View file

@ -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<Plugin> 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();
}

View file

@ -11,25 +11,23 @@ import org.elasticsearch.Version;
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.equalTo;
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 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
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<Plugin> getPlugins() {
return Collections.singletonList(new LocalStateSpatialPlugin());
protected AbstractShapeGeometryFieldType<?> fieldType(Mapper fieldMapper) {
AbstractShapeGeometryFieldMapper<?> shapeFieldMapper = (AbstractShapeGeometryFieldMapper<?>) fieldMapper;
return (AbstractShapeGeometryFieldType<?>) shapeFieldMapper.fieldType();
}
protected Class<? extends AbstractShapeGeometryFieldMapper<?>> 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 class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
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 class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
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 class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
*/
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 class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
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 class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
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 class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
);
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 class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
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 class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
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 class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
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 @@ public class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
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 class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
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 class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
.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 class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
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 class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
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 class GeoShapeWithDocValuesFieldMapperTests extends MapperTestCase {
}
}
public String toXContentString(GeoShapeWithDocValuesFieldMapper mapper) throws IOException {
public String toXContentString(AbstractShapeGeometryFieldMapper<?> mapper) {
return toXContentString(mapper, true);
}

View file

@ -12,6 +12,9 @@ import org.elasticsearch.Version;
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 @@ public class ShapeFieldMapperTests extends CartesianFieldMapperTests {
@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<? extends AbstractShapeGeometryFieldMapper<?>> 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 class ShapeFieldMapperTests extends CartesianFieldMapperTests {
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 class ShapeFieldMapperTests extends CartesianFieldMapperTests {
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 class ShapeFieldMapperTests extends CartesianFieldMapperTests {
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 class ShapeFieldMapperTests extends CartesianFieldMapperTests {
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 class ShapeFieldMapperTests extends CartesianFieldMapperTests {
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 class ShapeFieldMapperTests extends CartesianFieldMapperTests {
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 class ShapeFieldMapperTests extends CartesianFieldMapperTests {
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 class ShapeFieldMapperTests extends CartesianFieldMapperTests {
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 class ShapeFieldMapperTests extends CartesianFieldMapperTests {
{
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 class ShapeFieldMapperTests extends CartesianFieldMapperTests {
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 class ShapeFieldMapperTests extends CartesianFieldMapperTests {
);
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 class ShapeFieldMapperTests extends CartesianFieldMapperTests {
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 class ShapeFieldMapperTests extends CartesianFieldMapperTests {
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 class ShapeFieldMapperTests extends CartesianFieldMapperTests {
}));
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 class ShapeFieldMapperTests extends CartesianFieldMapperTests {
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 class ShapeFieldMapperTests extends CartesianFieldMapperTests {
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 class ShapeFieldMapperTests extends CartesianFieldMapperTests {
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 class ShapeFieldMapperTests extends CartesianFieldMapperTests {
}
}
public String toXContentString(ShapeFieldMapper mapper) {
public String toXContentString(AbstractShapeGeometryFieldMapper<?> mapper) {
return toXContentString(mapper, true);
}

View file

@ -42,6 +42,7 @@ public class GeoHexAggregatorTests extends GeoGridAggregatorTestCase<InternalGeo
@Override
protected List<ValuesSourceType> getSupportedValuesSourceTypes() {
// TODO: why is shape here already, it is not supported yet
return List.of(GeoShapeValuesSourceType.instance(), CoreValuesSourceType.GEOPOINT);
}

View file

@ -18,6 +18,7 @@ import org.apache.lucene.tests.index.RandomIndexWriter;
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 @@ public class GeoShapeCentroidAggregatorTests extends AggregatorTestCase {
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));
}
}

View file

@ -42,8 +42,8 @@ enum GridType {
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);
}
};